当前位置: 首页 > news >正文

imx6ull-驱动开发篇41——Linux RTC 驱动实验

目录

I.MX6U 内部 RTC 驱动

snvs_rtc 设备节点

snvs_rtc_probe 函数

snvs_rtc_ops操作集

snvs_rtc_read_time 函数

RTC 时间查看与设置

时间 RTC 查看date

设置 RTC 时间

hwclock 命令


在上一讲内容里:Linux RTC 驱动简介,我们简单了解了一些linux下RTC驱动相关的结构体变量和函数。

本讲内容里,我们学习正点原子I.MX6U 开发板的内部 RTC 驱动,掌握RTC时间查看与设置的方法。

I.MX6U 内部 RTC 驱动

从设备树开始,打开我们自己移植的linux源码路径下的 /arch/arm/boot/dts/imx6ull.dtsi,在里面找到如下 snvs_rtc 设备节点。

snvs_rtc 设备节点

snvs_rtc 设备节点内容如下所示

其中,设置兼容属性 compatible 的值为“fsl,sec-v4.0-mon-rtc-lp”,在 Linux 内核源码中搜索此字符串即可找到对应的驱动文件,此文件为 drivers/rtc/rtc-snvs.c

rtc-snvs.c 文件中找到如下所示内容:

static const struct of_device_id snvs_dt_ids[] = {{ .compatible = "fsl,sec-v4.0-mon-rtc-lp", },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, snvs_dt_ids);static struct platform_driver snvs_rtc_driver = {.driver = {.name = "snvs_rtc",.pm = SNVS_RTC_PM_OPS,.of_match_table = snvs_dt_ids,},.probe = snvs_rtc_probe,
};
module_platform_driver(snvs_rtc_driver);

其中,设备树 ID 表的 compatible 属性,值为“fsl,sec-v4.0-mon-rtc-lp”,因此 imx6ull.dtsi 中的 snvs_rtc 设备节点会和此驱动匹配。

当设备和驱动匹配成功以后, snvs_rtc_probe 函数就会执行。

snvs_rtc_probe 函数

snvs_rtc_probe 函数,函数内容如下(有省略):

static int snvs_rtc_probe(struct platform_device *pdev)
{struct snvs_rtc_data *data;struct resource *res;int ret;void __iomem *mmio;/* 1. 分配设备私有数据结构 */data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);if (!data)return -ENOMEM;/* 2. 获取寄存器映射 - 新/旧设备树兼容处理 */data->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");if (IS_ERR(data->regmap)) {/* 旧设备树兼容路径 */dev_warn(&pdev->dev, "snvs rtc: you use old dts file,please update it\n");/* 2.1 获取传统内存资源 */res = platform_get_resource(pdev, IORESOURCE_MEM, 0);mmio = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(mmio))return PTR_ERR(mmio);/* 2.2 手动创建寄存器映射 */data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, &snvs_rtc_config);} else {/* 新设备树路径 */data->offset = SNVS_LPREGISTER_OFFSET;of_property_read_u32(pdev->dev.of_node, "offset", &data->offset);}/* 3. 寄存器映射最终检查 */if (!data->regmap) {dev_err(&pdev->dev, "Can't find snvs syscon\n");return -ENODEV;}/* 4. 获取中断资源 */data->irq = platform_get_irq(pdev, 0);if (data->irq < 0)return data->irq;/* 5. 保存设备私有数据 */platform_set_drvdata(pdev, data);/* 6. 硬件初始化序列 *//* 6.1 初始化毛刺检测寄存器 */regmap_write(data->regmap, data->offset + SNVS_LPPGDR, SNVS_LPPGDR_INIT);/* 6.2 清除中断状态寄存器 */regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff);/* 6.3 使能RTC功能 */snvs_rtc_enable(data, true);/* 7. 配置设备唤醒功能 */device_init_wakeup(&pdev->dev, true);/* 8. 注册中断处理程序 */ret = devm_request_irq(&pdev->dev, data->irq, snvs_rtc_irq_handler,IRQF_SHARED, "rtc alarm", &pdev->dev);if (ret) {dev_err(&pdev->dev, "failed to request irq %d: %d\n", data->irq, ret);goto error_rtc_device_register;}/* 9. 注册RTC设备 */data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, &snvs_rtc_ops, THIS_MODULE);if (IS_ERR(data->rtc)) {ret = PTR_ERR(data->rtc);dev_err(&pdev->dev, "failed to register rtc: %d\n", ret);goto error_rtc_device_register;}return 0;error_rtc_device_register:/* 错误恢复路径 */if (data->clk)clk_disable_unprepare(data->clk);return ret;
}

关键代码分析如下:

调用 platform_get_resource 函数,从设备树中获取到 RTC 外设寄存器基地址。

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

