你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32MP1嵌入式Linux驱动开发指南V1.7——Linux RTC驱动实验

[复制链接]
STMCU小助手 发布时间:2022-9-22 21:36
Linux RTC驱动实验
RTC也就是实时时钟,用于记录当前系统时间,对于Linux系统而言时间是非常重要的,就和我们使用Windows电脑或手机查看时间一样,我们在使用Linux设备的时候也需要查看时间。本章我们就来学习一下如何编写Linux下的RTC驱动程序。

42.1 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文件中,结构体内容如下(删除条件编译):

  1. 示例代码42.1.1 rtc_device结构体
  2. 100 struct rtc_device {
  3. 101     struct device dev;                          /*  设备                  */
  4. 102     struct module *owner;
  5. 103
  6. 104     int id;                                             /* ID                     */
  7. 105
  8. 106     const struct rtc_class_ops *ops;          /* RTC设备底层操作函数 */
  9. 107     struct mutex ops_lock;
  10. 108
  11. 109     struct cdev char_dev;                       /* 字符设备                */
  12. 110     unsigned long flags;
  13. 111
  14. 112     unsigned long irq_data;
  15. 113     spinlock_t irq_lock;
  16. 114     wait_queue_head_t irq_queue;
  17. 115     struct fasync_struct *async_queue;
  18. 116
  19. 117     int irq_freq;
  20. 118     int max_user_freq;
  21. 119
  22. 120     struct timerqueue_head timerqueue;
  23. 121     struct rtc_timer aie_timer;
  24. 122     struct rtc_timer uie_rtctimer;
  25. 123     struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
  26. 124     int pie_enabled;
  27. 125     struct work_struct irqwork;
  28. 126     /* Some hardware can't support UIE mode */
  29. 127     int uie_unsupported;
  30. ......
  31. 159 };
复制代码

我们需要重点关注的是ops成员变量,这是一个rtc_class_ops类型的指针变量,rtc_class_ops为RTC设备的最底层操作函数集合,包括从RTC设备中读取时间、向RTC设备写入新的时间值等。因此,rtc_class_ops是需要用户根据所使用的RTC设备编写的,此结构体定义在include/linux/rtc.h文件中,内容如下:

  1. 示例代码42.1.2 rtc_class_ops 结构体
  2. 75  struct rtc_class_ops {
  3. 76      int (*ioctl)(struct device *, unsigned int, unsigned long);
  4. 77      int (*read_time)(struct device *, struct rtc_time *);
  5. 78      int (*set_time)(struct device *, struct rtc_time *);
  6. 79      int (*read_alarm)(struct device *, struct rtc_wkalrm *);
  7. 80      int (*set_alarm)(struct device *, struct rtc_wkalrm *);
  8. 81      int (*proc)(struct device *, struct seq_file *);
  9. 82      int (*alarm_irq_enable)(struct device *, unsigned int enabled);
  10. 83      int (*read_offset)(struct device *, long *offset);
  11. 84      int (*set_offset)(struct device *, long offset);
  12. 85  };
复制代码

看名字就知道rtc_class_ops操作集合中的这些函数是做什么的了,但是我们要注意,rtc_class_ops中的这些函数只是最底层的RTC设备操作函数,并不是提供给应用层的file_operations函数操作集。RTC是个字符设备,那么肯定有字符设备的file_operations函数操作集,Linux内核提供了一个RTC通用字符设备驱动文件,文件名为drivers/rtc/dev.c,dev.c文件提供了所有RTC设备共用的file_operations函数操作集,如下所示:

  1. 示例代码42.1.3 RTC通用 file_operations操作集
  2. 431 static const struct file_operations rtc_dev_fops = {
  3. 432     .owner           = THIS_MODULE,
  4. 433     .llseek          = no_llseek,
  5. 434     .read             = rtc_dev_read,
  6. 435     .poll             = rtc_dev_poll,
  7. 436     .unlocked_ioctl = rtc_dev_ioctl,
  8. 437     .open             = rtc_dev_open,
  9. 438     .release         = rtc_dev_release,
  10. 439     .fasync           = rtc_dev_fasync,
  11. 440 };
