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

河南推广网站的公司17素材网下载

河南推广网站的公司,17素材网下载,网站的优化seo,大网站服务器维护费用RTC 也就是实时时钟,用于记录当前系统时间,对于 Linux 系统而言时间是非常重要的,就和我们使用 Windows 电脑或手机查看时间一样,我们在使用 Linux 设备的时候也需要查看时间。本章我们就来学习一下如何编写 Linux 下的 RTC 驱动程…

RTC 也就是实时时钟,用于记录当前系统时间,对于 Linux 系统而言时间是非常重要的,就和我们使用 Windows 电脑或手机查看时间一样,我们在使用 Linux 设备的时候也需要查看时间。本章我们就来学习一下如何编写 Linux 下的 RTC 驱动程序。

Linux 内核 RTC 驱动简介

RTC 设备驱动是一个标准的字符设备驱动,应用程序通过 open、 release、 read、 write 和 ioctl等函数完成对 RTC 设备的操作。
Linux 内核将 RTC 设备抽象为 rtc_device 结构体,因此 RTC 设备驱动就是申请并初始化rtc_device,最后将 rtc_device 注册到 Linux 内核里面,这样 Linux 内核就有一个 RTC 设备。
至于 RTC 设备的操作肯定是用一个操作集合(结构体)来表示的,我们先来看一下 rtc_device 结构体,此结构体定义在 include/linux/rtc.h 文件中,结构体内容如下:

struct rtc_device
{struct device dev;struct module *owner;int id;char name[RTC_DEVICE_NAME_SIZE];const struct rtc_class_ops *ops;struct mutex ops_lock;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;#ifdef CONFIG_RTC_INTF_DEV_UIE_EMULstruct work_struct uie_task;struct timer_list uie_timer;/* Those fields are protected by rtc->irq_lock */unsigned int oldsecs;unsigned int uie_irq_active:1;unsigned int stop_uie_polling:1;unsigned int uie_task_active:1;unsigned int uie_timer_active:1;
#endif
};

我们需要重点关注的是 ops 成员变量,这是一个 rtc_class_ops 类型的指针变量, rtc_class_ops为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 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_class_ops 中的这些函数只是最底层的 RTC 设备操作函数,并不是提供给应用层的file_operations 函数操作集。 RTC 是个字符设备,那么肯定有字符设备的 file_operations 函数操作集, Linux 内核提供了一个 RTC 通用字符设备驱动文件,文件名为 drivers/rtc/rtc-dev.c, rtcdev.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_dev_ioctl 函数,函数内容如下(有省略):

218 static long rtc_dev_ioctl(struct file *file,
219                           unsigned int cmd, unsigned long arg)
220 {
221     int err = 0;
222     struct rtc_device *rtc = file->private_data;
223     const struct rtc_class_ops *ops = rtc->ops;
224     struct rtc_time tm;
225     struct rtc_wkalrm alarm;
226     void __user *uarg = (void __user *) arg;
227
228     err = mutex_lock_interruptible(&rtc->ops_lock);
229     if (err)
230         return err;
......
269     switch (cmd) {
......
333         case RTC_RD_TIME: /* 读取时间 */
334             mutex_unlock(&rtc->ops_lock);
335
336             err = rtc_read_time(rtc, &tm);
337             if (err < 0)
338                 return err;
339
340             if (copy_to_user(uarg, &tm, sizeof(tm)))
341                 err = -EFAULT;
342             return err;
343
344         case RTC_SET_TIME: /* 设置时间 */
345             mutex_unlock(&rtc->ops_lock);
346
347             if (copy_from_user(&tm, uarg, sizeof(tm)))
348                 return -EFAULT;
349
350             return rtc_set_time(rtc, &tm);
......
401         default:
402         /* Finally try the driver's ioctl interface */
403             if (ops->ioctl) {
404                 err = ops->ioctl(rtc->dev.parent, cmd, arg);
405                 if (err == -ENOIOCTLCMD)
406                 err = -ENOTTY;
407             } else
408                 err = -ENOTTY;
409         break;
410     }
411
412     done:
413     mutex_unlock(&rtc->ops_lock);
414     return err;
415 }

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