调用函数 devm_ioremap_resource 完成内存映射,得到 RTC 外设寄存器物理基地址对应的虚拟地址。

mmio = devm_ioremap_resource(&pdev->dev, res);

Linux3.1 引入了一个全新的 regmap 机制, regmap 用于提供一套方便的 API 函数去操作底层硬件寄存器,以提高代码的可重用性。 snvs-rtc.c 文件会采用 regmap 机制来读写RTC 底层硬件寄存器。

使用 devm_regmap_init_mmio 函数,将 RTC 的硬件寄存器转化为regmap 形式,这样 regmap 机制的 regmap_write、 regmap_read 等 API 函数才能操作寄存器。

/* 2.2 手动创建寄存器映射 */data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, &snvs_rtc_config);

调用platform_get_irq函数,从设备树中获取 RTC 的中断号。

data->irq = platform_get_irq(pdev, 0);

调用regmap 机制的 regmap_write 函数,设置 RTC_ LPPGDR 寄存器值为 SNVS_LPPGDR_INIT= 0x41736166。

regmap_write(data->regmap, data->offset + SNVS_LPPGDR, SNVS_LPPGDR_INIT);

调用regmap_write函数,设置 RTC_LPSR 寄存器,写入 0xffffffff, LPSR 是 RTC 状态寄存器,写 1 清零,因此这一步就是清除 LPSR 寄存器。

regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff);

调用 snvs_rtc_enable 函数,使能 RTC,此函数会设置 RTC_LPCR 寄存器。

snvs_rtc_enable(data, true);

调用devm_request_irq函数,请求RTC中断,中断服务函数为snvs_rtc_irq_handler,用于 RTC 闹钟中断。

/* 8. 注册中断处理程序 */ret = devm_request_irq(&pdev->dev, data->irq, snvs_rtc_irq_handler,IRQF_SHARED, "rtc alarm", &pdev->dev);

调用 devm_rtc_device_register 函数,向系统注册 rtc_devcie。

/* 9. 注册RTC设备 */data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, &snvs_rtc_ops, THIS_MODULE);

snvs_rtc_ops操作集

RTC 底层驱动集为snvs_rtc_ops,snvs_rtc_ops操作集包含了读取/设置RTC时间,读取/设置闹钟等函数。

snvs_rtc_ops操作集内容如下:

