上一章学习了STM32MP1内置RTC外设,了解了Linux系统下RTC驱动框架。一般的应用场合使用SOC内置的RTC就可以了,而且成本也低,但是在一些对于时间精度要求比较高的场合,SOC内置的RTC就不适用了。这个时候需要根据自己的应用要求选择合适的外置RTC芯片,正点原子STM32MP1开发板上板载了一个RTC芯片:PCF8563,这是一个IIC接口的外置RTC芯片,本章就来学习一 下如何驱动外置RTC芯片。
PCF8563是一个CMOS RTC芯片,支持时间和日历功能,支持可编程的时钟输出、中断输出以及低电压检测。PCF8563提供了两线IIC接口来传输时间信息,最大传输速度为400Kbit/S,在读写寄存器的时候地址自增,PCF8563相关特性如下:
PCF8563框图如下图所示:
简单分析一下上图中的框图:
PCF8563有16个内部寄存器,这些寄存器都是8位的。前两个寄存器(0x00和0x01)为控
制/状态寄存器。0X02-0X08为时间和日期寄存器,这些寄存器保存着秒、分、时、日、星期、月和年信息。0X09-0X0C为闹钟寄存器,保存闹钟信息。0X0D为时钟输出频率寄存器,0X0E和0X0F这两个寄存器时钟控制寄存器。注意、时分秒、年月日、闹钟等时间信息为BCD格式。
接下来看一下这些寄存器如何使用:
寄存器结构如下图所示:
上图是控制状态寄存器 1,相应的位含义如下:
寄存器结构如下图所示:
上图是控制状态寄存器2,相应的位含义如下:
接下来看一下时间和日期相关寄存器,一共7个寄存器,结构如下图所示:
依次来看一下上图中的这些寄存器:
接下来看一下闹钟相关寄存器,一共4个寄存器,结构如下图所示:
依次来看一下上图中的这些寄存器:
另外还有时钟输出寄存(0X0D)以及定时器寄存器(0X0E和0X0F),这里不用PFC8563的时钟输出和定时器功能,就不讲解了。
总体来说,PCF8563还是很简单的,这是一个IIC接口的RTC芯片,因此在Linux系统下
就涉及到两类驱动:
如果要用到中断功能的话,还需要用到Linux系统中的中断子系统,这些前面都有相应的实验讲解。所以PCF8563的Linux驱动并不复杂,而且重点是Linux系统默认就已经集了PCF8563驱动,使用起来非常简单,直接修改设备树,添加PCF8563节点信息,然后使能内核的PCF8563驱动即可。
PCF8563原理图如下图所示:
从上图可以看出,PCF8563连接到了STM32MP157的I2C4接口上,引脚为PZ5、 PZ4。另外, PCF8563的INT引脚连接到了STM32MP157的PI3引脚上。
PCF8563的IIC接口连接到了STM32MP157的I2C4上,对应的引脚为PZ4和PZ5。另外还有一个中断引脚PI3,首先需要在设备树中添加这3个引脚对应的配置信息。
首先添加PZ4和PZ5,打开stm32mp15-pincrtl.dtsi文件,查找一下有没有I2C4的引脚配置信息,默认是有的,内容如下:
示例代码44.3.1.1 i2c4引脚节点
1 i2c4_pins_a: i2c4-0 {
2 pins {
3 pinmux = <STM32_PINMUX('Z', 4, AF6)>, /* I2C4_SCL */
4 <STM32_PINMUX('Z', 5, AF6)>; /* I2C4_SDA */
5 bias-disable;
6 drive-open-drain;
7 slew-rate = <0>;
8 };
9 };
10
11 i2c4_pins_sleep_a: i2c4-1 {
12 pins {
13 pinmux = <STM32_PINMUX('Z', 4, ANALOG)>, /* I2C4_SCL */
14 <STM32_PINMUX('Z', 5, ANALOG)>; /* I2C4_SDA */
15 };
16 };
从第3、4行可以看出,I2C4默认引脚就是PZ4和PZ5,和本实验一样,所以I2C4的引脚不需要修改,直接使用i2c4_pins_a即可。接下来还需要定义中断引脚PI3的引脚信息,前面讲过了,如果一个引脚作为GPIO功能的话可以不用添加此引脚pinctrl信息。
前面说了Linux内核内部已经集成了PCF8563驱动,所以肯定有文档描述如何使用这个驱动。打开Documentation/devicetree/bindings/rtc/pcf8563.txt,此文档描述了如何使用Linux内核自带的pcf8563驱动,也给出了参考设备节点,参考此文档即可。
在stm32mp157d-atk.dts文件,追加I2C4节点,追加如下所示内容:
示例代码 44. 3.1.3 追加 pcf 8563 节点
1 &i2c4 {
2 pinctrl-names = "default", "sleep";
3 pinctrl-0 = <&i2c4_pins_a>;
4 pinctrl-1 = <&i2c4_pins_sleep_a>;
5 status = "okay";
6
7 pcf8563@51{
8 compatible = "nxp,pcf8563";
9 irq_gpio = <&gpioi 3 IRQ_TYPE_EDGE_FALLING>;
10 reg = <0x51>;
11 };
12 };
第2-4行,设置IO要使用的pinmux配置。
第7-10行,pcf8563设备子节点,第8行设置compatible为“nxp,pcf8563”,这个是必须的,否则无法匹配Linux内核自带的pcf8563驱动。从第9行设置pcf8563中断引脚为PI3,下降沿触发。pcf8563的I2C地址为0X51,因此reg为0X51。
上一个实验使能了STM32MP157内部RTC,为了防止干扰,所以要先关闭内部RTC!配置路径为:
-> Device Drivers -> Real Time Clock -> STM32 RTC //取消选中 |
如下图所示:
接下来需要使能Linux内核自带的PCF8563驱动,配置路径如下:
-> Device Drivers -> Real Time Clock -> <*> Philips PCF8563/Epson RTC8564 //选中 PCF8563 |
如下图所示:
配置完成后重新编译内核和设备树,得到新的uImage以及stm32mp157d-atk.dtb。
使用上面编译得到的内核和设备树启动开发板。当系统第一次启动,没有设置PCF8563时间的时候,启动过程会提示如下图所示信息:
从上图可以看出,系统已经识别出了PCF8563,说明驱动没问题。但是,这里提示检测到低电压,日期和时间无效。这是因为没有设置时间,等系统启动成功,然后参考上一篇笔记内部RTC的设置方法设置RTC时间,比如这里设置时间为2021年5月21号,下午15:52:00,输入如下命令:
date -s "2021-05-21 15:52:00" //设置时间 hwclock -w //保存 |
时间设置好以后重启系统,此时系统log信息如下图所示:
从上图可以看出,此时PCF8563再没有提示电压低的错误,而且正确的读出了时间信
息,整个开发板掉电以后PCF8563也会继续计时,因为有一个纽扣电池供电。
上一小节已经测试了PCF8563,本小节来简单看一下PCF8563驱动源码,根据示例代码44.3.1.3中的第8行的compatible属性值可以找到对应到驱动文件,在Linux源码中搜索字符串“nxp,pcf8563”即可找到对应的驱动文件,驱动文件为drivers/rtc/rtc-pcf8563.c。
PCF8563是个I2C器件,因此基础驱动框架是I2C,在rtc-pcf8563.c文件中找到如下所示内容:
上述示例代码就是个标准的I2C驱动框架,第9-14行的pcf8563_of_match结构体数组就是设备树匹配数组,第10行的compatible属性为“nxp,pcf8563”,和设备树相匹配。匹配以后第23行的pcf8563_probe函数就会执行。
接下来看一下pcf8563_probe函数,函数源码如下(有缩略):
示例代码44.5.2 pcf8563_probe 函数
1 static int pcf8563_probe(struct i2c_client *client,
2 const struct i2c_device_id *id)
3 {
4 struct pcf8563 *pcf8563;
5 int err;
6 unsigned char buf;
......
13 pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563),
14 GFP_KERNEL);
15 if (!pcf8563)
16 return -ENOMEM;
17
18 i2c_set_clientdata(client, pcf8563);
19 pcf8563->client = client;
20 device_set_wakeup_capable(&client->dev, 1);
21
22 /* Set timer to lowest frequency to save power */
23 buf = PCF8563_TMRC_1_60;
24 err = pcf8563_write_block_data(client, PCF8563_REG_TMRC, 1, &buf);
25 if (err < 0) {
26 dev_err(&client->dev, "%s: write error\n", __func__);
27 return err;
28 }
29
30 /* Clear flags and disable interrupts */
31 buf = 0;
32 err = pcf8563_write_block_data(client, PCF8563_REG_ST2, 1, &buf);
33 if (err < 0) {
34 dev_err(&client->dev, "%s: write error\n", __func__);
35 return err;
36 }
37
38 pcf8563->rtc = devm_rtc_allocate_device(&client->dev);
39 if (IS_ERR(pcf8563->rtc))
40 return PTR_ERR(pcf8563->rtc);
41
42 pcf8563->rtc->ops = &pcf8563_rtc_ops;
43 /* the pcf8563 alarm only supports a minute accuracy */
44 pcf8563->rtc->uie_unsupported = 1;
45 pcf8563->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
46 pcf8563->rtc->range_max = RTC_TIMESTAMP_END_2099;
47 pcf8563->rtc->set_start_time = true;
48
49 if (client->irq > 0) {
50 err = devm_request_threaded_irq(&client->dev, client->irq,
51 NULL, pcf8563_irq,
52 IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
53 pcf8563_driver.driver.name, client);
54 if (err) {
55 dev_err(&client->dev, "unable to request IRQ %d\n",
56 client->irq);
57 return err;
58 }
59 }
60
61 err = rtc_register_device(pcf8563->rtc);
62 if (err)
63 return err;
......
70 return 0;
71 }
第13行,申请内存内存,rtc-pcf8563.c定义了一个pcf8563结构体来描述PCF8563芯片,所以这里就是申请一个pcf8563实例。
第23-36行,初始化PCF8563。
第38行,pcf8563结构体里面有个rtc成员变量,此成员变量是个rtc_device结构体指针。
这个就是上一章讲解的RTC驱动框架最核心的rtc_device。这里需要对这个rtc指针分配内存。
第42行,设置rtc_device的ops成员变量为pcf8563_rtc_ops,pcf8563_rtc_ops包含了PCF8563的具体操作,包括设置时间、读取时间、设置闹钟等。
第44-47行,继续初始化rtc的其他成员变量。
第49-59行,中断初始化,PCF8563有个中断引脚INT,因此可以使用中断功能。这里使用devm_request_threaded_irq函数完成中断申请已经初始化,中断函数为pcf8563_irq。
第61行,调用rtc_register_device函数向系统注册rtc_device,也就是pcf8563。
总结 一下,pcf8563_probe函数的核心就是初始化PCF8563,然后使用上一章讲的RTC驱动框架来设置PCF8563,然后向内核注册。
接下来看一下PCF8563的核心:pcf8563_rtc_ops,内容如下:
pcf8563_rtc_ops提供了PCF8563的时间以及闹钟读写操作函数,应用程序对PCF8563的所有操作最终都是通过这些函数来完成的。以读时间为例,当应用程序读取PCF8563当前时间的时候,.read_time就会执行,在这里就是pcf8563_rtc_read_time,函数源码如下(有省略):
第8行,使用pcf8563_read_block_data函数从PCF8563_REG_ST1寄存器(地址为0X00)开始,连续读取9个寄存器的数据。这样就可以得到PCF8563的控制与状态寄存器1和2,以及事件与日期寄存器的值。
第12行,判断PCF8563的0X02寄存器VL位是否为1,也就是检查PCF8563是否处于低电压模式,事件和日期是否有效。
第28-34行,依次获取PCF8563中的时间和日期值,这里使用bcd2bin函数将原始的BCD值转换为时间值。将获取到的时间和日期打包到参数tm中,tm是个rtc_time结构体指针变量。
第36行,判断0X07寄存器的C位(bit7)的值,此位为1的话表示20xx年,为0的话就是19xx年。
可以看出pcf8563_rtc_read_time函数很简单,就是读取PCF8563内部的时间和日期值,然后将其打包进rtc_time里面。其他的函数大同小异,可以自行分析一下。
至此,PCF8563驱动就简单分析完成了,其他IIC接口的RTC芯片驱动基本都是类似的,可以在实际项目开发中选择合适的RTC芯片。
这里的驱动PCF8563还是比较简单的,因为Linux内核是已经写好了相关驱动的,只要自己在Linux内核配置开启,然后在设备树中添加相应的对应的i2c节点以及GPIO对应的子节点就可以使用了。