0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深入浅出了解Linux RTC实时时钟

jf_78858299 来源:嵌入式攻城狮 作者:安迪西 2023-05-26 15:06 次阅读

实时时钟是个常用的外设,可以用来获取年、月、日和时间等信息。目前大多数的芯片内部都自带了实时时钟外设模块。例如本实验所使用的I.MX6ULL芯片内部SNVS就提供了RTC(实时计数器)功能。SNVS(安全的非易性存储)里面主要是一些低功耗的外设,其可以在芯片掉电后由电池供电继续运行。RTC需要外接晶振来提供时钟,本实验中I.MX6ULL芯片外接了一个32.768KHz的晶振,原理图如下

图片

1. Linux内核RTC驱动简介

RTC 设备驱动是一个标准的字符设备驱动,应用程序通过open、release、read、write和ioctl等函数完成对 RTC 设备的操作

内核将 RTC 设备抽象为 rtc_device 结构体,RTC设备驱动就是申请并初始化rtc_device,最后将 rtc_device 注册到Linux内核里面,此结构体定义在include/linux/rtc.h文件中

struct rtc_device
{
 struct device dev; /* 设备 */
 struct module *owner;

 int id; /* ID */
 char name[RTC_DEVICE_NAME_SIZE]; /* 名字 */

 const struct rtc_class_ops *ops; /* RTC 设备底层操作函数 */
 struct mutex ops_lock;

 struct cdev char_dev; /* 字符设备 */
 unsigned long flags;
 ......
 ......
};

结构体中的ops成员变量是RTC设备的底层操作函数集合,是一个 rtc_class_ops 类型的指针变量,需要用户根据所使用的RTC设备编写的,此结构体定义在include/linux/rtc.h 文件中,内容如下

struct rtc_class_ops {
 int (*open)(struct device *);
 void (*release)(struct device *);
 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 (*set_mmss64)(struct device *, time64_t secs);
 int (*set_mmss)(struct device *, unsigned long secs);
 int (*read_callback)(struct device *, int data);
 int (*alarm_irq_enable)(struct device *, unsigned int enabled);
};

rtc_class_ops 是最底层的 RTC 设备操作函数,并不是提供给应用层的。内核提供了一个 RTC 通用字符设备驱动文件,文件名为 drivers/rtc/rtc-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_dev_ioctl 最终会通过操作 rtc_class_ops 中的 read_time、 set_time 等函数来对具体 RTC 设备的读写操作。内核中 RTC 驱动调用流程图如下示

图片

2. Linux内核RTC驱动分析

一般情况下,半导体厂商都会编写好内部RTC驱动,无需我们自已动手编写。但是有必要了解一下是如何编写的

⏩ 打开imx6ull.dtsi,然后找到 snvs_rtc 节点内容,如下所示:

snvs_rtc: snvs-rtc-lp {
 compatible = "fsl,sec-v4.0-mon-rtc-lp";
 regmap = <&snvs>;
 offset = <0x34>;
 interrupts =

⏩ 根据compatible属性值,在Linux源码中搜索"fsl,sec-v4.0-mon-rtc-lp"符串,即可找到对应的驱动文件drivers//rtc/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);

⏩ 可见这是一个标准的platform驱动,当驱动和设备匹配以后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;

 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
 if (!data)
  return -ENOMEM;

 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");
  //从设备树中获取RTC外设寄存器基地址
  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  //内存映射,获得RTC外设寄存器物理基地址对应的虚拟地址
  mmio = devm_ioremap_resource(&pdev->dev, res);
  if (IS_ERR(mmio))
   return PTR_ERR(mmio);
  //采用regmap机制来读写RTC底层硬件寄存器
  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);
 }

 if (!data->regmap) {
  dev_err(&pdev->dev, "Can't find snvs syscon\\n");
  return -ENODEV;
 }
 //从设备树中获取 RTC 的中断号
 data->irq = platform_get_irq(pdev, 0);
 if (data->irq < 0)
  return data->irq;
 ......

 platform_set_drvdata(pdev, data);

 //用regmap机制的regmap_write函数完成对寄存器进行写操作
 regmap_write(data->regmap, data->offset + SNVS_LPPGDR, SNVS_LPPGDR_INIT);
 //清除LPSR寄存器
 regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff);
 //使能RTC
 snvs_rtc_enable(data, true);
 device_init_wakeup(&pdev->dev, true);
 //请求RTC中断
 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;
 }
 //向系统注册rtc_devcie
 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;
}

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

