26 Linux RTC 驱动

        RTC 其实就是实时时钟,用于记录当前系统时间。当使用 Linux 设备查看时间的时候,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。

二、RTC 时间查看与设置

1. 使能内部 RTC

        直接打开 stm32mp157d-atk.dts 文件,添加下面代码:

&rtc {
 status = "okay";
};

        追加的 RTC 节点内容很简单,就是把 status 属性改为“okay”。接着我们重新编译设备树,
然后使用新编译的 stm32mp157d-atk.dtb 文件启动开发板。

2. 查看时间

        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 开发板底板
接了纽扣电池,那么开发板即使断电了时间也不会丢失。

猜你喜欢

转载自blog.csdn.net/qq_45475497/article/details/134677472