复制代码

看到示例代码42.1.3是不是很熟悉了,标准的字符设备操作集。应用程序可以通过ioctl函数来设置/读取时间、设置/读取闹钟的操作,对应的rtc_dev_ioctl函数就会执行。 rtc_dev_ioctl最终会通过操作rtc_class_ops中的read_time、set_time等函数来对具体RTC设备的读写操作。我们简单来看一下rtc_dev_ioctl函数,函数内容如下(有省略):

  1. 示例代码42.1.4 rtc_dev_ioctl函数代码段
  2. 202 static long rtc_dev_ioctl(struct file *file,
  3. 203               unsigned int cmd, unsigned long arg)
  4. 204 {
  5. 205     int err = 0;
  6. 206     struct rtc_device *rtc = file->private_data;
  7. 207     const struct rtc_class_ops *ops = rtc->ops;
  8. 208     struct rtc_time tm;
  9. 209     struct rtc_wkalrm alarm;
  10. 210     void __user *uarg = (void __user *)arg;
  11. 211
  12. 212     err = mutex_lock_interruptible(&rtc->ops_lock);
  13. 213     if (err)
  14. 214         return err;
  15. ......
  16. 253     switch (cmd) {
  17. ......
  18. 317     case RTC_RD_TIME:       /* 读取时间 */
  19. 318         mutex_unlock(&rtc->ops_lock);
  20. 319
  21. 320         err = rtc_read_time(rtc, &tm);
  22. 321         if (err < 0)
  23. 322             return err;
  24. 323
  25. 324         if (copy_to_user(uarg, &tm, sizeof(tm)))
  26. 325             err = -EFAULT;
  27. 326         return err;
  28. 327
  29. 328     case RTC_SET_TIME:      /* 设置时间 */
  30. 329         mutex_unlock(&rtc->ops_lock);
  31. 330
  32. 331         if (copy_from_user(&tm, uarg, sizeof(tm)))
  33. 332             return -EFAULT;
  34. 333
  35. 334         return rtc_set_time(rtc, &tm);
  36. .....
  37. 385     default:
  38. 386         /* Finally try the driver's ioctl interface */
  39. 387         if (ops->ioctl) {
  40. 388             err = ops->ioctl(rtc->dev.parent, cmd, arg);
  41. 389             if (err == -ENOIOCTLCMD)
  42. 390                 err = -ENOTTY;
  43. 391         } else {
  44. 392             err = -ENOTTY;
  45. 393         }
  46. 394         break;
  47. 395     }
  48. 396
  49. 397 done:
  50. 398     mutex_unlock(&rtc->ops_lock);
  51. 399     return err;
  52. 400 }
复制代码

第317行,RTC_RD_TIME为时间读取命令。
第320行,如果是读取时间命令的话就调用rtc_read_time函数获取当前RTC时钟, rtc_read_time会调用__rtc_read_time函数,__rtc_read_time函数内容如下:

  1. 示例代码42.1.5 __rtc_read_time函数代码段
  2. 84 static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
  3. 85 {
  4. 86     int err;
  5. 87
  6. 88     if (!rtc->ops) {
  7. 89         err = -ENODEV;
  8. 90     } else if (!rtc->ops->read_time) {
  9. 91         err = -EINVAL;
  10. 92     } else {
  11. 93         memset(tm, 0, sizeof(struct rtc_time));
  12. 94         err = rtc->ops->read_time(rtc->dev.parent, tm);
  13. 95         if (err < 0) {
  14. 96             dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",
  15. 97                 err);
  16. 98             return err;
  17. 99         }
  18. 100
  19. 101         rtc_add_offset(rtc, tm);
  20. 102
  21. 103         err = rtc_valid_tm(tm);
  22. 104         if (err < 0)
  23. 105             dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");
  24. 106     }
  25. 107     return err;
  26. 108 }
