参考文章:
【正点原子】STM32MP1嵌入式Linux驱动开发——RTC驱动
RTC设备驱动是一个标准的字符设备驱动,应用程序通过open、release、read、write和ioctl等函数完成对RTC设备的操作。Linux内核将RTC设备抽象为rtc_device结构体,因此RTC设备驱动就是申请并初始化rtc_device,最后将rtc_device注册到Linux内核里,这样Linux内核就有一个RTC设备。
STM32MP1 的 RTC 驱动已经由 ST 写好,这里仅对其做出简要分析。
先从设备树入手,在 stm32mp151.dtsi文件中,有如下 rtc 设备节点:
rtc: rtc@5c004000 {
compatible = "st,stm32mp1-rtc";
reg = <0x5c004000 0x400>;
clocks = <&scmi0_clk CK_SCMI0_RTCAPB>,
<&scmi0_clk CK_SCMI0_RTC>;
clock-names = "pclk", "rtc_ck";
interrupts-extended = <&exti 19 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
};
兼容属性 compatible 的值为“st,stm32mp1-rtc”,所以可以找到驱动文件为 drivers/rtc/rtc-stm32.c 文件。其中有如下内容:
......
static const struct of_device_id stm32_rtc_of_match[] = {
{ .compatible = "st,stm32-rtc", .data = &stm32_rtc_data },
{ .compatible = "st,stm32h7-rtc", .data = &stm32h7_rtc_data },
{ .compatible = "st,stm32mp1-rtc", .data = &stm32mp1_data },
{}
};
......
static struct platform_driver stm32_rtc_driver = {
.probe = stm32_rtc_probe,
.remove = stm32_rtc_remove,
.driver = {
.name = DRIVER_NAME,
.pm = &stm32_rtc_pm_ops,
.of_match_table = stm32_rtc_of_match,
},
};
module_platform_driver(stm32_rtc_driver);
stm32_rtc_of_match 为设备树 ID 表,compitable值匹配成功后就会使用本驱动。stm32_rtc_driver 为设备操作函数集,可以看出是一个标准的platform驱动框架,当设备和驱动匹配成功以后 stm32_rtc_probe 函数就会执行。
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",ret);
897
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, pdev->name, rtc);
905 if (ret) {
906 dev_err(&pdev->dev, "IRQ%d (alarm interrupt) alreadyclaimed\n", rtc->irq_alarm);
908 goto err;
909 }
......
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_devcie, RTC 底层驱动集为 stm32_rtc_ops。 stm32_rtc_ops 操作集包含了读取/设置 RTC 时间,读取/设置闹钟等函数。
第 902 行,调用 devm_request_threaded_irq 函数请求 RTC 中断,中断服务函数为stm32_rtc_alarm_irq,用于 RTC 闹钟中断。
在 Linux 内核移植时,设备树是经过精简的,没有启动 RTC 功能。在 stm32mp157datk.dts 文件中, 添加如下代码:
&rtc {
status = "okay";
};
就可以使能RTC设备。然后在内核启动时就能看到时钟设置信息:
可以看出,Linux 内核在启动的时候将 rtc 设置为 rtc0。输入date
命令就可以查看时间:
发现时间还没有校正,使用date --help
查看帮助:
使用date -s "2022-12-25 23:24:00"
命令就可以设置当前时间,然后使用hwclock -w
命令写入到RTC设备中,就不怕掉电丢失了。