淘宝客网站如何备案,免费推广工具,四川省微信网站建设公,长沙网站设计流程目录 一、实时时钟#xff08;RTC#xff09;介绍1.1 概述1.2 功能1.3 应用场景1.4 工作原理1.5 对外接口1.6 常见 RTC 芯片1.7 在 Linux 系统中的应用1.8 注意事项 二、Linux 内核 RTC 驱动框架2.1 相关源码文件介绍2.2 核心数据结构2.2.1 struct rtc_device2.2.2 rtc_class… 目录 一、实时时钟RTC介绍1.1 概述1.2 功能1.3 应用场景1.4 工作原理1.5 对外接口1.6 常见 RTC 芯片1.7 在 Linux 系统中的应用1.8 注意事项 二、Linux 内核 RTC 驱动框架2.1 相关源码文件介绍2.2 核心数据结构2.2.1 struct rtc_device2.2.2 rtc_class_ops2.2.3 struct rtc_timer 2.4 RTC驱动实例分析2.5 提供带有中文注释的drivers/rtc/源码2.6 APP层操作RTC 三、参考资料 一、实时时钟RTC介绍
1.1 概述 实时时钟Real-Time Clock简称 RTC是一种能够持续记录时间的电子设备。它通常用于计算机、嵌入式系统和其他需要准确时间记录的设备中。RTC 可以在系统关机或断电的情况下继续运行因此即使在系统重启后也能保持准确的时间。
1.2 功能
时间记录: 记录当前的日期和时间。闹钟功能: 可以设置特定的时间点触发中断用于唤醒系统或执行特定任务。周期性中断:可以设置周期性的中断用于定时任务。电源管理: 通常由电池供电确保在主电源断开时仍能正常工作。
1.3 应用场景
个人电脑: 用于记录 BIOS/UEFI 中的时间。嵌入式系统: 用于工业控制、医疗设备、汽车电子等需要高精度时间的应用。服务器用于日志记录、定时任务调度等。 物联网设备: 用于时间同步和定时任务。
1.4 工作原理
时钟源: RTC 通常使用低频晶体振荡器如 32.768 kHz作为时钟源这种频率的振荡器功耗低且精度高。计数器内部计数器根据时钟源的脉冲进行计数记录秒、分钟、小时、日、月和年。寄存器: 时间和日期信息存储在寄存器中可以通过 I2C、SPI 或其他接口读取和写入。中断: 当设置的闹钟时间到达或周期性中断条件满足时RTC 会触发中断信号。
1.5 对外接口
I2C: 常见的通信接口用于与主控制器通信。SPI: 另一种常见的通信接口适用于高速通信。GPIO: 一些简单的 RTC设备可能使用 GPIO 进行通信。
1.6 常见 RTC 芯片
DS1307: 由 Maxim 生产广泛用于各种嵌入式系统.PCF8563: 由 NXP 生产具有低功耗特性。MCP79410由 Microchip 生产集成了 EEPROM 和时钟功能。RV-1805: 由 Epson 生产具有高精度和低功耗特性。
1.7 在 Linux 系统中的应用
RTC 驱动: Linux 内核提供了 RTC 驱动框架用于管理和操作 RTC 设备。用户空间工具: hwclock命令用于读取和设置硬件时钟date 命令用于读取和设置系统时间。系统启动: 在系统启动时通常会从 RTC 读取时间并设置系统时间。
1.8 注意事项
电池寿命: RTC 通常由纽扣电池供电需要注意电池的寿命和更换。精度校准: 由于环境温度等因素的影响RTC 的时间可能会有偏差需要定期校准。中断处理: 闹钟和周期性中断需要正确处理避免影响系统的正常运行。
二、Linux 内核 RTC 驱动框架 在内核源码中的路径drivers/rtc
2.1 相关源码文件介绍
class.c为底层驱动提供 register 与 unregister 接口用于 RTC 设备的注册/注销。初始化 RTC设备结构、sysfs、procinterface.c提供用户程序与 RTC 的接口函数dev.c将 RTC设备抽象为通用的字符设备提供文件操作函数struct file_operations rtc_dev_fops 的成员,可认为是一个字符设备驱动实现sysfs.c管理 RTC 设备的 sysfs 属性获取 RTC 设备名、日期、时间等proc.c管理 RTC 设备的 procfs 属性提供中断状态和标志查询lib.c提供 RTC、Data 和 Time之间的转换函数rtc-xxx.c不同 RTC 芯片的实际驱动;rtc.h 定义了 RTC 设备的数据结构和操作接口。
2.2 核心数据结构
2.2.1 struct rtc_device
/*** struct rtc_device - 实时时钟设备结构体** 该结构体表示实时时钟 (RTC) 设备的信息和状态。* 包括设备初始化、操作函数指针、中断处理和定时器相关信息。*/
struct rtc_device {// 基本设备结构体struct device dev;// 设备所属模块的所有者struct module *owner;// 设备标识号int id;// 指向包含 RTC 设备操作函数的结构体的指针const struct rtc_class_ops *ops;// 保护操作函数的互斥锁确保线程安全struct mutex ops_lock;// RTC 字符设备结构体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;// 中断频率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; // 标记是否启用了周期性中断功能int pie_enabled;// 工作结构体用于处理中断struct work_struct irqwork;// 有些硬件不支持 UIE 模式int uie_unsupported;// 设置 RTC 时钟所需的时间纳秒。这会影响设置操作的调用时间。偏移量// - 0.5 秒会在墙上时间 10.0 秒时在 9.5 秒调用 RTC 设置// - 1.5 秒会在墙上时间 10.0 秒时在 8.5 秒调用 RTC 设置// - -0.5 秒会在墙上时间 10.0 秒时在 10.5 秒调用 RTC 设置long set_offset_nsec;// 标记设备是否已注册bool registered;// 旧 ABI 支持bool nvram_old_abi;struct bin_attribute *nvram;// RTC 范围的最小值time64_t range_min;// RTC 范围的最大值timeu64_t range_max;// 开始秒数time64_t start_secs;// 偏移秒数time64_t offset_secs;// 标记是否设置了开始时间bool set_start_time;#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL// 工作结构体用于处理 UIE 任务struct work_struct uie_task;// UIE 定时器struct timer_list uie_timer;// 这些字段受 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;
#endifANDROID_KABI_RESERVE(1);
};UIE 是 “Update Interrupt Enable” 的缩写它是一种实时时钟RTC设备的功能。具体来说UIE 模式用于在 RTC 时间更新时生成中断。以下是 UIE 模式的详细解释 时间更新中断 当 RTC 的时间每秒钟更新时会触发一个中断。这个中断可以被用户空间程序捕获和处理。例如某些应用程序可能需要在每一秒的边界上执行特定的操作UIE 模式可以提供这种精确的时间同步。 应用场景 日志记录在需要精确时间戳的日志记录系统中UIE 中断可以确保日志条目的时间戳非常准确。定时任务需要在固定时间间隔内执行的任务可以通过 UIE 中断来触发。 实现细节 在 struct rtc_device 结构体中有几个与 UIE 相关的字段 int uie_unsupported标记某些硬件是否不支持 UIE 模式。long set_offset_nsec设置 RTC 时钟所需的时间偏移量影响中断的触发时间。struct work_struct uie_task 和 struct timer_list uie_timer用于处理 UIE 中断任务和定时器。unsigned int uie_irq_active:1标记 UIE 中断是否激活。unsigned int stop_uie_polling:1标记是否停止 UIE 轮询。unsigned int uie_task_active:1标记 UIE 任务是否激活。unsigned int uie_timer_active:1标记 UIE 定时器是否激活。 配置和启用 UIE 模式通常需要在内核配置中启用例如通过 CONFIG_RTC_INTF_DEV_UIE_EMUL 配置选项。应用程序可以通过 RTC 设备文件接口如 /dev/rtc0来启用或禁用 UIE 模式。 总结来说UIE 模式是为了在 RTC 时间更新时生成中断以便应用程序能够精确地响应时间变化。这对于需要高精度时间同步的应用非常有用。
2.2.2 rtc_class_ops
/** RTC 类操作结构体定义了与 RTC 设备交互的各种方法。* 这些方法中的 device 参数是指物理设备该设备位于硬件所在的总线上如 I2C、Platform、SPI 等* 并且已传递给 rtc_device_register() 函数。通常driver_data 包含设备状态包括 RTC 的 rtc_device 指针。** 大多数这些方法在调用时会持有 rtc_device.ops_lock 锁通过 rtc_*(struct rtc_device *, ...) 调用。** 当前的例外情况主要是文件系统钩子* - proc() 钩子用于 procfs*/
struct rtc_class_ops {/** ioctl 方法用于处理 RTC 设备的 I/O 控制命令。* param dev: RTC 设备* param cmd: 命令码* param arg: 命令参数* return: 成功返回 0失败返回负错误码*/int (*ioctl)(struct device *dev, unsigned int cmd, unsigned long arg);/** read_time 方法用于读取 RTC 设备的时间。* param dev: RTC 设备* param tm: 存储读取时间的结构体指针* return: 成功返回 0失败返回负错误码*/int (*read_time)(struct device *dev, struct rtc_time *tm);/** set_time 方法用于设置 RTC 设备的时间。* param dev: RTC 设备* param tm: 包含要设置时间的结构体指针* return: 成功返回 0失败返回负错误码*/int (*set_time)(struct device *dev, struct rtc_time *tm);/** read_alarm 方法用于读取 RTC 设备的闹钟设置。* param dev: RTC 设备* param alrm: 存储读取闹钟设置的结构体指针* return: 成功返回 0失败返回负错误码*/int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** set_alarm 方法用于设置 RTC 设备的闹钟。* param dev: RTC 设备* param alrm: 包含要设置闹钟的结构体指针* return: 成功返回 0失败返回负错误码*/int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** proc 方法用于处理 procfs 文件系统的请求。* param dev: RTC 设备* param seq: 序列文件指针* return: 成功返回 0失败返回负错误码*/int (*proc)(struct device *dev, struct seq_file *seq);/** alarm_irq_enable 方法用于启用或禁用 RTC 设备的闹钟中断。* param dev: RTC 设备* param enabled: 启用或禁用标志* return: 成功返回 0失败返回负错误码*/int (*alarm_irq_enable)(struct device *dev, unsigned int enabled);/** read_offset 方法用于读取 RTC 设备的时间偏移量。* param dev: RTC 设备* param offset: 存储读取时间偏移量的指针* return: 成功返回 0失败返回负错误码*/int (*read_offset)(struct device *dev, long *offset);/** set_offset 方法用于设置 RTC 设备的时间偏移量。* param dev: RTC 设备* param offset: 要设置的时间偏移量* return: 成功返回 0失败返回负错误码*/int (*set_offset)(struct device *dev, long offset);ANDROID_KABI_RESERVE(1);
};2.2.3 struct rtc_timer
/*** brief RTC定时器结构体* * 该结构体用于表示RTC实时时钟定时器包含了定时器所需的信息和配置如定时周期、回调函数等。*/
struct rtc_timer {/*** brief 定时器队列节点* * 该字段用于将定时器插入到定时器队列中以管理多个定时器的到期时间。*/struct timerqueue_node node;/*** brief 定时周期* * 该字段表示定时器的周期时间使用ktime_t类型来存储时间间隔。*/ktime_t period;/*** brief 定时器回调函数指针* * 当定时器到期时将调用此字段指向的函数。该函数将接收一个指向RTC设备的指针作为参数。*/void (*func)(struct rtc_device *rtc);/*** brief 指向RTC设备的指针* * 该字段用于关联定时器和特定的RTC设备使得定时器可以操作或访问该设备。*/struct rtc_device *rtc;/*** brief 定时器启用状态* * 该字段用于指示定时器是否已启用。当定时器被禁用时其值为0当定时器被启用时其值为非0。*/int enabled;
};2.4 RTC驱动实例分析
drivers/rtc/rtc-rk808.c
// SPDX-License-Identifier: GPL-2.0-only
/** RTC driver for Rockchip RK808** Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd** Author: Chris Zhong zywrock-chips.com* Author: Zhang Qing zhangqingrock-chips.com*/#include linux/module.h
#include linux/kernel.h
#include linux/rtc.h
#include linux/bcd.h
#include linux/mfd/rk808.h
#include linux/platform_device.h
#include linux/i2c.h/* RTC_CTRL_REG bitfields */
#define BIT_RTC_CTRL_REG_STOP_RTC_M BIT(0)/* RK808 has a shadowed register for saving a frozen RTC time.* When user setting GET_TIME to 1, the time will save in this shadowed* register. If set READSEL to 1, user read rtc time register, actually* get the time of that moment. If we need the real time, clr this bit.*/
#define BIT_RTC_CTRL_REG_RTC_GET_TIME BIT(6)
#define BIT_RTC_CTRL_REG_RTC_READSEL_M BIT(7)
#define BIT_RTC_INTERRUPTS_REG_IT_ALARM_M BIT(3)
#define RTC_STATUS_MASK 0xFE
#define RTC_ALARM_STATUS BIT(6)#define SECONDS_REG_MSK 0x7F
#define MINUTES_REG_MAK 0x7F
#define HOURS_REG_MSK 0x3F
#define DAYS_REG_MSK 0x3F
#define MONTHS_REG_MSK 0x1F
#define YEARS_REG_MSK 0xFF
#define WEEKS_REG_MSK 0x7#define RTC_NEED_TRANSITIONS BIT(0)
/* REG_SECONDS_REG through REG_YEARS_REG is how many registers? */#define NUM_TIME_REGS (RK808_WEEKS_REG - RK808_SECONDS_REG 1)
#define NUM_ALARM_REGS (RK808_ALARM_YEARS_REG - RK808_ALARM_SECONDS_REG 1)struct rk_rtc_compat_reg {unsigned int ctrl_reg;unsigned int status_reg;unsigned int alarm_seconds_reg;unsigned int int_reg;unsigned int seconds_reg;
};struct rk808_rtc {struct rk808 *rk808;struct rtc_device *rtc;struct rk_rtc_compat_reg *creg;int irq;unsigned int flag;
};/** 该函数用于处理 Rockchip 日历与公历Gregorian calendar之间的转换。* Rockchip 日历在 RK808 中将 11 月视为有 31 天。我们定义 2016 年 1 月 1 日为两个日历同步的基准日期* 并根据该日期进行其他日期的相对转换。* 注意其他系统软件例如固件在读取相同硬件时必须实现完全相同的转换算法并使用相同的基准日期。** param tm 指向 rtc_time 结构体的指针包含待转换的时间信息** return 返回一个 time64_t 类型的值表示从基准日期2016年1月1日开始的年份偏移量* 如果当前月份大于11月则额外加1以补偿11月多出的一天。*/
static time64_t nov2dec_transitions(struct rtc_time *tm)
{// 计算年份偏移量并检查是否需要补偿11月多出的一天return (tm-tm_year 1900) - 2016 (tm-tm_mon 1 11 ? 1 : 0);
}/*** 将 Rockchip 日期表示转换为公历Gregorian日期。* * 此函数用于处理从 Rockchip 特定日期表示法转换为标准公历的逻辑。* 特别地它处理从11月31日转换为12月1日的特殊情况。* 转换过程分为两个主要步骤* 1. 首先使用 rtc_tm_to_time64 函数将输入的 Rockchip 日期转换为 Unix 时间戳。* 2. 然后根据自输入日期以来发生的11月31日到12月1日的转换次数调整时间戳。* 这种调整是必要的因为在 Rockchip 表示法中11月31日被视为12月1日。* 调整后的时间戳再使用 rtc_time64_to_tm 函数转换回公历格式。* * param tm 指向 rtc_time 结构的指针该结构包含 Rockchip 格式的日期和时间信息。* 经过转换后此结构将更新为对应的公历日期和时间。*/
static void rockchip_to_gregorian(struct rtc_time *tm)
{// 将 Rockchip 日期和时间转换为 Unix 时间戳time64_t time rtc_tm_to_time64(tm);// 根据11月31日到12月1日的转换次数调整时间戳rtc_time64_to_tm(time nov2dec_transitions(tm) * 86400, tm);
}/*** 将公历日期转换为Rockchip格式的日期* 此函数旨在处理特定的日期转换问题即将公历日期转换为Rockchip硬件时钟可以理解的格式* 其中包括处理从11月到12月的过渡这是Rockchip硬件时钟处理日期的一种特殊需求* * param tm 指向RTC时间结构的指针该结构包含日期和时间信息*/
static void gregorian_to_rockchip(struct rtc_time *tm)
{// 计算从11月到12月的过渡天数time64_t extra_days nov2dec_transitions(tm);// 将RTC时间结构转换为自1970年1月1日以来的秒数time64_t time rtc_tm_to_time64(tm);// 根据过渡天数调整时间并将结果转换回RTC时间结构rtc_time64_to_tm(time - extra_days * 86400, tm);/* * 如果调整后的日期导致我们回到了11月这可能发生在特定的年份则进行补偿* 这种补偿机制可以确保日期正确地向前推进即使在复杂的闰年情况下也是如此* 该补偿机制将在2381年之前有效*/if (nov2dec_transitions(tm) extra_days) {// 如果当前月份是11月则简单地将日期推进一天if (tm-tm_mon 1 11)tm-tm_mday; /* This may result in 31! */// 否则重新计算时间确保日期正确地反映了从11月到12月的过渡elsertc_time64_to_tm(time - (extra_days - 1) * 86400, tm);}
}/* Read current time and date in RTC */
/*** 从RTC设备读取当前时间。** 此函数通过I2C总线与RTC芯片通信读取当前的时间和日期并将读取的数据转换为可使用的格式。** param dev RTC设备的device结构指针。* param tm 用于存储读取到的时间和日期信息的rtc_time结构指针。** return 返回0表示成功返回负值表示失败。*/static int rk808_rtc_readtime(struct device *dev, struct rtc_time *tm)
{struct rk808_rtc *rk808_rtc dev_get_drvdata(dev);struct rk808 *rk808 rk808_rtc-rk808;u8 rtc_data[NUM_TIME_REGS];int ret;/* 强制立即更新影子寄存器 */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);if (ret) {dev_err(dev, Failed to update bits rtc_ctrl: %d\n, ret);return ret;}/** 设置GET_TIME位后不能立即读取RTC时间。需要等待大约31.25微秒* 这是32kHz时钟的一个周期。如果在这里清除GET_TIME位则I2C传输时间* 肯定超过31.25微秒在400kHz总线频率下为16 * 2.5微秒。*/ret regmap_update_bits(rk808-regmap, rk808_rtc-creg-ctrl_reg,BIT_RTC_CTRL_REG_RTC_GET_TIME, 0);if (ret) {dev_err(dev, Failed to update bits rtc_ctrl: %d\n, ret);return ret;}/* 批量读取RTC数据 */ret regmap_bulk_read(rk808-regmap, rk808_rtc-creg-seconds_reg,rtc_data, NUM_TIME_REGS);if (ret) {dev_err(dev, Failed to bulk read rtc_data: %d\n, ret);return ret;}/* 将BCD编码的时间数据转换为二进制并填充到tm结构中 */tm-tm_sec bcd2bin(rtc_data[0] SECONDS_REG_MSK);tm-tm_min bcd2bin(rtc_data[1] MINUTES_REG_MAK);tm-tm_hour bcd2bin(rtc_data[2] HOURS_REG_MSK);tm-tm_mday bcd2bin(rtc_data[3] DAYS_REG_MSK);tm-tm_mon (bcd2bin(rtc_data[4] MONTHS_REG_MSK)) - 1;tm-tm_year (bcd2bin(rtc_data[5] YEARS_REG_MSK)) 100;tm-tm_wday bcd2bin(rtc_data[6] WEEKS_REG_MSK);/* 如果需要转换调用rockchip_to_gregorian进行转换 */if (rk808_rtc-flag RTC_NEED_TRANSITIONS)rockchip_to_gregorian(tm);/* 打印调试信息 */dev_dbg(dev, RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n,1900 tm-tm_year, tm-tm_mon 1, tm-tm_mday, tm-tm_wday,tm-tm_hour, tm-tm_min, tm-tm_sec);return ret;
}/*** rk808_rtc_set_time - 设置RTC实时时钟的时间和日期* dev: 设备结构体指针代表RTC设备* tm: 指向rtc_time结构体的指针包含要设置的日期和时间信息* * 此函数将给定的日期和时间信息写入到RTC芯片中以更新RTC的当前时间和日期设置* 它首先将日期和时间信息转换为BCD格式然后通过regmap接口将这些信息写入到RTC的相应寄存器中* * 返回值:* 成功时返回0失败时返回负的错误代码*/
static int rk808_rtc_set_time(struct device *dev, struct rtc_time *tm)
{// 获取RTC设备的驱动数据struct rk808_rtc *rk808_rtc dev_get_drvdata(dev);// 获取RK808芯片的数据struct rk808 *rk808 rk808_rtc-rk808;// 定义一个数组来存储RTC数据u8 rtc_data[NUM_TIME_REGS];// 定义返回值变量int ret;// 调试信息显示正在设置的日期和时间dev_dbg(dev, set RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n,1900 tm-tm_year, tm-tm_mon 1, tm-tm_mday, tm-tm_wday,tm-tm_hour, tm-tm_min, tm-tm_sec);// 如果需要转换则将格里高利日期转换为适合RTC的格式if (rk808_rtc-flag RTC_NEED_TRANSITIONS)gregorian_to_rockchip(tm);// 将时间数据从二进制转换为BCD格式并存储到rtc_data数组中rtc_data[0] bin2bcd(tm-tm_sec);rtc_data[1] bin2bcd(tm-tm_min);rtc_data[2] bin2bcd(tm-tm_hour);rtc_data[3] bin2bcd(tm-tm_mday);rtc_data[4] bin2bcd(tm-tm_mon 1);rtc_data[5] bin2bcd(tm-tm_year - 100);rtc_data[6] bin2bcd(tm-tm_wday);// 停止RTC以便更新RTC寄存器ret regmap_update_bits(rk808-regmap, rk808_rtc-creg-ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M,BIT_RTC_CTRL_REG_STOP_RTC_M);if (ret) {dev_err(dev, Failed to update RTC control: %d\n, ret);return ret;}// 将rtc_data数组中的数据批量写入到RTC寄存器中ret regmap_bulk_write(rk808-regmap, rk808_rtc-creg-seconds_reg,rtc_data, NUM_TIME_REGS);if (ret) {dev_err(dev, Failed to bull write rtc_data: %d\n, ret);return ret;}// 再次启动RTCret regmap_update_bits(rk808-regmap, rk808_rtc-creg-ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M, 0);if (ret) {dev_err(dev, Failed to update RTC control: %d\n, ret);return ret;}return 0;
}/*** rk808_rtc_readalarm - 读取RTC报警时间* dev: 设备结构体指针* alrm: RTC报警时间结构体指针** 此函数从RTC中读取报警时间并将其填充到alrm参数中。它首先读取报警时间寄存器* 然后根据寄存器的值更新alrm结构体中的时间字段。此外它还会读取中断寄存器以确定* 报警是否已启用。** 返回值: 成功时返回0失败时返回负错误代码*/
static int rk808_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 获取RTC设备的驱动数据struct rk808_rtc *rk808_rtc dev_get_drvdata(dev);// 获取RK808芯片结构体指针struct rk808 *rk808 rk808_rtc-rk808;// 定义一个数组来存储报警时间寄存器的值u8 alrm_data[NUM_ALARM_REGS];// 定义一个变量来存储中断寄存器的值uint32_t int_reg;// 定义一个变量来存储函数执行结果int ret;// 从RTC中读取报警时间寄存器的值ret regmap_bulk_read(rk808-regmap,rk808_rtc-creg-alarm_seconds_reg, alrm_data,NUM_ALARM_REGS);if (ret) {// 如果读取失败打印错误信息并返回错误代码dev_err(dev, Failed to read RTC alarm date REG: %d\n, ret);return ret;}// 将读取的寄存器值转换为报警时间alrm-time.tm_sec bcd2bin(alrm_data[0] SECONDS_REG_MSK);alrm-time.tm_min bcd2bin(alrm_data[1] MINUTES_REG_MAK);alrm-time.tm_hour bcd2bin(alrm_data[2] HOURS_REG_MSK);alrm-time.tm_mday bcd2bin(alrm_data[3] DAYS_REG_MSK);alrm-time.tm_mon (bcd2bin(alrm_data[4] MONTHS_REG_MSK)) - 1;alrm-time.tm_year (bcd2bin(alrm_data[5] YEARS_REG_MSK)) 100;// 如果需要转换将Rockchip日历时间转换为公历时间if (rk808_rtc-flag RTC_NEED_TRANSITIONS)rockchip_to_gregorian(alrm-time);// 读取中断寄存器的值以确定报警是否已启用ret regmap_read(rk808-regmap, rk808_rtc-creg-int_reg, int_reg);if (ret) {// 如果读取失败打印错误信息并返回错误代码dev_err(dev, Failed to read RTC INT REG: %d\n, ret);return ret;}// 打印调试信息显示读取的报警时间dev_dbg(dev, alrm read RTC date/time %ptRd(%d) %ptRt\n, alrm-time,alrm-time.tm_wday, alrm-time);// 根据中断寄存器的值设置报警启用状态alrm-enabled (int_reg BIT_RTC_INTERRUPTS_REG_IT_ALARM_M) ? 1 : 0;// 函数执行成功返回0return 0;
}/*** 停止RTC闹钟。* * 该函数通过清除相应的中断使能位和闹钟状态位来停止RTC的闹钟功能。主要用于禁用RTC的闹钟功能。* * param rk808_rtc 指向rk808_rtc结构的指针包含RTC操作所需的信息。* return 成功返回0失败返回负的错误码。*/static int rk808_rtc_stop_alarm(struct rk808_rtc *rk808_rtc)
{struct rk808 *rk808 rk808_rtc-rk808;int ret;/* 禁用RTC闹钟中断 */ret regmap_update_bits(rk808-regmap, rk808_rtc-creg-int_reg,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M, 0);/** 必须在闹钟触发1秒后或禁用闹钟后清除RTC闹钟状态BIT(6)。*/ret regmap_write(rk808-regmap, rk808_rtc-creg-status_reg,RTC_ALARM_STATUS);return ret;
}static int rk808_rtc_start_alarm(struct rk808_rtc *rk808_rtc)
{struct rk808 *rk808 rk808_rtc-rk808;int ret;ret regmap_update_bits(rk808-regmap, rk808_rtc-creg-int_reg,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M);return ret;
}/*** rk808_rtc_setalarm - 设置RTC闹钟* dev: 设备结构体指针* alrm: 闹钟数据结构指针包含闹钟时间和是否启用闹钟的信息* * 此函数负责将给定的闹钟时间设置到RTC芯片中并根据alrm-enabled决定是否启用闹钟。* 它首先停止当前的闹钟然后将时间数据从二进制转换为BCD格式并写入到RTC的相关寄存器中。* 如果需要转换会将时间从格里高利历转换为适合RTC芯片的格式。* * 返回值: 0表示成功负值表示出错*/
static int rk808_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 获取RTC设备的私有数据结构struct rk808_rtc *rk808_rtc dev_get_drvdata(dev);// 获取主设备结构体指针struct rk808 *rk808 rk808_rtc-rk808;// 定义一个数组来存储闹钟数据u8 alrm_data[NUM_ALARM_REGS];int ret;// 停止当前的闹钟ret rk808_rtc_stop_alarm(rk808_rtc);if (ret) {// 如果停止闹钟失败打印错误信息并返回错误码dev_err(dev, Failed to stop alarm: %d\n, ret);return ret;}// 打印设置的闹钟时间信息dev_dbg(dev, alrm set RTC date/time %ptRd(%d) %ptRt\n, alrm-time,alrm-time.tm_wday, alrm-time);// 如果需要转换将时间从格里高利历转换为适合RTC芯片的格式if (rk808_rtc-flag RTC_NEED_TRANSITIONS)gregorian_to_rockchip(alrm-time);// 将时间数据从二进制转换为BCD格式并存储到数组中alrm_data[0] bin2bcd(alrm-time.tm_sec);alrm_data[1] bin2bcd(alrm-time.tm_min);alrm_data[2] bin2bcd(alrm-time.tm_hour);alrm_data[3] bin2bcd(alrm-time.tm_mday);alrm_data[4] bin2bcd(alrm-time.tm_mon 1);alrm_data[5] bin2bcd(alrm-time.tm_year - 100);// 将闹钟数据写入到RTC的相关寄存器中ret regmap_bulk_write(rk808-regmap,rk808_rtc-creg-alarm_seconds_reg, alrm_data,NUM_ALARM_REGS);if (ret) {// 如果写入失败打印错误信息并返回错误码dev_err(dev, Failed to bulk write: %d\n, ret);return ret;}// 如果闹钟被启用启动闹钟if (alrm-enabled) {ret rk808_rtc_start_alarm(rk808_rtc);if (ret) {// 如果启动闹钟失败打印错误信息并返回错误码dev_err(dev, Failed to start alarm: %d\n, ret);return ret;}}// 返回成功return 0;
}static int rk808_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{struct rk808_rtc *rk808_rtc dev_get_drvdata(dev);if (enabled)return rk808_rtc_start_alarm(rk808_rtc);return rk808_rtc_stop_alarm(rk808_rtc);
}/** We will just handle setting the frequency and make use the framework for* reading the periodic interupts.** freq: Current periodic IRQ freq:* bit 0: every second* bit 1: every minute* bit 2: every hour* bit 3: every day*/
static irqreturn_t rk808_alarm_irq(int irq, void *data)
{struct rk808_rtc *rk808_rtc data;struct rk808 *rk808 rk808_rtc-rk808;struct i2c_client *client rk808-i2c;int ret;ret regmap_write(rk808-regmap, rk808_rtc-creg-status_reg,RTC_STATUS_MASK);if (ret) {dev_err(client-dev, %s:Failed to update RTC status: %d\n,__func__, ret);return ret;}rtc_update_irq(rk808_rtc-rtc, 1, RTC_IRQF | RTC_AF);dev_dbg(client-dev, %s:irq%d\n, __func__, irq);return IRQ_HANDLED;
}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,
};#ifdef CONFIG_PM_SLEEP
/* Turn off the alarm if it should not be a wake source. */
static int rk808_rtc_suspend(struct device *dev)
{struct rk808_rtc *rk808_rtc dev_get_drvdata(dev);if (device_may_wakeup(dev))enable_irq_wake(rk808_rtc-irq);return 0;
}/* Enable the alarm if it should be enabled (in case it was disabled to* prevent use as a wake source).*/
static int rk808_rtc_resume(struct device *dev)
{struct rk808_rtc *rk808_rtc dev_get_drvdata(dev);if (device_may_wakeup(dev))disable_irq_wake(rk808_rtc-irq);return 0;
}
#endifstatic SIMPLE_DEV_PM_OPS(rk808_rtc_pm_ops, rk808_rtc_suspend, rk808_rtc_resume);static struct rk_rtc_compat_reg rk808_creg {.ctrl_reg RK808_RTC_CTRL_REG,.status_reg RK808_RTC_STATUS_REG,.alarm_seconds_reg RK808_ALARM_SECONDS_REG,.int_reg RK808_RTC_INT_REG,.seconds_reg RK808_SECONDS_REG,
};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,
};/*** brief 实现 RK808 芯片的 RTC 设备探测功能。* * 此函数在加载相应的驱动程序时初始化 RTC 设备。* 主要任务包括* - 检查设备树中是否启用了 RTC 设备。* - 为 RTC 设备结构分配内存。* - 根据不同的芯片变体设置 RTC 控制寄存器。* - 启动 RTC 并启用影子计时器。* - 注册 RTC 设备并请求报警中断。* * param pdev 平台设备指针* return 成功返回 0失败返回负的错误码*/
static int rk808_rtc_probe(struct platform_device *pdev)
{struct rk808 *rk808 dev_get_drvdata(pdev-dev.parent);struct rk808_rtc *rk808_rtc;struct device_node *np;int ret;// 根据芯片变体检查 RTC 设备是否启用switch (rk808-variant) {case RK805_ID:case RK808_ID:case RK816_ID:case RK818_ID:np of_get_child_by_name(pdev-dev.parent-of_node, rtc);if (np !of_device_is_available(np)) {dev_info(pdev-dev, 设备已禁用\n);return -EINVAL;}break;default:break;}// 为 RTC 设备结构分配内存rk808_rtc devm_kzalloc(pdev-dev, sizeof(*rk808_rtc), GFP_KERNEL);if (rk808_rtc NULL)return -ENOMEM;// 根据不同的芯片变体设置控制寄存器switch (rk808-variant) {case RK808_ID:case RK818_ID:rk808_rtc-creg rk808_creg;rk808_rtc-flag | RTC_NEED_TRANSITIONS;break;case RK805_ID:case RK816_ID:rk808_rtc-creg rk808_creg;break;case RK809_ID:case RK817_ID:rk808_rtc-creg rk817_creg;break;default:rk808_rtc-creg rk808_creg;break;}// 设置平台设备数据platform_set_drvdata(pdev, rk808_rtc);rk808_rtc-rk808 rk808;// 启动 RTC 并启用影子计时器ret regmap_update_bits(rk808-regmap, rk808_rtc-creg-ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M |BIT_RTC_CTRL_REG_RTC_READSEL_M,BIT_RTC_CTRL_REG_RTC_READSEL_M);if (ret) {dev_err(pdev-dev, Failed to update RTC control: %d\n, ret);return ret;}ret regmap_write(rk808-regmap, rk808_rtc-creg-status_reg,RTC_STATUS_MASK);if (ret) {dev_err(pdev-dev, Failed to write RTC status: %d\n, ret);return ret;}// 启用设备唤醒功能device_init_wakeup(pdev-dev, 1);// 分配 RTC 设备rk808_rtc-rtc devm_rtc_allocate_device(pdev-dev);if (IS_ERR(rk808_rtc-rtc))return PTR_ERR(rk808_rtc-rtc);// 设置 RTC 操作函数rk808_rtc-rtc-ops rk808_rtc_ops;// 获取 RTC 中断号rk808_rtc-irq platform_get_irq(pdev, 0);if (rk808_rtc-irq 0)return rk808_rtc-irq;// 请求报警中断ret devm_request_threaded_irq(pdev-dev, rk808_rtc-irq, NULL,rk808_alarm_irq, 0, RTC alarm,rk808_rtc);if (ret) {dev_err(pdev-dev, Failed to request alarm IRQ %d: %d\n,rk808_rtc-irq, ret);return ret;}// 注册 RTC 设备return rtc_register_device(rk808_rtc-rtc);
}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);MODULE_DESCRIPTION(RTC driver for the rk808 series PMICs);
MODULE_AUTHOR(Chris Zhong zywrock-chips.com);
MODULE_AUTHOR(Zhang Qing zhangqingrock-chips.com);
MODULE_LICENSE(GPL);
MODULE_ALIAS(platform:rk808-rtc);2.5 提供带有中文注释的drivers/rtc/源码
在本文章的附带绑定资源中
2.6 APP层操作RTC
#include stdio.h
#include fcntl.h
#include unistd.h
#include sys/ioctl.h
#include linux/rtc.h
#include time.h
#include string.h
#include errno.h#define RTC_DEVICE /dev/rtc0void print_time(struct rtc_time *rtc_tm)
{printf(RTC date/time: %04d-%02d-%02d %02d:%02d:%02d\n,rtc_tm-tm_year 1900, rtc_tm-tm_mon 1, rtc_tm-tm_mday,rtc_tm-tm_hour, rtc_tm-tm_min, rtc_tm-tm_sec);
}int main(int argc, char *argv[])
{int fd;struct rtc_time rtc_tm;time_t rawtime;struct tm *timeinfo;// 打开RTC设备fd open(RTC_DEVICE, O_RDWR);if (fd -1) {perror(打开RTC设备失败);return errno;}// 读取当前时间if (ioctl(fd, RTC_RD_TIME, rtc_tm) -1) {perror(读取RTC时间失败);close(fd);return errno;}printf(当前);print_time(rtc_tm);// 设置新的时间time(rawtime);timeinfo localtime(rawtime);rtc_tm.tm_year timeinfo-tm_year;rtc_tm.tm_mon timeinfo-tm_mon;rtc_tm.tm_mday timeinfo-tm_mday;rtc_tm.tm_hour timeinfo-tm_hour;rtc_tm.tm_min timeinfo-tm_min;rtc_tm.tm_sec timeinfo-tm_sec;if (ioctl(fd, RTC_SET_TIME, rtc_tm) -1) {perror(设置RTC时间失败);close(fd);return errno;}printf(设置后的);print_time(rtc_tm);// 关闭RTC设备close(fd);return 0;
}三、参考资料
Linux下RTC子系统驱动Linux NVMEM子系统概述以及RK3588 OTP实例