复制代码

从第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驱动调用流程就很清晰了,如图42.1.1所示:

c5e09919998f483ebea49d6f8ed6a23e.png

图42.1.1 Linux RTC驱动调用流程
当rtc_class_ops准备好以后需要将其注册到Linux内核中,这里我们可以使用rtc_device_register函数完成注册工作。此函数会申请一个rtc_device并且初始化这个rtc_device,最后向调用者返回这个rtc_device,此函数原型如下:

  1. struct rtc_device *rtc_device_register(const char                                 *name,
  2.                                                           struct device                         *dev,
  3.                                                           const struct rtc_class_ops         *ops,
  4.                                                           struct module                         *owner)
复制代码

函数参数和返回值含义如下:
name:设备名字。
dev:设备。
ops:RTC底层驱动函数集。
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。
42.2 STM32MP1内部RTC驱动分析
先直接告诉大家,STM32MP1的RTC驱动我们不用自己编写,因为ST已经写好了。其实对于大多数的SOC来讲,内部RTC驱动都不需要我们去编写,半导体厂商会编写好。但是这不代表我们就偷懒了,虽然不用编写RTC驱动,但是我们得看一下这些原厂是怎么编写RTC驱动的。
分析驱动,先从设备树入手,打开stm32mp151.dtsi,在里面找到如下rtc设备节点,节点内容如下所示:

  1. 示例代码42.2.1 stm32mp151.dtsi文件rtc节点
  2. 1746        rtc: rtc@5c004000 {
  3. 1747                   compatible = "st,stm32mp1-rtc";
  4. 1748              reg = <0x5c004000 0x400>;
  5. 1749               clocks = <&scmi0_clk CK_SCMI0_RTCAPB>,
  6. 1750                  <&scmi0_clk CK_SCMI0_RTC>;
  7. 1751              clock-names = "pclk", "rtc_ck";
  8. 1752               interrupts-extended = <&exti 19 IRQ_TYPE_LEVEL_HIGH>;
  9. 1753               status = "disabled";
  10. 1754          };
复制代码

第1747行设置兼容属性compatible的值为“st,stm32mp1-rtc”,因此在Linux内核源码中搜索此字符串即可找到对应的驱动文件,此文件为drivers/rtc/rtc-stm32.c,在rtc-stm32.c文件中找到如下所示内容:

  1. 示例代码42.2.2 设备 platform驱动框架
  2. 719     static const struct of_device_id stm32_rtc_of_match[] = {
  3. 720         { .compatible = "st,stm32-rtc", .data = &stm32_rtc_data },
  4. 721         { .compatible = "st,stm32h7-rtc", .data = &stm32h7_rtc_data },
  5. 722         { .compatible = "st,stm32mp1-rtc", .data = &stm32mp1_data },
  6. 723         {}
  7. 724     };
  8. 725     MODULE_DEVICE_TABLE(of, stm32_rtc_of_match);
  9. 726
  10. ......
  11. 1020    static struct platform_driver stm32_rtc_driver = {
  12. 1021        .probe      = stm32_rtc_probe,
  13. 1022        .remove     = stm32_rtc_remove,
  14. 1023        .driver     = {
  15. 1024            .name   = DRIVER_NAME,
  16. 1025            .pm = &stm32_rtc_pm_ops,
  17. 1026            .of_match_table = stm32_rtc_of_match,
  18. 1027        },
  19. 1028    };
  20. 1029
  21. 1030    module_platform_driver(stm32_rtc_driver);
复制代码

