【北京迅为】i.MX6ULL终结者Linux RTC驱动实验i.MX6UL下的RTC驱动

RTC是一个非常普遍的设备,因此一般厂家都会编写好,所以NXP官方已经实现好了,不用我们自己去写了。但是我们要了解RTC驱动是如何实现的,下面就来分析一下。
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内核中根据compatible属性值信息就可以找到对应的驱动文件,此文件为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);

第381行,compatible的值也是“fsl,sec-v4.0-mon-rtc-lp”,这样才能匹配成功。
其他的内容就是标准的platform框架的内容,然后来看一下,当设备和驱动匹配成功后,执行的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
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 时间值的过程,其他的底层操作函数实现都差不多,就不在这里分析了。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/BeiJingXunWei/article/details/112358851