static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{int err;if (!rtc->ops)err = -ENODEV;else if (!rtc->ops->read_time)err = -EINVAL;else {memset(tm, 0, sizeof(struct rtc_time));err = rtc->ops->read_time(rtc->dev.parent, tm);if (err < 0) {dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",err);return err;}err = rtc_valid_tm(tm);if (err < 0)dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");}return err;
}

从上述代码可以看出, __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 驱动调用流程就很清晰了,如下图所示:

当 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_register 和 devm_rtc_device_unregister,分别为注册和注销 rtc_device。

I.MX6U 内部 RTC 驱动分析

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

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

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

380 static const struct of_device_id snvs_dt_ids[] = {
381     { .compatible = "fsl,sec-v4.0-mon-rtc-lp", },
382     { /* sentinel */ }
383 };
384 MODULE_DEVICE_TABLE(of, snvs_dt_ids);
385
386 static struct platform_driver snvs_rtc_driver = {
387     .driver = {
388         .name = "snvs_rtc",
389         .pm = SNVS_RTC_PM_OPS,
390         .of_match_table = snvs_dt_ids,
391     },
392     .probe = snvs_rtc_probe,
393 };
394 module_platform_driver(snvs_rtc_driver);

第 380~383 行,设备树 ID 表,有一条 compatible 属性,值为“fsl,sec-v4.0-mon-rtc-lp”,因此 imx6ull.dtsi 中的 snvs_rtc 设备节点会和此驱动匹配。
第 386~393 行,标准的 platform 驱动框架,当设备和驱动匹配成功以后 snvs_rtc_probe 函数就会执行。我们来看一下 snvs_rtc_probe 函数,函数内容如下(有省略):

238 static int snvs_rtc_probe(struct platform_device *pdev)
239 {
240     struct snvs_rtc_data *data;
241     struct resource *res;
242     int ret;
243     void __iomem *mmio;
244
245     data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
246     if (!data)
247         return -ENOMEM;
248
249     data->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
250
251     if (IS_ERR(data->regmap)) {
252         dev_warn(&pdev->dev, "snvs rtc: you use old dts file, please update it\n");
253         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
254
255         mmio = devm_ioremap_resource(&pdev->dev, res);
256         if (IS_ERR(mmio))
257             return PTR_ERR(mmio);
258
259         data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, &snvs_rtc_config);
260     } else {
261         data->offset = SNVS_LPREGISTER_OFFSET;
262         of_property_read_u32(pdev->dev.of_node, "offset", &data->offset);
263     }
264
265     if (!data->regmap) {
266         dev_err(&pdev->dev, "Can't find snvs syscon\n");
267         return -ENODEV;
268     }
269
270     data->irq = platform_get_irq(pdev, 0);
271     if (data->irq < 0)
272         return data->irq;
......
285
286     platform_set_drvdata(pdev, data);
287
288     /* Initialize glitch detect */
289     regmap_write(data->regmap, data->offset + SNVS_LPPGDR, SNVS_LPPGDR_INIT);
290
291     /* Clear interrupt status */
292     regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff);
293
294     /* Enable RTC */
295     snvs_rtc_enable(data, true);
296
297     device_init_wakeup(&pdev->dev, true);
298
299     ret = devm_request_irq(&pdev->dev, data->irq, snvs_rtc_irq_handler,
300                         IRQF_SHARED, "rtc alarm", &pdev->dev);
301     if (ret) {
302         dev_err(&pdev->dev, "failed to request irq %d: %d\n",
303                 data->irq, ret);
304         goto error_rtc_device_register;
305     }
306
307     data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
308                                     &snvs_rtc_ops, THIS_MODULE);
309     if (IS_ERR(data->rtc)) {
310         ret = PTR_ERR(data->rtc);
311         dev_err(&pdev->dev, "failed to register rtc: %d\n", ret);
312         goto error_rtc_device_register;
313     }
314
315     return 0;
316
317 error_rtc_device_register:
318 if (data->clk)
319 clk_disable_unprepare(data->clk);
320
321 return ret;
322 }