static const struct rtc_class_ops snvs_rtc_ops = {/* 基础时间操作 */.read_time = snvs_rtc_read_time,      // 读取当前RTC时间(必须实现).set_time = snvs_rtc_set_time,        // 设置RTC时间(必须实现)/* 闹钟功能 */.read_alarm = snvs_rtc_read_alarm,    // 读取闹钟设置.set_alarm = snvs_rtc_set_alarm,      // 设置闹钟时间.alarm_irq_enable = snvs_rtc_alarm_irq_enable, // 控制闹钟中断使能};

snvs_rtc_read_time 函数为例,讲解一下 rtc_class_ops 的各个 RTC 底层操作函数,该如何去编写。

snvs_rtc_read_time 函数

snvs_rtc_read_time 函数用于读取 RTC 时间值,函数内容如下所示:

static int snvs_rtc_read_time(struct device *dev, struct rtc_time *tm)
{/* 1. 获取设备私有数据 */struct snvs_rtc_data *data = dev_get_drvdata(dev);/* 2. 读取硬件计数器值 */unsigned long time = rtc_read_lp_counter(data);/* 3. 将秒数转换为RTC时间结构 */rtc_time_to_tm(time, tm);/* 4. 返回成功状态 */return 0;
}
  • 调用 rtc_read_lp_counter 函数,获取 RTC 计数值,这个时间值是秒数。
  • 调用 rtc_time_to_tm 函数,将获取到的秒数转换为时间值,也就是 rtc_time 结构体类型。
  • 调用rtc_read_lp_counter 函数,用于读取 RTC 计数值。

rtc_time 结构体定义如下:

struct rtc_time {int tm_sec;   // 秒 [0-59] (可能包含闰秒至60)int tm_min;   // 分 [0-59]int tm_hour;  // 时 [0-23]int tm_mday;  // 月中的日 [1-31]int tm_mon;   // 月 [0-11] (注意:比实际月份小1)int tm_year;  // 年 - 1900的偏移量(如2023年存储为123)int tm_wday;  // 周几 [0-6] (0=周日)int tm_yday;  // 年中的日 [0-365]int tm_isdst; // 夏令时标志(通常RTC不维护此字段)
};

    rtc_read_lp_counter 函数内容如下(有省略):

    static u32 rtc_read_lp_counter(struct snvs_rtc_data *data)
    {u64 read1, read2;  // 用于存储两次读取的64位组合值u32 val;           // 临时存储32位寄存器值/* 硬件同步读取循环 */do {/* 第一次完整读取 */// 读取高32位计数器regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);read1 = val;read1 <<= 32;// 读取低32位计数器regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);read1 |= val;/* 第二次完整读取(用于验证) */regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);read2 = val;read2 <<= 32;regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);read2 |= val;/** 由于低速总线可能导致读取撕裂(tearing),这里采用宽松验证策略:* 只比较两个读数的有效秒数部分(忽略低位的亚秒计数)*/} while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));/* 将47位计数器转换为32位秒数 */return (u32)(read1 >> CNTR_TO_SECS_SH);
    }

    读取 RTC_LPSRTCMRRTC_LPSRTCLR 这两个寄存器,得到 RTC 的计数值,单位为秒,这个秒数就是当前时间。

    这里读取了两次 RTC 计数值,因为要读取两个寄存器,因此可能存在读取第二个寄存器的时候时间数据更新了,导致时间不匹配,因此这里连续读两次,如果两次的时间值相等那么就表示时间数据有效。

    RTC 时间查看与设置

    时间 RTC 查看date

    Linux 内核启动的时候,可以看到系统时钟设置信息,如图

    Linux 内核在启动的时候将 snvs_rtc 设置为 rtc0

    如果要查看时间的话输入“date”命令即可,结果如图:

    可以看出,当前时间和现实不一致,我们需要重新设置 RTC 时间。

    设置 RTC 时间

    RTC 时间设置也是使用的 date 命令,输入“date --help”命令即可查看 date 命令如何设置系统时间,结果如图:

    按照图中说明,示例用法如下:

    设置系统日期:

    # 使用标准格式设置日期(时间默认为00:00:00)
    date -s "2025-08-25"# 设置2025年8月25日15:30:45
    date -s "2025.08.25-15:30:45"# 使用点分隔格式(TIME formats格式)
    date -s "2025.08.25"# 使用紧凑格式(MMDDhhmmYYYY格式)
    date -s "082515302025.45"

    显示日期

    # 显示指定日期的默认格式
    date -d "2025.08.25"# 输出:Mon Aug 25 00:00:00 CST 2025# 显示ISO-8601格式
    date -I -d "2025-08-25"
    # 输出:2025-08-25# 显示RFC-2822格式
    date -R -d "2025.08.25"
    # 输出:Mon, 25 Aug 2025 00:00:00 +0800

    用“ date -s”命令仅仅是将当前系统时间设置了,此时间还没有写入到I.MX6U 内部 RTC 里面或其他的 RTC 芯片里面,因此系统重启以后时间又会丢失。

    hwclock 命令

    我们需要将当前的时间写入到 RTC 里面,这里要用到 hwclock 命令。

    输入如下命令将系统时间写入到 RTC里面:

    hwclock -w //将当前系统时间写入到 RTC 里面

    时间写入到 RTC 里面以后,就不怕系统重启以后时间丢失了,如果 I.MX6U-ALPHA 开发板底板接了纽扣电池,那么开发板即使断电了时间也不会丢失。

    http://www.dtcms.com/a/350243.html

    相关文章:

  • 详解flink SQL基础(四)
  • 使用Docker+WordPress部署个人博客
  • 无人机和无人系统的计算机视觉-人工智能无人机
  • k8s的etcd备份脚本
  • 4G模块 EC200通过MQTT协议连接到阿里云
  • Java-面试八股文-Java高级篇
  • Springboot 集成 TraceID
  • 在react里使用路由,手动跳转
  • C++ 内存安全与智能指针深度解析
  • 【flutter对屏幕底部有手势区域(如:一条横杠)导致出现重叠遮挡】
  • YOLOv7:重新定义实时目标检测的技术突破
  • 浅聊RLVR
  • 绿色循环经济下的旧物回收App:重构闲置资源的价值链条
  • 设计仿真 | 从物理扫描到虚拟检具:Simufact Welding革新汽车零部件检测
  • 汽车零部件工厂ESOP系统工业一体机如何选型
  • 基于51单片机红外避障车辆高速汽车测速仪表设计
  • AEB 强制来临,东软睿驰Next-Cube-Lite有望成为汽车安全普惠“破局器”
  • kubeadm join 命令无法加入node节点,ip_forward 内核参数没有被正确设置
  • IIS 安装了.netcore运行时 还是报错 HTTP 错误 500.19
  • k8s笔记03-常用操作命令
  • Qt开发:智能指针的介绍和使用
  • 君正T31学习(二)- USB烧录
  • 支持指令流水的计算机系统设计与实现
  • mysql绿色版本教程
  • 【python断言插件responses_validator使用】
  • 校园科研自动气象站:藏在校园里的 “科研小站”
  • Nginx零拷贝技术深度解析
  • 【 Python程序员的Ubuntu入门指南】
  • Python二进制、八进制与十六进制高级操作指南:从底层处理到工程实践
  • freqtrade进行回测