static const struct rtc_class_ops snvs_rtc_ops = {
 .read_time = snvs_rtc_read_time,
 .set_time = snvs_rtc_set_time,
 .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底层操作函数该如何去编写,该函数用于读取RTC时间值

static int snvs_rtc_read_time(struct device *dev,struct rtc_time *tm) {
 struct snvs_rtc_data *data = dev_get_drvdata(dev);
 //获取RTC计数值,该值是秒数
 unsigned long time = rtc_read_lp_counter(data);
 //将获取到的秒数转换为时间值,也就是rtc_time结构体类型
 rtc_time_to_tm(time, tm);

 return 0;
}

rtc_time 结构体定义如下:

struct rtc_time {
 int tm_sec;
 int tm_min;
 int tm_hour;
 int tm_mday;
 int tm_mon;
 int tm_year;
 int tm_wday;
 int tm_yday;
 int tm_isdst;
};

rtc_read_lp_counter 函数,此函数用于读取 RTC 计数值,函数内容如下

static u32 rtc_read_lp_counter(struct snvs_rtc_data *data) {
 u64 read1, read2;
 u32 val;
 //读取RTC_LPSRTCMR和RTC_LPSRTCLR这两个寄存器,得到RTC的计数值
 do {
  regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
  read1 = val;
  read1 <<= 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;
 } while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));

 /* Convert 47-bit counter to 32-bit raw second count */
 return (u32) (read1 >> CNTR_TO_SECS_SH);
}

3. RTC时间相关设置

RTC 是用来计时的,最基本的就是查看时间,Linux内核启动时可以看到系统时钟设置信息

图片

⏩ 查看时间命令:date

⏩ 设置当前时间:date -s

date -s "2022-08-15 13:20:00"

⏩ 将当前时间写入到RTC里:hwclock -w

图片

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 芯片
    +关注

    关注

    455

    文章

    50732

    浏览量

    423251
  • 晶振
    +关注

    关注

    34

    文章

    2860

    浏览量

    68009
  • 实时时钟
    +关注

    关注

    4

    文章

    245

    浏览量

    65769
  • 计数器
    +关注

    关注

    32

    文章

    2256

    浏览量

    94485
收藏 人收藏

    评论

    相关推荐

    STM32 RTC实时时钟(一)

    STM32处理器内部集成了实时时钟控制器(RTC),因此在实现实时时钟功能时,无须外扩时钟芯片即可构建实时时钟系统。
    的头像 发表于 07-22 15:41 4689次阅读
    STM32 <b class='flag-5'>RTC</b><b class='flag-5'>实时时钟</b>(一)

    深入浅出Linux_设备驱动编程

    深入浅出Linux_设备驱动编程
    发表于 08-16 15:57

    深入浅出Android

    深入浅出Android
    发表于 08-20 10:14

    深入浅出Linux_设备驱动编程

    深入浅出Linux_设备驱动编程
    发表于 08-20 14:58

    深入浅出Android

    深入浅出Android
    发表于 04-26 10:48

    什么是实时时钟RTC)?如何更改RTC的时间?

    什么是实时时钟RTC)?实时时钟RTC)的基本功能是什么?实时时钟RTC)晶体误差的主要来
    发表于 07-19 08:44

    RTC是什么?RTC实时时钟实验

    文章目录前言一、RTC是什么?二、RTC实时时钟实验1.引入库2.读入数据总结前言前面我们说了OLED实验,是一个比较好的显示测试代码的方法。现在我们学习关于RTC
    发表于 01-13 07:19

    RTC实时时钟怎么使用?

    RTC实时时钟怎么使用?cubemx中如何配置RTC?如何在keil中编写程序?
    发表于 01-18 07:33

    浅谈RTC实时时钟特征与原理

    一、RTC实时时钟特征与原理 查看STM32中文手册 16 实时时钟RTC)(308页) RTC (Real Time Clock):
    的头像 发表于 06-30 15:54 1.1w次阅读

    STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

    STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
    发表于 11-23 18:06 19次下载
    STM32CubeMX | 40 - <b class='flag-5'>实时时钟</b><b class='flag-5'>RTC</b>的使用(日历和闹钟)

    STM32CubeMX系列|RTC实时时钟

    RTC实时时钟1. RTC实时时钟简介2. 硬件设计3. 软件设计3.1 STM32CubeMX设置3.2 MDK-ARM编程4. 下载验证
    发表于 12-24 19:15 16次下载
    STM32CubeMX系列|<b class='flag-5'>RTC</b><b class='flag-5'>实时时钟</b>

    DA1468x SoC 的实时时钟(RTC) 概念

    DA1468x SoC 的实时时钟 (RTC) 概念
    发表于 03-15 20:16 0次下载
    DA1468x SoC 的<b class='flag-5'>实时时钟</b>(<b class='flag-5'>RTC</b>) 概念

    实时时钟RTC:32.768kHz晶振

    实时时钟(RTC: Real-Time Clock)是集成电路,通常称为时钟芯片。目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源。
    的头像 发表于 05-08 10:45 2759次阅读
    <b class='flag-5'>实时时钟</b><b class='flag-5'>RTC</b>:32.768kHz晶振

    DA1468x SoC 的实时时钟(RTC) 概念

    DA1468x SoC 的实时时钟 (RTC) 概念
    发表于 07-06 19:27 0次下载
    DA1468x SoC 的<b class='flag-5'>实时时钟</b>(<b class='flag-5'>RTC</b>) 概念

    CW32实时时钟RTC)介绍

    CW32实时时钟RTC)介绍
    的头像 发表于 10-24 15:36 1140次阅读
    CW32<b class='flag-5'>实时时钟</b>(<b class='flag-5'>RTC</b>)介绍