第 253 行,调用 platform_get_resource 函数从设备树中获取到 RTC 外设寄存器基地址。
第 255 行,调用函数 devm_ioremap_resource 完成内存映射,得到 RTC 外设寄存器物理基地址对应的虚拟地址。
第 259 行, Linux3.1 引入了一个全新的 regmap 机制, regmap 用于提供一套方便的 API 函数去操作底层硬件寄存器,以提高代码的可重用性。 snvs-rtc.c 文件会采用 regmap 机制来读写
RTC 底层硬件寄存器。这里使用 devm_regmap_init_mmio 函数将 RTC 的硬件寄存器转化为regmap 形式,这样 regmap 机制的 regmap_write、 regmap_read 等 API 函数才能操作寄存器。
第 270 行,从设备树中获取 RTC 的中断号。
第 289 行,设置 RTC_ LPPGDR 寄存器值为 SNVS_LPPGDR_INIT= 0x41736166,这里就是用的 regmap 机制的 regmap_write 函数完成对寄存器进行写操作。
第 292 行,设置 RTC_LPSR 寄存器,写入 0xffffffff, LPSR 是 RTC 状态寄存器,写 1 清零,因此这一步就是清除 LPSR 寄存器。
第 295 行,调用 snvs_rtc_enable 函数使能 RTC,此函数会设置 RTC_LPCR 寄存器。
第299行,调用devm_request_irq函数请求RTC中断,中断服务函数为snvs_rtc_irq_handler,用于 RTC 闹钟中断。
第 307 行,调用 devm_rtc_device_register 函数向系统注册 rtc_devcie, RTC 底层驱动集为snvs_rtc_ops。snvs_rtc_ops操作集包含了读取/设置RTC时间,读取/设置闹钟等函数。snvs_rtc_ops
内容如下:

200 static const struct rtc_class_ops snvs_rtc_ops = {
201     .read_time = snvs_rtc_read_time,
202     .set_time = snvs_rtc_set_time,
203     .read_alarm = snvs_rtc_read_alarm,
204     .set_alarm = snvs_rtc_set_alarm,
205     .alarm_irq_enable = snvs_rtc_alarm_irq_enable,
206 };

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

126 static int snvs_rtc_read_time(struct device *dev, struct rtc_time *tm)
127 {
128     struct snvs_rtc_data *data = dev_get_drvdata(dev);
129     unsigned long time = rtc_read_lp_counter(data);
130
131     rtc_time_to_tm(time, tm);
132
133     return 0;
134 }

第 129 行,调用 rtc_read_lp_counter 获取 RTC 计数值,这个时间值是秒数。
第 131 行,调用 rtc_time_to_tm 函数将获取到的秒数转换为时间值,也就是 rtc_time 结构体类型, rtc_time 结构体定义如下:

20 struct rtc_time {
21     int tm_sec;
22     int tm_min;
23     int tm_hour;
24     int tm_mday;
25     int tm_mon;
26     int tm_year;
27     int tm_wday;
28     int tm_yday;
29     int tm_isdst;
30 };

最后我们来看一下 rtc_read_lp_counter 函数,此函数用于读取 RTC 计数值,函数内容如下:

50 static u32 rtc_read_lp_counter(struct snvs_rtc_data *data)
51 {
52     u64 read1, read2;
53     u32 val;
54
55     do {
56         regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
57         read1 = val;
58         read1 <<= 32;
59         regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
60         read1 |= val;
61
62         regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
63         read2 = val;
64         read2 <<= 32;
65         regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
66         read2 |= val;
67 /*
68 * when CPU/BUS are running at low speed, there is chance that
69 * we never get same value during two consecutive read, so here
70 * we only compare the second value.
71 */
72     } while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));
73
74 /* Convert 47-bit counter to 32-bit raw second count */
75     return (u32) (read1 >> CNTR_TO_SECS_SH);
76 }

