济南正规企业站seo,做旅游网站的首页的图片,长沙网络公司推广,在哪个网站做外贸生意好RTC也就是实时时钟#xff0c;用于记录当前系统时间#xff0c;对于Linux系统而言时间是非常重要的#xff0c;就和使用Windows电脑或手机查看时间一样#xff0c;在使用Linux设备的时候也需要查看时间。本章就来学习一下如何编写Linux下的RTC驱动程序。
Linux内核RTC驱动…RTC也就是实时时钟用于记录当前系统时间对于Linux系统而言时间是非常重要的就和使用Windows电脑或手机查看时间一样在使用Linux设备的时候也需要查看时间。本章就来学习一下如何编写Linux下的RTC驱动程序。
Linux内核RTC驱动简介
RTC设备驱动是一个标准的字符设备驱动应用程序通过open、release、read、write和ioctl等函数完成对RTC设备的操作本章主要学习如何使用STM32MP1内部自带的 RTC外设。
Linux内核将RTC设备抽象为rtc_device结构体因此RTC设备驱动就是申请并初始化rtc_device最后将rtc_device注册到Linux内核里面这样Linux内核就有一个RTC设备的。至于RTC设备的操作肯定是用一个操作集合(结构体)来表示的先来看一下rtc_device结构体 此结构体定义在include/linux/rtc.h文件中结构体内容如下 (删除条件编译) 需要重点关注的是ops成员变量这是一个rtc_class_ops类型的指针变量rtc_class_ops为RTC设备的最底层操作函数集合包括从RTC设备中读取时间、向RTC设备写入新的时间值等。因此rtc_class_ops是需要用户根据所使用的RTC设备编写的此结构体定义在include/linux/rtc.h文件中内容如下 看名字就知道rtc_class_ops操作集合中的这些函数是做什么的了但是要注意rtc_class_ops中的这些函数只是最底层的RTC设备操作函数并不是提供给应用层的file_operations函数操作集。RTC是个字符设备那么肯定有字符设备的file_operations函数操作集Linux内核提供了一个RTC通用字符设备驱动文件文件名为drivers/rtc/dev.c dev.c文件提供了所有RTC设备共用的file_operations函数操作集如下所示 示例代码43.1.3标准的字符设备操作集。应用程序可以通过ioctl函数来设置/读取时间、设置/读取闹钟的操作对应的rtc_dev_ioctl函数就会执行。rtc_dev_ioctl最终会通过操作rtc_class_ops中的read_time、set_time等函数来对具体RTC设备的读写操作。简单来看一下rtc_dev_ioctl函数函数内容如下(有省略)
示例代码 43.1.4 rtc_dev_ioctl 函数代码段
202 static long rtc_dev_ioctl (struct file *file,
203 unsigned int cmd, unsigned long arg)
204 {
205 int err 0;
206 struct rtc_device *rtc file-private_data;
207 const struct rtc_class_ops *ops rtc-ops;
208 struct rtc_time tm;
209 struct rtc_wkalrm alarm;
210 void __user *uarg (void __user *)arg;
211
212 err mutex_lock_interruptible(rtc ops_lock);
213 if (err)
214 return err;
......
253 switch (cmd) {
......
317 case RTC_RD_TIME: /* 读取时间 */
318 mutex_unlock(rtc-ops_lock);
319
320 err rtc_read_time(rtc, tm);
321 if (err 0)
322 return err
323
324 if (copy_to_user(uarg, tm, sizeof(tm)))
325 err -EFAULT;
326 return err;
327
328 case RTC_SET_TIME: /* 设置时间 */
329 mutex_unlock(rtc-ops_lock);
330
331 if(copy_from_user(tm, uarg, sizeof(tm)))
332 return -EFAULT;
333
334 return rtc_set_time(rtc, tm);
......
385 default:
386 /* Finally try the drivers ioctl interface */
387 if (ops-ioctl) {
388 err ops-ioctl(rtc-dev.parent, cmd, arg);
389 if (err -ENOIOCTLCMD)
390 err -ENOTTY;
391 } else {
392 err -ENOTTY;
393 }
394 break;
395 }
396
397 done:
398 mutex_unlock(rtc-ops_lock);
399 return err;
400 }第317行RTC_RD_TIME为时间读取命令。
第320行如果是读取时间命令的话就调用rtc_read_time函数获取当前RTC时钟rtc_read_time会调用__rtc_read_time函数__rtc_read_time函数内容如下 从第94行可以看出__rtc_read_time函数会通过调用rtc_class_ops中的read_time成员变量来从RTC设备中获取当前时间。rtc_dev_ioctl函数对其他的命令处理都是类似的比如RTC_ALM_READ命令会通过rtc_read_alarm函数获取到闹钟值而rtc_read_alarm函数经过层层调用最终会调用rtc_class_ops中的read_alarm函数来获取闹钟值。
至此Linux内核中RTC驱动调用流程就很清晰了如下图所示 当rtc_class_ops准备好以后需要将其注册到Linux内核中这里可以使用rtc_device_register函数完成注册工作。此函数会申请一个rtc_device并且初始化这个rtc_device最后向调用者返回这个rtc_device此函数原型如下
struct rtc_device *rtc_device_register(const char *name, struct device *dev, const struct rtc_class_ops *ops, struct module *owner)函数参数和返回值含义 如下
name设备名字。dev设备。opsRTC底层驱动函数集。owner驱动模块拥有者。返回值 注册成功的话就返回rtc_device错误的话会返回一个负值。
当卸载RTC驱动的时候需要调用rtc_device_unregister函数来注销注册的 rtc_device函数原型如下
void rtc_device_unregister(struct rtc_device *rtc)函数参数和返回值含义如下
rtc要删除的rtc_device。返回值无。
还有另外一对rtc_device注册函数devm_rtc_device_register和devm_rtc_device_unregister 分别为注册和注销rtc_device。
STM32MP1内部RTC驱动分析
STM32MP1的RTC驱动不用自己编写因为ST已经写好了。其实对于大多数的SOC来讲内部RTC驱动都不需要自己去编写半导体厂商会编写好。分析驱动先从设备树入手打开stm32mp151.dtsi在里面找到如下rtc设备节点节点内容如下所示 第1747行设置兼容属性compatible的值为“st,stm32mp1-rtc”因此在Linux内核源码中搜索此字符串即可找到对应的驱动文件此文件为drivers/rtc/rtc-stm32.c在rtc-stm32.c文件中找到如下所示内容 第719-723行设备树ID表。第722行刚好有一个compatible属性和设备树的rtc的 compatible属性值一样所以rtc设备节点会和此驱动匹配。
第1020-1028行标准的platform驱动框架当设备和驱动匹配成功以后stm32_rtc_probe函数就会执行来看一下stm32_rtc_probe函数函数内容如下(有省略)
示例代码 43.2.3 stm32_rtc_probe 函数代码段
789 static int stm32_rtc_probe(struct platform_device *pdev)
790 {
791 struct stm32_rtc *rtc;
792 const struct stm32_rtc_registers *regs;
793 struct resource *res;
794 int ret;
795
796 rtc devm_kzalloc(pdev-dev, sizeof(*rtc), GFP_KERNEL);
797 if(!rtc)
798 return -ENOMEM;
799
800 res platform_get_resource(pdev, IORESOURCE_MEM, 0);
801 rtc-base devm_ioremap_resource(pdev-dev, res);
......
856 ret clk_prepare_enable(rtc-rtc_ck);
857 if (ret)
858 goto err;
......
872 ret stm32_rtc_init(pdev, rtc);
873 if(ret)
874 goto err;
875
876 rtc-irq_alarm platform_get_irq(pdev, 0);
877 if(rtc-irq_alarm 0) {
878 ret rtc-irq_alarm;
879 goto err;
880 }
......
892 rtc-rtc_dev devm_rtc_device_register(pdev-dev, pdev-name,
893 stm32_rtc_ops, THIS_MODULE);
894 if(IS_ERR(rtc-rtc_dev)) {
895 ret PTR_ERR(rtc-rtc_dev);
896 dev_err(pdev-dev, rtc device registration failed, err%d\n,
897 ret);
898 goto err;
899 }
900
901 /* Handle RTC alarm interrupts */
902 ret devm_request_threaded_irq(pdev-dev, rtc-irq_alarm, NULL,
903 stm32_rtc_alarm_irq, IRQF_ONESHOT,
904 pdev-name, rtc);
905 if(ret) {
906 dev_err(pdev-dev, IRQ%d (alarm interrupt) already
claimed\n,
907 rtc-irq_alarm);
908 goto err;
909 }
......
940
941 return 0;
......
954 }第796行调用devm_kzalloc申请rtc大小的空间返回申请空间的首地址。
第800行调用platform_get_resource函数从设备树中获取到RTC外设寄存器基地址。
第801行调用函数devm_ioremap_resource 完成内存映射得到RTC外设寄存器物理基地址对应的虚拟地址。
第856行调用clk_prepare_enable函数使能时钟。
第872行初始化STM32MP1 rtc的寄存器。
第876行获取设备树的中断号。
第892行调用devm_rtc_device_register函数向系统注册rtc_devcieRTC底层驱动集为stm32_rtc_ops。stm32_rtc_ops操作集包含了读取/设置RTC时间读取/设置闹钟等函数。
第902行调用devm_request_threaded_irq函数请求RTC中断中断服务函数为stm32_rtc_alarm_irq用于RTC闹钟中断。
stm32_rtc_ops内容如下所示 就以第624行的stm32_rtc_read_time函数为例讲解一下rtc_class_ops的各个RTC底层操作函数该如何去编写。stm32_rtc_read_time函数用于读取RTC时间值此函数内容如下所示 第371-372行调用readl_relaxed读取STM32MP1的RTC_TR和RTC_DR这两个寄存器的值其中TR寄存器为RTC时间寄存器保存着时、分、秒信息DR为RTC的日期寄存器保存着年、月、日信息。通过这两个寄存器就可以得到RTC时间。
第374-381行前两行获取到了TR和DR这两个寄存器的值这里需要从这两个寄存器值中提取出具体的年、月、日和时、分、秒信息。
第385行上面得到的时间信息为BCD格式的这里通过bcd2tm函数将BCD格式转换 为rtc_time格式rtc_time结构体定义如下 RTC时间查看与设置
使能内部RTC
在Linux内核移植的时候设备树是经过精简的没有启动RTC功能。打开stm32mp157d-atk.dts文件添加如下代码
示例代码43.3.1.1 rtc节点信息
1 rtc {
2 status okay;
3 };追加的RTC节点内容很简单就是把status属性改为“okay”。接着重新编译设备树然后使用新编译的stm32mp157d-atk.dtb文件启动开发板。
查看时间
RTC是用来记时的因此最基本的就是查看时间Linux内核启动的时候可以看到系统时钟设置信息如下图所示 从上图中可以看出Linux内核在启动的时候将rtc设置为rtc0大家的启动信息可能会和上图中不同但是基本上都是一样的。
如果要查看时间的话输入“date”命令即可结果如下图所示 从上图可以看出当前时间为2000年1月1日03:30:29很明显时间不对需要重新设置RTC时间。
RTC时间设置也是使用的date命令输入“date --help”命令即可查看date命令如何设置系统时间结果如下图所示 比如现在设置当前时间为2021年5月2日18:53:00因此输入如下命令 date -s 2021-05-02 18:53:00
设置完成以后再次使用date命令查看一下当前时间就会发现时间改过来了。
注意使用“date -s”命令仅仅是修改了当前时间此时间还没有写入到STM32MP1内部RTC里面或其他的RTC芯片里面因此系统重启以后时间又会丢失。需要将当前的时间写入到RTC里面这里要用到hwclock命令输入如下命令将系统时间写入到RTC里面 hwclock -w //将当前系统时间写入到 RTC里面
时间写入到RTC里面以后就不怕系统重启以后时间丢失了如果STM32MP1开发板底板接了纽扣电池那么开发板即使断电了时间也不会丢失。可以尝试一下不断电重启和断电重启这两种情况下开发板时间会不会丢失。
总结
这一章节比较简单因为Linux内核已经实现了RTC的驱动对我们来说我只要会用“date”命令和“hwclock”命令去修改使用RTC就可以了。