第719~723行,设备树ID表。第722行,刚好有一个compatible属性和设备树的rtc的compatible属性值一样,所以rtc设备节点会和此驱动匹配。
第1020~1028行,标准的platform驱动框架,当设备和驱动匹配成功以后stm32_rtc_probe函数就会执行,我们来看一下stm32_rtc_probe函数,函数内容如下(有省略):

  1. 示例代码42.2.3 stm32_rtc_probe函数代码段
  2. 789 static int stm32_rtc_probe(struct platform_device *pdev)
  3. 790 {
  4. 791     struct stm32_rtc *rtc;
  5. 792     const struct stm32_rtc_registers *regs;
  6. 793     struct resource *res;
  7. 794     int ret;
  8. 795
  9. 796     rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
  10. 797     if (!rtc)
  11. 798         return -ENOMEM;
  12. 799
  13. 800     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  14. 801     rtc->base = devm_ioremap_resource(&pdev->dev, res);
  15. ......
  16. 856     ret = clk_prepare_enable(rtc->rtc_ck);
  17. 857     if (ret)
  18. 858         goto err;
  19. ......
  20. 872     ret = stm32_rtc_init(pdev, rtc);
  21. 873     if (ret)
  22. 874         goto err;
  23. 875
  24. 876     rtc->irq_alarm = platform_get_irq(pdev, 0);
  25. 877     if (rtc->irq_alarm <= 0) {
  26. 878         ret = rtc->irq_alarm;
  27. 879         goto err;
  28. 880     }
  29. ......
  30. 892     rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
  31. 893                         &stm32_rtc_ops, THIS_MODULE);
  32. 894     if (IS_ERR(rtc->rtc_dev)) {
  33. 895         ret = PTR_ERR(rtc->rtc_dev);
  34. 896         dev_err(&pdev->dev, "rtc device registration failed, err=%d\n",
  35. 897             ret);
  36. 898         goto err;
  37. 899     }
  38. 900
  39. 901     /* Handle RTC alarm interrupts */
  40. 902     ret = devm_request_threaded_irq(&pdev->dev, rtc->irq_alarm, NULL,
  41. 903                     stm32_rtc_alarm_irq, IRQF_ONESHOT,
  42. 904                     pdev->name, rtc);
  43. 905     if (ret) {
  44. 906         dev_err(&pdev->dev, "IRQ%d (alarm interrupt) already claimed\n",
  45. 907             rtc->irq_alarm);
  46. 908         goto err;
  47. 909     }
  48. ......
  49. 940
  50. 941     return 0;
  51. ......
  52. 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_devcie,RTC 底层驱动集为stm32_rtc_ops。stm32_rtc_ops操作集包含了读取/设置RTC时间,读取/设置闹钟等函数。
第902行,调用devm_request_threaded_irq函数请求RTC中断,中断服务函数为stm32_rtc_alarm_irq,用于RTC闹钟中断。
stm32_rtc_ops内容如下所示:

  1. 示例代码42.2.4 rtc_class_ops操作集
  2. 623 static const struct rtc_class_ops stm32_rtc_ops = {
  3. 624     .read_time         = stm32_rtc_read_time,
  4. 625     .set_time           = stm32_rtc_set_time,
  5. 626     .read_alarm         = stm32_rtc_read_alarm,
  6. 627     .set_alarm          = stm32_rtc_set_alarm,
  7. 628     .alarm_irq_enable = stm32_rtc_alarm_irq_enable,
  8. 629 };
复制代码

我们就以第624行的stm32_rtc_read_time函数为例讲解一下rtc_class_ops的各个RTC底层操作函数该如何去编写。stm32_rtc_read_time函数用于读取RTC时间值,此函数内容如下所示:

  1. 示例代码42.2.5 stm32_rtc_read_time代码段
  2. 364 static int stm32_rtc_read_time(struct device *dev,
  3. struct rtc_time *tm)
  4. 365 {
  5. 366     struct stm32_rtc *rtc = dev_get_drvdata(dev);
  6. 367     const struct stm32_rtc_registers *regs = &rtc->data->regs;
  7. 368     unsigned int tr, dr;
  8. 369
  9. 370     /* Time and Date in BCD format */
  10. 371     tr = readl_relaxed(rtc->base + regs->tr);
  11. 372     dr = readl_relaxed(rtc->base + regs->dr);
  12. 373
  13. 374     tm->tm_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT;
  14. 375     tm->tm_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT;
  15. 376     tm->tm_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT;
  16. 377
  17. 378     tm->tm_mday = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT;
  18. 379     tm->tm_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT;
  19. 380     tm->tm_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT;
  20. 381     tm->tm_wday = (dr & STM32_RTC_DR_WDAY) >> STM32_RTC_DR_WDAY_SHIFT;
  21. 382
  22. 383     /* We don't report tm_yday and tm_isdst */
  23. 384
  24. 385     bcd2tm(tm);
  25. 386
  26. 387     return 0;
  27. 388 }
