60 rtc 实时时钟驱动

本文详细介绍了Linux内核中的RTC驱动,包括基本结构、底层操作函数、I.MX6U RTC驱动分析以及时间查看与设置。重点讲解了如何编写和注册RTC驱动,以及如何通过file_operations和rtc_class_ops进行设备操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • RTC 也就是实时时钟,用于记录当前系统时间

1、Linux 内核 RTC 驱动简介

RTC 设备驱动是字符设备驱动,需要有 struct file_operation 操作集(成员函数 open,release,read,write 和 ioctl)

在linux内核中使用 struct rtc_device 来表示一个 rtc实时时钟。

编写linux下的rtc驱动就是申请并初始化 struct rtc_device ,最后将 struct rtc_device 注册到 linux内核 里面。

struct rtc_device 定义在 include/linux/rtc.h

struct rtc_device
{
	struct device dev;
	struct module *owner;
	int id;
	char name[RTC_DEVICE_NAME_SIZE];
	/*
	 * rtc设备底层操作函数
	 * file_operations 结构体中的成员函数最终
	 * 也是调用此操作集里面的成员函数
	 */
	const struct rtc_class_ops *ops;
	struct mutex ops_lock;
	/* rtc也是字符设备,内嵌一个cdev结构体 */
	struct cdev char_dev;
	unsigned long flags;

	unsigned long irq_data;
	spinlock_t irq_lock;
	wait_queue_head_t irq_queue;
	struct fasync_struct *async_queue;

	struct rtc_task *irq_task;
	spinlock_t irq_task_lock;
	int irq_freq;
	int max_user_freq;

	struct timerqueue_head timerqueue;
	struct rtc_timer aie_timer;
	struct rtc_timer uie_rtctimer;
	struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
	int pie_enabled;
	struct work_struct irqwork;
	/* Some hardware can't support UIE mode */
	int uie_unsupported;
};

重点关注的是 ops 成员变量,这是一个 rtc_class_ops 类型的指针变量, rtc_class_ops 为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间值等。因此, rtc_class_ops 是需要用户根据所使用的 RTC 设备编写的。
结构体 struct rtc_class_ops定义在 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);
};

struct rtc_class_ops 中的这些函数是最底层的 RTC 设备操作函数,并不是提供给应用层的 file_operations 函数操作集。
RTC 是个字符设备,有字符设备的 struct file_operations 函数操作集。
Linux 内核提供了一个RTC 通用字符设备驱动文件,文件名为 drivers/rtc/rtc-dev.crtc-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,
};

应用程序可以通过 struct file_operation 的成员函数 ioctl 函数来设置/读取时间、设置/读取闹钟,那么对应的 rtc_dev_ioctl 函数就会执行。
成员函数 rtc_dev_ioctl 最终会通过操作 struct rtc_class_ops 中的 read_time、 set_time 、read_alarm、set_alarm 等rtc底层函数来对具体 RTC 设备的读写操作。(这些底层操作函数内部就是利用 regmap 机制,利用该机制提供的api 来读写相关寄存器)

  • 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: 设备。
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_registerdevm_rtc_device_unregister,分别为注册和注销 rtc_device。

2、I.MX6U 内部 RTC 驱动分析

分析驱动,先从设备树入手,打开 imx6ull.dtsi,在里面找到如下 snvs_rtc 设备节点,节点内容如下所示:

snvs: snvs@020cc000 {
				compatible = "fsl,sec-v4.0-mon", "syscon", "simple-mfd";
				reg = <0x020cc000 0x4000>;

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

第 2 行设置兼容属性 compatible 的值为“fsl,sec-v4.0-mon-rtc-lp”,因此在 Linux 内核源码中搜索此字符串即可找到对应的驱动文件,此文件为 drivers/rtc/rtc-snvs.c
上面的 snvs_rtc 节点会与此驱动匹配。

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

另外 struct rtc_class_ops 中的底层操作函数内部就是利用 regmap 机制,利用该机制提供的api 来读写相关寄存器。详见 drivers/rtc/rtc-snvs.c

3、 RTC 时间查看与设置

  • Linux 内核启动的时候可以看到系统时钟设置信息:
snvs_rtc 20cc000.snvs:snvs-rtc-lp: rtc core: registered 20cc000.snvs:snvs-r as rtc0

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

  • 查看时间
/ # 
/ # date
Thu Jan  1 03:35:35 UTC 1970
/ # 
  • 设置时间
    RTC 时间设置也是使用的 date 命令,date --help 命令即可查看如何设置系统时间。
/ # 
/ # date --help
BusyBox v1.29.0 (2020-12-31 23:03:46 CST) multi-call binary.

Usage: date [OPTIONS] [+FMT] [TIME]

Display time (using +FMT), or set time

        [-s,--set] TIME Set time to TIME
        -u,--utc        Work in UTC (don't convert to local time)
        -R,--rfc-2822   Output RFC-2822 compliant date string
        -I[SPEC]        Output ISO-8601 compliant date string
                        SPEC='date' (default) for date only,
                        'hours', 'minutes', or 'seconds' for date and
                        time to the indicated precision
        -r,--reference FILE     Display last modification time of FILE
        -d,--date TIME  Display TIME, not 'now'
        -D FMT          Use FMT for -d TIME conversion

Recognized TIME formats:
        hh:mm[:ss]
        [YYYY.]MM.DD-hh:mm[:ss]
        YYYY-MM-DD hh:mm[:ss]
        [[[[[YY]YY]MM]DD]hh]mm[.ss]
        'date TIME' form accepts MMDDhhmm[[YY]YY][.ss] instead
/ # 
/ #
/ # date -s "2021-05-26 23:43:00" (设置时间)
/ # hwclock -w (将当前系统时间写入到 RTC 里面)

若纽扣电池没电了。rtc断电了也就不能继续工作了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值