RTC 其实就是实时时钟,用于记录当前系统时间。当使用 Linux 设备查看时间的时候,RTC就显得尤为重要。
Linux 内核将 RTC 设备抽象为 rtc_device 结构体,所以 RTC 设备驱动就是申请并初始化 rtc_device,最后将 rtc_device 注册到 Linux 内核中,最终内核就会有一个 RTC 设备。
首先先看 rtc_device 结构体,它定义在 include/linux/rtc.h文件中,内容如下:
struct rtc_device {
struct device dev; /* 设备 */
struct module *owner;
int id; /* ID */
const struct rtc_class_ops *ops; /* RTC 设备底层操作函数 */
struct mutex ops_lock;
struct cdev char_dev; /* 字符设备 */
......
};
重点看 ops 成员变量,它是 rtc_class_ops 类型的指针变量,rtc_class_ops 是 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间值等。因此,rtc_class_ops 是需要用户根据所使用的 RTC 设备编写的,此结构体定义在 include/linux/rtc.h 文件中,内容如下:
struct rtc_class_ops {
int (*ioctl)(struct device *, unsigned int, unsigned long);
int (*read_time)(struct device *, struct rtc_time *);
int (*set_time)(struct device *, struct rtc_time *);
int (*read_alarm)(struct device *, struct rtc_wkalrm *);
int (*set_alarm)(struct device *, struct rtc_wkalrm *);
int (*proc)(struct device *, struct seq_file *);
int (*alarm_irq_enable)(struct device *, unsigned int enabled);
int (*read_offset)(struct device *, long *offset);
int (*set_offset)(struct device *, long offset);
};
注意,rtc_class_opt 中的函数只是最底层的 RTC 设备操作函数,并不是提供给应用层的 file_operations 函数操作集合。RTC 是一个字符设备,那肯定有 file_operations 函数操作集。
Linux 内核提供了一个 RTC 通用字符设备驱动文件,文件名为 drivers/rtc/dev.c, dev.c 文
件提供了所有 RTC 设备共用的 file_operations 函数操作集,如下所示:
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,
};
这是标准的字符设备操作集。应用程序可以通过 ioctl 函数来设置/读取时间、设置/读取闹钟,对应的 rtc_dev_ioctl 会通过 rtc_class_ops 中的 read_time、set_time 来对具体的 RTC 设备进行读写操作。
首先是从 rtc_dev_fops 开始,然后进入相应的函数,比如 rtc_dev_ioctl,进入这个函数后,里面的一些操作就是由这个函数提供 rtc_class_ops。
当 rtc_class_ops 准备好以后需要将其注册到 Linux 内核中,这里我们可以使用 rtc_device_register 函数完成注册工作。此函数会申请一个 rtc_device 并且初始化这个 rtc_device,最后向调用者返回这个 rtc_device,原型如下:
/*
* @description : 注册 RTC 驱动
* @param - name : 设备名字
* @param - dev : 设备
* @param - ops : RTC 底层驱动函数集
* @param - owner : 驱动模块拥有者
* @return : 注册成功的话就返回 rtc_device,错误的话会返回一个负值
*/
struct rtc_device *rtc_device_register(const char *name,
struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
当卸载 RTC 驱动的时候需要调用 rtc_device_unregister 函数来注销注册的 rtc_device,函数
原型如下:
/*
* @description : 卸载 RTC 驱动
* @param - rtc : 要删除的rtc_device
* @return : 无
*/
void rtc_device_unregister(struct rtc_device *rtc)
还有另外一对 rtc_device 注册函数 devm_rtc_device_register 和devm_rtc_device_unregister,分别为注册和注销 rtc_device。
直接打开 stm32mp157d-atk.dts 文件,添加下面代码:
&rtc {
status = "okay";
};
追加的 RTC 节点内容很简单,就是把 status 属性改为“okay”。接着我们重新编译设备树,
然后使用新编译的 stm32mp157d-atk.dtb 文件启动开发板。
Linux 内核启动的时候可以看到系统时钟设置信息。Linux 内核在启动的时候将 rtc 设置为 rtc0,大家的启动信息可能会不同,但是基本上都是一样的。
如果要查看时间的话输入“date”命令即可。当前时间为 2000 年 1 月 1 日 03:30:29,很明显时间不对,我们需要重新设置 RTC 时间。
RTC的时间设置也是用的 data 命令,输入 data --help 命令可以看到 data 命令如何设置系统时间。比如现在设置当前时间 2023/11/28 23:33:30,输入以下命令:
date -s "2023-11-28 23:33:30
设置完成之后再输入命令 data 就可以看到时间改过来了。但是这时候并没有写入 STM32MP1 内部的 RTC 里面。输入以下命令:
hwclock -w // 将当前系统时间写入到 RTC 里面
时间写入到 RTC 里面以后就不怕系统重启以后时间丢失了,如果 STM32MP1 开发板底板
接了纽扣电池,那么开发板即使断电了时间也不会丢失。