复制代码

第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结构体定义如下:

  1. 示例代码42.2.6 rtc_time结构体类型
  2. 20 sruct rtc_time {
  3. 21          int tm_sec;
  4. 22          int tm_min;
  5. 23          int tm_hour;
  6. 24          int tm_mday;
  7. 25          int tm_mon;
  8. 26          int tm_year;
  9. 27          int tm_wday;
  10. 28          int tm_yday;
  11. 29          int tm_isdst;
  12. 30 };
复制代码

42.3 RTC时间查看与设置
42.3.1 使能内部RTC
在Linux内核移植的时候,设备树是经过精简的,就没有启动RTC功能。打开stm32mp157d-atk.dts文件,添加如下代码所示:
示例代码42.3.1.1 rtc节点信息
  1. 1 &rtc {
  2. 2 status = “okay”;
  3. 3 };
复制代码

追加的RTC节点内容很简单,就是把status属性改为“okay”。接着我们重新编译设备树,然后使用新编译的stm32mp157d-atk.dtb文件启动开发板。
42.3.2 查看时间
RTC是用来记时的,因此最基本的就是查看时间,Linux内核启动的时候可以看到系统时钟设置信息,如图42.3.2.1所示:

e2fde9421fb74437ba7861b2a5717dc3.png

图42.3.2.1 Linux启动log信息
从图42.3.2.1中可以看出,Linux内核在启动的时候将rtc设置为rtc0,大家的启动信息可能会和图42.3.2.1中不同,但是基本上都是一样的。
如果要查看时间的话输入“date”命令即可,结果如图42.3.2.2所示:

50a69520f98c454fb2a845a9e367d925.png

图42.3.2.2 当前时间值
从图42.3.2.2可以看出,当前时间为2000年1月1日 03:30:29,很明显时间不对,我们需要重新设置RTC时间。
RTC时间设置也是使用的date命令,输入“date --help”命令即可查看date命令如何设置系统时间,结果如图42.3.2.3所示:

6ce2ad9865f0403bbeeaa8835f4d04f4.png

图42.3.2.3 date命令帮助信息
比如现在设置当前时间为2021年5月2日 18:53:00,因此输入如下命令:
date -s “2021-05-02 18:53:00”
设置完成以后再次使用date命令查看一下当前时间就会发现时间改过来了,如图42.3.2.4所示:

53345e9464e549e997f18452b0212237.png

图42.3.2.4 当前时间
大家注意我们使用“date -s”命令仅仅是修改了当前时间,此时间还没有写入到STM32MP1内部RTC里面或其他的RTC芯片里面,因此系统重启以后时间又会丢失。我们需要将当前的时间写入到RTC里面,这里要用到hwclock命令,输入如下命令将系统时间写入到RTC里面:
hwclock -w //将当前系统时间写入到RTC里面
时间写入到RTC里面以后就不怕系统重启以后时间丢失了,如果STM32MP1开发板底板接了纽扣电池,那么开发板即使断电了时间也不会丢失。大家可以尝试一下不断电重启和断电重启这两种情况下开发板时间会不会丢失。
————————————————
版权声明:正点原子

收藏 评论0 发布时间:2022-9-22 21:36

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版