第 56~72 行,读取 RTC_LPSRTCMR 和 RTC_LPSRTCLR 这两个寄存器,得到 RTC 的计数值,单位为秒,这个秒数就是当前时间。这里读取了两次 RTC 计数值,因为要读取两个寄存器,
因此可能存在读取第二个寄存器的时候时间数据更新了,导致时间不匹配,因此这里连续读两次,如果两次的时间值相等那么就表示时间数据有效。
第 75 行,返回时间值,注意这里将前面读取到的 RTC 计数值右移了 15 位。
这个就是 snvs_rtc_read_time 函数读取 RTC 时间值的过程,至于其他的底层操作函数大家自行分析即可,都是大同小异的,这里就不再分析了。关于 I.MX6U 内部 RTC 驱动源码就讲解到这里。

RTC 时间查看与设置

RTC 是用来计时的,因此最基本的就是查看时间, Linux 内核启动的时候可以看到系统时钟设置信息,如图所示:

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

设置 RTC 时间

RTC 时间设置需要使用 date和hwclock 命令,输入“date --help”命令即可查看 date 命令如何设置系统时间,结果如图所示:

现在我要设置当前时间为 2019 年 8 月 31 日 18:13:00,因此输入如下命令:

date -s "2019-08-31 18:13:00"

设置完成以后再次使用 date 命令查看一下当前时间就会发现时间改过来了:

大家注意我们使用“ date -s”命令仅仅是将当前系统时间设置了,此时间还没有写入到I.MX6U 内部 RTC 里面或其他的 RTC 芯片里面,因此系统重启以后时间又会丢失。我们需要将当前的时间写入到 RTC 里面,这里要用到 hwclock 命令,输入如下命令将系统时间写入到 RTC里面:

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

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

http://www.15wanjia.com/news/162568.html

相关文章:

  • 廊坊住房和城乡建设厅网站怎么免费做一个网站做淘宝客
  • 做一个网站需要多少钱网站有那些风格
  • 随便编一个公司网站北京市公共资源交易中心
  • 永久免费的自建网站万网网站建设选哪个好
  • 网站运维工作内容广州微网站建设案例
  • 兰州市城市建设设计院官方网站湛江快速网站建设在哪里做
  • 怎么做手机版网站外贸营销型网站制作
  • WordPress站点添加ssl证书wordpress大胡子主题
  • 网站淘客宝怎么做网址后缀名大全
  • 网站的建设背景网络推广软件工具
  • 如何做网站的维护和推广网站ipc备案
  • qfd 网站开发台州市建设规划局网站6
  • 门户网站的定义电商平台搭建方案
  • 网站上的链接怎么做的营销推广的目标通常是什么
  • cad dwt模板做网站模版美食网站php源码
  • 网站开发的搭建框架是什么意思wordpress英文版登陆
  • 金华竞价排名 金华企业网站建设wordpress手机号码
  • 阜宁网站制作哪家好金蝶erp系统介绍
  • 怎么自己搭建网站wordpress富文本编辑器
  • 一般上什么网站看吧播放器为什么卡
  • 做网站的微信号校园网站建设培训稿
  • 网页中网站设计规划流程百度网盟推广 网站
  • 金山区做网站公司nodejs wordpress
  • 网站导航设计欣赏关于网站开发书籍
  • 企业建设网站需要服务器吗做网站首页的尺寸
  • 做教育机器网站网站建设 的公司
  • 中企动力做的网站升级收费做网页设计卖钱的网站
  • 代码优化网站排名seo网站建设课程
  • php学校网站模板网站建设的设计思路
  • 网站安全建设方案步骤郑州网站制作公司排名