使用PCF8563代替内核的RTC,可以降低功耗,提高时间的精度。同时有助于进一步熟悉I2C驱动的编写。
1、了解rtc_time64_to_tm()和rtc_tm_to_time64()
打开"drivers/rtc/lib.c"
/*
* rtc_time64_to_tm - Converts time64_t to rtc_time.
* Convert seconds since 01-01-1970 00:00:00 to Gregorian date.
*/
//将time转换为年月日时分秒和星期几
void rtc_time64_to_tm(time64_t time, struct rtc_time *tm)
{
unsigned int month, year, secs;
int days;
/* time must be positive */
days = div_s64_rem(time, 86400, &secs);/*计算总共有多少天*/
/* day of the week, 1970-01-01 was a Thursday */
tm->tm_wday = (days + 4) % 7;/*计算是星期几*/
year = 1970 + days / 365;/*计算公元年数值*/
days -= (year - 1970) * 365
- LEAPS_THRU_END_OF(year - 1)
- LEAPS_THRU_END_OF(1970 - 1);
while (days < 0) {
year -= 1;
days += 365 + is_leap_year(year);
}
tm->tm_year = year - 1900;/*计算年*/
tm->tm_yday = days + 1;
for (month = 0; month < 11; month++) {
int newdays;
newdays = days - rtc_month_days(month, year);
if (newdays < 0)
break;
days = newdays;
}
tm->tm_mon = month;
tm->tm_mday = days + 1;
tm->tm_hour = secs / 3600;/*计算小时*/
secs -= tm->tm_hour * 3600;
tm->tm_min = secs / 60;/*计算分钟*/
tm->tm_sec = secs - tm->tm_min * 60;/*计算秒*/
tm->tm_isdst = 0;
}
/*
* rtc_tm_to_time64 - Converts rtc_time to time64_t.
* Convert Gregorian date to seconds since 01-01-1970 00:00:00.
*/
//将年月日时分秒和星期几转换为64位的time
time64_t rtc_tm_to_time64(struct rtc_time *tm)
{
return mktime64(((unsigned int)tm->tm_year + 1900), tm->tm_mon + 1,
tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
}
2、了解i2c_check_functionality()
打开"include/linux/i2c.h"
/* Return the functionality mask */
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
{
return adap->algo->functionality(adap);
//返回该适配器支持的标志
}
/* Return 1 if adapter supports everything we need, 0 if not. */
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
{
return (func & i2c_get_functionality(adap)) == func;
//如果I2C适配器支持func函数,则返回1;
}
举例:
打开"/usr/include/linux/i2c.h"
/* To determine what functionality is present */
#define I2C_FUNC_I2C 0x00000001
#define I2C_FUNC_10BIT_ADDR 0x00000002
#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_IGNORE_NAK etc. */
#define I2C_FUNC_SMBUS_PEC 0x00000008
#define I2C_FUNC_NOSTART 0x00000010 /* I2C_M_NOSTART */
#define I2C_FUNC_SLAVE 0x00000020
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_QUICK 0x00010000
#define I2C_FUNC_SMBUS_READ_BYTE 0x00020000
#define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000
#define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000
#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000
#define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000
#define I2C_FUNC_SMBUS_PROC_CALL 0x00800000
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_HOST_NOTIFY 0x10000000
i2c_check_functionality(client->adapter, I2C_FUNC_I2C);
3、了解module_i2c_driver()
打开"include/linux/device.h"
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##VA_ARGS); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##VA_ARGS); \
} \
module_exit(__driver##_exit);
打开"include/linux/i2c.h"
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)
/*module_init()为驱动入口函数
module_exit()为驱动出口函数
*/
举例:
module_i2c_driver(pcf8563_driver);
因此module_driver(pcf8563_driver, i2c_add_driver, i2c_del_driver)展开后,就是下面的内容:
static int __init pcf8563_driver_init(void)
{
return i2c_add_driver( &(pcf8563_driver) );
}
module_init(pcf8563_driver_init);
static void __exit pcf8563_driver_exit(void)
{
i2c_del_driver(&(pcf8563_driver) );
}
module_exit(pcf8563_driver_exit);
4、PCF8563的相关寄存器
1)、控制/状态寄存器1
|------|-------|------|------|------|-------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x00 | TEST1 | NC | STOP | NC | TESTC | NC | NC | NC |
当TEST1=0时,则设置为普通模式;当TEST1=1时,则设置为测试模式;
当STOP=0时,芯片时钟运行;当STOP=1时,芯片时钟停止运行;
当TESTC=0时,电源复位功能失效;当TESTC=1时,电源复位功能有效;
NC表示不使用;
2)、控制/状态寄存器2
|------|------|------|------|-------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x01 | NC | NC | NC | TI/TP | AF | TF | AIE | TIE |
当TIE=0时,定时器中断不允许;当TIE=1时,定时器中断允许;
当AIE=0时,闹钟中断不允许;当AIE=1时,闹钟中断允许;
TF为定时器倒计数中断标志位;若读TF为1,表示建立定时器倒计数中断标志;若设置TF=0时,清除定时器倒计数中断标志位;
AF为闹钟标志位;若读AF为1,表示建立闹钟标志;若设置AF=0时,清除建立的闹钟标志;
当TI/TP=0时,若TF=1且TIE=1,则INT脚输出高电平,取决于TF位;
当TI/TP=1时,若TIE=1,则INT脚输出指定频率的脉冲;
NC表示不使用;
注意:当AIE=0,TIE=0时,INT脚输出高阻抗;
3)、定时器控制寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x0E | TE | NC | NC | NC | NC | NC | TD[1:0] ||
TE=0,向下计数器不使能;TE=1,向下计数器使能;
TD[1:0]用来选择时钟源;
TD[1:0]=00b,选择时钟源为4096Hz;
TD[1:0]=01b,选择时钟源为64Hz;
TD[1:0]=10b,选择时钟源为1Hz;
TD[1:0]=11b,选择时钟源为1/60Hz;
分析:
TD[1:0]=00b,TI/TP=1,TIE=1,TE=1,配置INT脚输出指定频率的脉冲:当Timer[7:0]=1时8.192KHz;
当Timer[7:0]>1时4.096KHz;当Timer[7:0]=0时,定时器不工作;
TD[1:0]=01b,TI/TP=1,TIE=1,TE=1,配置INT脚输出指定频率的脉冲:当Timer[7:0]=1时128Hz;
当Timer[7:0]>1时64Hz;当Timer[7:0]=0时,定时器不工作;
TD[1:0]=10b,TI/TP=1,TIE=1,TE=1,配置INT脚输出指定频率的脉冲:当Timer[7:0]=1时64Hz;
当Timer[7:0]>1时64Hz;当Timer[7:0]=0时,定时器不工作;
TD[1:0]=11b,TI/TP=1,TIE=1,TE=1,配置INT脚输出指定频率的脉冲:当Timer[7:0]=1时64Hz;
当Timer[7:0]>1时64Hz;当Timer[7:0]=0时,定时器不工作;
![](https://i-blog.csdnimg.cn/direct/f09e572a6d4746549b8000d3b73a95d8.png)
4)、定时器值寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x0F | Timer[7:0] ||||||||
当TI/TP=0时,TIE=1,当Timer[7:0]递减到0时,TF设置为1,INT脚输出高电平;
TE=1,向下计数器使能;Timer[7:0]为8为向下计数器值;TD[1:0]用来选择时钟源;
向下计数器周期为:(Timer[7:0]+1)/TD[1:0]选择的时钟源;
5)、秒钟寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x02 | VL | Seconds(0~59) |||||||
当VDD电压低于最小允许电压的时候,VL位就会置1,表示时钟异常;如果电压正的话,VL位就会置0;
Seconds表示秒,为BCD格式,范围:0~59;
6)、分钟寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x03 | NC | Minutes(0~59) |||||||
Minutes表示分钟,为BCD格式,范围:0~59;
7)、小时寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x04 | NC | NC | Hours(0~23) ||||||
Hours表示小时,为BCD格式,范围:0~23;
8)、日期寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x05 | NC | NC | Days(0~31) ||||||
Days表示日期,为BCD格式,范围:0~31;
9)、星期寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x06 | NC | NC | NC | NC | NC | Weekdays(0~6) |||
Weekdays表示星期,为BCD格式,范围:0~6;0为星期日,1为星期一,以此类推6就是星期六;
10)、月份寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x07 | C | NC | NC | Months(0~12) |||||
C表示世纪位,C=1表示20XX年;C=0表示19XX年;
Months表示月份,为BCD格式,范围:0~12;
11)、年寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x08 | Years(0~99) ||||||||
Years表示年,为BCD格式,范围:0~99;
12)、闹钟分钟寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x09 | AE_M | Minutes_Alarm(0~59) |||||||
Minutes_Alarm表示具体闹钟分钟,为BCD格式,范围:0~59;
AE_M闹钟分钟使能;AE_M=1,分钟报警不使能;AE_M=0,分钟报警使能;
13)、闹钟小时寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x0A | AE_H | NC | Hours_Alarm(0~23) ||||||
Hours_Alarm表示具体闹钟小时,为BCD格式,范围:0~23;
AE_H闹钟小时使能;AE_H=1,小时报警不使能;AE_H=0,小时报警使能;
14)、闹钟日期寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x0B | AE_D | NC | Days_Alarm(0~31) ||||||
Days_Alarm表示具体闹钟日期,为BCD格式,范围:0~31;
AE_D闹钟日期使能;AE_D=1,日期报警不使能;AE_D=0,日期报警使能;
15)、闹钟星期寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x0C | AE_W | NC | NC | NC | NC | Weekday_Alarms(0~6) |||
Weekdays表示星期,为BCD格式,范围:0~6;0为星期日,1为星期一,以此类推6就是星期六;
Weekday_Alarms表示具体闹钟星期,为BCD格式,范围:0~6;
AE_W闹钟星期使能;AE_W=1,星期报警不使能;AE_W=0,星期报警使能;
![](https://i-blog.csdnimg.cn/direct/16528439436b448886083dbca4adc1b7.png)
16)、CLKOUT引脚控制寄存器
|------|------|------|------|------|------|------|------|------|
| 地址 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| 0x0D | FE | NC | NC | NC | NC | NC | FD[1:0] ||
FE=0,禁止CLKOUT引脚输出高阻抗;FE=1,使能CLKOUT引脚输出指定的时钟频率;
FD[1:0]=00b,CLKOUT引脚输出32.768KHz;
FD[1:0]=01b,CLKOUT引脚输出1.024KHz;
FD[1:0]=10b,CLKOUT引脚输出32Hz;
FD[1:0]=11b,CLKOUT引脚输出1Hz;
5、PCF8563原理图
![](https://i-blog.csdnimg.cn/direct/08ee75ea5475406c91d14bb9580dfbf7.png)
PCF8563读地址为0xA3,PCF8563写地址为0xA2;PCF8563器件地址为0x51;器件地址解释:0x51<<1=0xA2;
PCF8563的I2C接口连接到了STM32MP157的I2C4上,SCL引脚连接到PZ4,SDA连接到PZ5。INT中断引脚连接到PI3。
6、修改设备树
1)、打开"arch/arm/boot/dts/stm32mp15-pinctrl.dtsi"找到"i2c4",内容如下:
i2c4_pins_a: i2c4-0 {
pins {
pinmux = <STM32_PINMUX('Z', 4, AF6)>, /* I2C4_SCL */
<STM32_PINMUX('Z', 5, AF6)>; /* I2C4_SDA */
bias-disable;
drive-open-drain;
slew-rate = <0>;
};
};
i2c4_pins_sleep_a: i2c4-1 {
pins {
pinmux = <STM32_PINMUX('Z', 4, ANALOG)>, /* I2C4_SCL */
<STM32_PINMUX('Z', 5, ANALOG)>; /* I2C4_SDA */
};
};
2)、打开"Documentation/devicetree/bindings/rtc/pcf8563.txt",内容如下:
Philips PCF8563/Epson RTC8564 Real Time Clock
Required properties:
- compatible: Should contain "nxp,pcf8563",
"epson,rtc8564" or
"microcrystal,rv8564"
- reg: I2C address for chip.
Optional property:
-
#clock-cells: Should be 0.
-
clock-output-names:
overwrite the default clock name "pcf8563-clkout"
Example:
pcf8563: pcf8563@51 {
compatible = "nxp,pcf8563";
reg = <0x51>;
#clock-cells = <0>;
};
device {
...
clocks = <&pcf8563>;
...
};
3)、打开"stm32mp157d-atk.dts",添加内容如下(注意:不是在根节点"/"下添加):
&i2c4 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c4_pins_a>;/*pinctrl-0为default模式*/
pinctrl-1 = <&i2c4_pins_sleep_a>;/*pinctrl-1为sleep模式*/
/*设置了两个pinmux模式:pinctrl-0为default模式,pinctrl-1为sleep模式,
系统默认使用default模式。*/
status = "okay";
pcf8563@51{
/*向i2c4添加pcf8563子节点,"@"后面的"51"就是PCF8563的I2C器件地址*/
compatible = "nxp,pcf8563";/*compatible属性值为"nxp,pcf8563"*/
irq_gpio = <&gpioi 3 IRQ_TYPE_EDGE_FALLING>;/*设置中断引脚为PI3,下降沿有效*/
/*查看参考手册"Table 118",EXTI[3]的事件输入号码为3*/
/*中断类型和触发方式为下降沿触发*/
reg = <0x51>;
/*reg属性是设置PCF8563的器件地址0x51*/
/*I2C设备的写地址 = I2C设备地址 << 1,则写器件地址为0xA2
I2C设备的读地址 = (I2C设备地址 << 1) + 1,则读器件地址为0xA3
*/
};
};
7、通过"linux内核图形化配置界面",取消上次实验的"STM32 RTC"
1)、打开终端。
2)、输入"cd linux/atk-mp1/linux/my_linux/linux-5.4.31/回车",切换到"linux/atk-mp1/linux/my_linux/linux-5.4.31/"目录;
3)、输入"make menuconfig回车",打开linux内核图形化配置界面,移动向下光标键至"Device Drivers",见下图:
![](https://i-blog.csdnimg.cn/direct/deb67a4a66a9484fbac151e9be5faf3a.png)
4)、按下回车键,移动向下光标键至"Real Time Clock",见下图:
![](https://i-blog.csdnimg.cn/direct/fabdee926bc34627a7b3d1fbbec9ce14.png)
5)、按下Y键,按下回车键,移动向下光标键至"Philips PCF8563/Epson RTC8564",见下图:
![](https://i-blog.csdnimg.cn/direct/eae610cad26e461294473dc45af2ad28.png)
6)、按下Y键,使能外部RTC的功能PCF8563;
7)、按下回车键,移动向下光标键至"STM32 RTC",见下图:
![](https://i-blog.csdnimg.cn/direct/aac7cfa566714fedbbed634c21e222d2.png)
8)、按"N",取消Linux内核的RTC;
9)、先"保存",按"TAB键"至"Save",按下"回车键",得到下面的界面。
![](https://i-blog.csdnimg.cn/direct/e43355ae26a940478a9e94b8a8e56608.png)
10)、输入"./arch/arm/configs/stm32mp1_atk_defconfig",移动"向下光标键"至"Ok",得到下图:
![](https://i-blog.csdnimg.cn/direct/6b8985f633ab4cde9ac1f35304332efe.png)
11)、按"回车键",保存完成。得到下面的界面。
![](https://i-blog.csdnimg.cn/direct/83d36a662e204b6dba25567a922c688e.png)
12)、按"回车键",退出保存界面。然后按"ESC键",直到得到下面的界面:
![](https://i-blog.csdnimg.cn/direct/7c62ff1ec17149459789403af4f941d3.png)
13)、输入"make stm32mp1_atk_defconfig回车",注意:如果忘记执行,可能再次打开时会发现".config"没有被更新,得到下图:
![](https://i-blog.csdnimg.cn/direct/f98c3783c03641e69f77691680aa684a.png)
8、了解rtc-pcf8563.c
打开"drivers/rtc/rtc-pcf8563.c",程序如下:
#include <linux/clk-provider.h>
#include <linux/i2c.h>
#include <linux/bcd.h>
#include <linux/rtc.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/err.h>
#define PCF8563_REG_ST1 0x00 /*控制/状态寄存器1的的地址为0x00,status */
#define PCF8563_REG_ST2 0x01 /*控制/状态寄存器2的的地址为0x01,status */
#define PCF8563_BIT_AIE (1 << 1)
/*bit1=1;当AIE=0时,闹钟中断不允许;当AIE=1时,闹钟中断允许;*/
#define PCF8563_BIT_AF (1 << 3)
/*bit3=1b;AF为闹钟标志位;若读AF为1,表示建立闹钟标志;若设置AF=0时,清除建立的闹钟标志;*/
#define PCF8563_BITS_ST2_N (7 << 5) /*bit7:5=111b*/
#define PCF8563_REG_SC 0x02 /*秒钟寄存器的的地址为0x02*/
#define PCF8563_REG_MN 0x03 /*分钟寄存器的的地址为0x03*/
#define PCF8563_REG_HR 0x04 /*小时寄存器的的地址为0x04*/
#define PCF8563_REG_DM 0x05 /*日期寄存器的的地址为0x05*/
#define PCF8563_REG_DW 0x06 /*星期寄存器的的地址为0x06*/
#define PCF8563_REG_MO 0x07 /*月份寄存器的的地址为0x07*/
#define PCF8563_REG_YR 0x08 /*年寄存器的的地址为0x08*/
#define PCF8563_REG_AMN 0x09 /*闹钟分钟寄存器的的地址为0x09*/
#define PCF8563_REG_CLKO 0x0D /*CLKOUT引脚控制寄存器的地址为0x0D,clock out */
#define PCF8563_REG_CLKO_FE 0x80
/*FE=1,使能CLKOUT引脚输出指定的时钟频率;clock out enabled */
#define PCF8563_REG_CLKO_F_MASK 0x03 /*用来屏蔽FD[1:0]的位,frequenc mask */
#define PCF8563_REG_CLKO_F_32768HZ 0x00 /*FD[1:0]=00b,CLKOUT引脚输出32.768KHz;*/
#define PCF8563_REG_CLKO_F_1024HZ 0x01 /*FD[1:0]=01b,CLKOUT引脚输出1.024KHz;*/
#define PCF8563_REG_CLKO_F_32HZ 0x02 /*FD[1:0]=10b,CLKOUT引脚输出32Hz;*/
#define PCF8563_REG_CLKO_F_1HZ 0x03 /*FD[1:0]=11b,CLKOUT引脚输出1Hz;*/
#define PCF8563_REG_TMRC 0x0E /*定时器控制寄存器的地址为0x0E,timer control */
#define PCF8563_TMRC_ENABLE BIT(7) /*bit7=1b;TE=1,向下计数器使能;*/
#define PCF8563_TMRC_4096 0 /*TD[1:0]=00b,选择时钟源为4096Hz;*/
#define PCF8563_TMRC_64 1 /*TD[1:0]=01b,选择时钟源为64Hz;*/
#define PCF8563_TMRC_1 2 /*TD[1:0]=10b,选择时钟源为1Hz;*/
#define PCF8563_TMRC_1_60 3 /*TD[1:0]=11b,选择时钟源为1/60Hz;*/
#define PCF8563_TMRC_MASK 3 /*用来屏蔽TD[1:0]的位,mask */
#define PCF8563_REG_TMR 0x0F /*定时器值寄存器,timer */
#define PCF8563_SC_LV 0x80
/*秒钟寄存器的bit7,low voltage */
/*当VDD电压低于最小允许电压的时候,VL位就会置1,表示时钟异常;如果电压正的话,VL位就会置0;*/
#define PCF8563_MO_C 0x80
/*月份寄存器的bit7,用C表示,century */
/*C表示世纪位,C=1表示20XX年;C=0表示19XX年;*/
static struct i2c_driver pcf8563_driver;
/*定义设备数据*/
struct pcf8563 {
struct rtc_device *rtc;/*定义一个rtc设备*/
/*
* The meaning of MO_C bit varies by the chip type.
* From PCF8563 datasheet: this bit is toggled when the years
* register overflows from 99 to 00
* 0 indicates the century is 20xx
* 1 indicates the century is 19xx
* From RTC8564 datasheet: this bit indicates change of
* century. When the year digit data overflows from 99 to 00,
* this bit is set. By presetting it to 0 while still in the
* 20th century, it will be set in year 2000, ...
* There seems no reliable way to know how the system use this
* bit. So let's do it heuristically, assuming we are live in
* 1970...2069.
*/
int c_polarity; /*保存世纪位, 0: MO_C=1 means 19xx, otherwise MO_C=1 means 20xx */
int voltage_low; /*保存低电压检测位,incicates if a low_voltage was detected */
struct i2c_client *client;/*因为PCF8563是采用i2c通讯,所以要定义一个i2c设备*/
#ifdef CONFIG_COMMON_CLK
struct clk_hw clkout_hw;
#endif
};
/*
函数功能: 从PCF8563读取多个寄存器数据
参数client : I2C设备
参数reg : 要读取的寄存器首地址
参数length : 要读取的数据长度
参数buf : 读取到的数据
返回值: 0表示读取成功
*/
static int pcf8563_read_block_data(struct i2c_client *client, unsigned char reg,
unsigned char length, unsigned char *buf)
{
/*声明i2c_msg结构变量msgs*/
struct i2c_msg msgs[] = {
{/* setup read ptr */
.addr = client->addr,/*将PCF8563的i2c地址保存到addr*/
.len = 1, /*发送数据的长度为1*/
.buf = ®, /*读取的寄存器首地址*/
},
{
.addr = client->addr,/*将PCF8563的i2c地址保存到addr*/
.flags = I2C_M_RD, /*标记为读取数据*/
.len = length, /*告诉I2C,要读取的数据长度*/
.buf = buf /*读取数据缓冲区,pointer to msg data*/
},
};
if ((i2c_transfer(client->adapter, msgs, 2)) != 2)
{
/*先发送"PCF8563的I2C地址",接着发送"读取的寄存器首地址",最后读取"该寄存器的数据"*/
/*因为是先写后读,因此消息数量有2个*/
dev_err(&client->dev, "%s: read error\n", __func__);
return -EIO;
}
return 0;
}
/*
函数功能: 向PCF8563多个寄存器写入数据
参数client : I2C设备
参数reg: 要写入的寄存器首地址
参数length : 写入数据的长度
参数buf : 指向待写入数据缓冲区的首地址
返回值: 操作结果
*/
static int pcf8563_write_block_data(struct i2c_client *client,
unsigned char reg, unsigned char length,
unsigned char *buf)
{
int i, err;
for (i = 0; i < length; i++)
{
unsigned char data[2] = { reg + i, buf[i] };
//声明data[2],令data[0]=reg + i;data[1]=buf[i];
err = i2c_master_send(client, data, sizeof(data));
/*
client表示I2C设备对应的i2c_client;
data指向要发送的数据;sizeof(data)表示要发送的数据字节数量
注意:sizeof(data)要小于64Kb,因为i2c_msg结构中的len成员是u16型的;
返回值:非幅值表示发送的字节的数量;负值表示失败;
*/
if (err != sizeof(data)) {
dev_err(&client->dev,
"%s: err=%d addr=%02x, data=%02x\n",
__func__, err, data[0], data[1]);
return -EIO;
}
}
return 0;
}
/*
函数功能:
on=1,配置PCF8563闹钟中断允许;
on=0,配置PCF8563不允许闹钟中断;
*/
static int pcf8563_set_alarm_mode(struct i2c_client *client, bool on)
{
unsigned char buf;
int err;
err = pcf8563_read_block_data(client, PCF8563_REG_ST2, 1, &buf);
/*从PCF8563读"控制/状态寄存器2"中的数据,所读数据保存在buf中*/
/*为修改"控制/状态寄存器2"作准备*/
if (err < 0)
return err;
if (on)
buf |= PCF8563_BIT_AIE;/*令AIE=1时,闹钟中断允许*/
else
buf &= ~PCF8563_BIT_AIE;/*令AIE=0时,不允许闹钟中断*/
buf &= ~(PCF8563_BIT_AF | PCF8563_BITS_ST2_N);
/*令AF=0时,清除建立的闹钟标志,令bit7:5=000b*/
err = pcf8563_write_block_data(client, PCF8563_REG_ST2, 1, &buf);
/*将buf中数据写入PCF8563的控制/状态寄存器2,字节数量为1*/
if (err < 0) {
dev_err(&client->dev, "%s: write error\n", __func__);
return -EIO;
}
return 0;
}
/*
函数功能:
如果en指针为非空指针,则返回配置的"闹钟中断使能位";
如果pen指针为非空指针,则返回"闹钟标志位";
*/
static int pcf8563_get_alarm_mode(struct i2c_client *client, unsigned char *en,
unsigned char *pen)
{
unsigned char buf;
int err;
err = pcf8563_read_block_data(client, PCF8563_REG_ST2, 1, &buf);
/*从PCF8563读"控制/状态寄存器2"中的数据,所读数据保存在buf中*/
if (err)
return err;
if (en)/*en指针为非空指针,要求读"闹钟中断使能位"*/
*en = !!(buf & PCF8563_BIT_AIE);
/*读AIE位:为1时,表示闹钟中断允许;为0时,表示不允许闹钟中断;*/
if (pen)/*pen指针为非空指针,要求"闹钟标志位"*/
*pen = !!(buf & PCF8563_BIT_AF);
/*读AF为闹钟标志位;为1,表示建立闹钟标志;若为0时,表示没有建立的闹钟标志;*/
return 0;
}
/*
函数功能:
如果PCF8563建立闹钟标志,将PCF8563的闹钟事件传递给内核,并配置PCF8563闹钟中断允许;
*/
static irqreturn_t pcf8563_irq(int irq, void *dev_id)
{
struct pcf8563 *pcf8563 = i2c_get_clientdata(dev_id);
/*返回dev_id->dev->driver_data指针,类型为device结构指针*/
int err;
char pending;
err = pcf8563_get_alarm_mode(pcf8563->client, NULL, &pending);
/*如果pending指针为非空指针,则返回"闹钟标志位";*/
if (err)
return IRQ_NONE;
if (pending)/*为1,表示建立闹钟标志;*/
{
rtc_update_irq(pcf8563->rtc, 1, RTC_IRQF | RTC_AF);
/*将PCF8563的闹钟事件(周期性闹钟中断)传递给内核,Pass event to the kernel */
pcf8563_set_alarm_mode(pcf8563->client, 1);
/*on=1,配置PCF8563闹钟中断允许;*/
return IRQ_HANDLED;
}
return IRQ_NONE;
}
/*
* In the routines that deal directly with the pcf8563 hardware, we use
* rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch.
*/
/*
函数功能:
返回值为负值,表示电压过低,没有读到时间和年月日;
返回值为0,表示读到时间和年月日,保存到tm结构中;
*/
static int pcf8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
/*已知i2c_client结构中的dev成员的指针值为dev,
计算出i2c_client型结构变量的首地址*/
struct pcf8563 *pcf8563 = i2c_get_clientdata(client);
/*返回client->dev->driver_data指针,类型为device结构指针*/
unsigned char buf[9];
int err;
err = pcf8563_read_block_data(client, PCF8563_REG_ST1, 9, buf);
/*从PCF8563的"控制/状态寄存器1"开始连续读取9个数据,所读数据保存在buf中*/
if (err)
return err;
if (buf[PCF8563_REG_SC] & PCF8563_SC_LV)
{
/*判断秒钟寄存器的bit7,low voltage */
/*
当VDD电压低于最小允许电压的时候,VL位就会置1,表示时钟异常;
如果电压正的话,VL位就会置0;
*/
pcf8563->voltage_low = 1;/*电压过低*/
dev_err(&client->dev,
"low voltage detected, date/time is not reliable.\n");
return -EINVAL;
}
dev_dbg(&client->dev,
"%s: raw data is st1=%02x, st2=%02x, sec=%02x, min=%02x, hr=%02x, "
"mday=%02x, wday=%02x, mon=%02x, year=%02x\n",
__func__,
buf[0], buf[1], buf[2], buf[3],
buf[4], buf[5], buf[6], buf[7],
buf[8]);
tm->tm_sec = bcd2bin(buf[PCF8563_REG_SC] & 0x7F);/*保存秒*/
tm->tm_min = bcd2bin(buf[PCF8563_REG_MN] & 0x7F);/*保存分钟*/
tm->tm_hour = bcd2bin(buf[PCF8563_REG_HR] & 0x3F); /*保存小时,rtc hr 0-23 */
tm->tm_mday = bcd2bin(buf[PCF8563_REG_DM] & 0x3F);/*保存日期*/
tm->tm_wday = buf[PCF8563_REG_DW] & 0x07;/*保存星期几*/
tm->tm_mon = bcd2bin(buf[PCF8563_REG_MO] & 0x1F) - 1; /*保存月,rtc mn 1-12 */
tm->tm_year = bcd2bin(buf[PCF8563_REG_YR]) + 100;
/*保存年;这里加100,
tm->tm_year=100,表示2000年;
tm->tm_year=99,表示1999年;
*/
/* detect the polarity heuristically. see note above. */
pcf8563->c_polarity = (buf[PCF8563_REG_MO] & PCF8563_MO_C) ?
(tm->tm_year >= 100) : (tm->tm_year < 100);
/*如果世纪位为1,则表示20XX年;pcf8563->c_polarity=1*/
dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
__func__,
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
return 0;
}
/*
函数功能:
将tm中的时间,星期和年月日写入PCF8563;
返回值为0,表示配置成功;
*/
static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
/*已知i2c_client结构中的dev成员的指针值为dev,
计算出i2c_client型结构变量的首地址*/
struct pcf8563 *pcf8563 = i2c_get_clientdata(client);
/*返回client->dev->driver_data指针,类型为device结构指针*/
unsigned char buf[9];
dev_dbg(&client->dev, "%s: secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
__func__,
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
/* hours, minutes and seconds */
buf[PCF8563_REG_SC] = bin2bcd(tm->tm_sec);/*将秒的16进制数据转换为BCD格式*/
buf[PCF8563_REG_MN] = bin2bcd(tm->tm_min);/*将分钟的16进制数据转换为BCD格式*/
buf[PCF8563_REG_HR] = bin2bcd(tm->tm_hour);/*将小时的16进制数据转换为BCD格式*/
buf[PCF8563_REG_DM] = bin2bcd(tm->tm_mday);/*将日期的16进制数据转换为BCD格式*/
/* month, 1 - 12 */
buf[PCF8563_REG_MO] = bin2bcd(tm->tm_mon + 1);
/*将月份的16进制数据转换为BCD格式;
加1,tm->tm_mon=0表示1月
*/
/* year and century */
buf[PCF8563_REG_YR] = bin2bcd(tm->tm_year - 100);
/*
tm->tm_year=100,表示2000年;所以这里减100;
tm->tm_year=99,表示1999年;
*/
if (pcf8563->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year < 100))
buf[PCF8563_REG_MO] |= PCF8563_MO_C;
/*如果世纪为1,则设置"月份寄存器"的bit7=1,否则设置为0*/
buf[PCF8563_REG_DW] = tm->tm_wday & 0x07;/*准备写星期寄存器的数据*/
return pcf8563_write_block_data(client, PCF8563_REG_SC,
9 - PCF8563_REG_SC, buf + PCF8563_REG_SC);
/*将buf中数据写入PCF8563,
字节数量为(9 - PCF8563_REG_SC);
源数据首地址为(buf + PCF8563_REG_SC)
*/
}
#ifdef CONFIG_RTC_INTF_DEV
/*
函数功能:
cmd=RTC_VL_READ;读电压状态;
cmd=RTC_VL_CLR;
如果电压正常;则将读到的时间,星期和年月日保存到tm结构中;
如果电压不正常;则没有读到时间和年月日,设置时间为00:00:00,星期为0,年月日为00-00-00
*/
static int pcf8563_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
{
struct pcf8563 *pcf8563 = i2c_get_clientdata(to_i2c_client(dev));
/*to_i2c_client()已知i2c_client结构中的dev成员的指针值为dev,
计算出i2c_client型结构变量的首地址*/
/*i2c_get_clientdata()返回client->dev->driver_data指针,类型为device结构指针*/
struct rtc_time tm;
switch (cmd) {
case RTC_VL_READ:/*读电压状态*/
if (pcf8563->voltage_low)
dev_info(dev, "low voltage detected, date/time is not reliable.\n");
if ( copy_to_user((void __user *)arg, &pcf8563->voltage_low,sizeof(int)) )
/*将pcf8563->voltage_low中数据拷贝到arg[]中*/
return -EFAULT;
return 0;
case RTC_VL_CLR:/*清除电压过低的信息*/
/*
* Clear the VL bit in the seconds register in case
* the time has not been set already (which would
* have cleared it). This does not really matter
* because of the cached voltage_low value but do it
* anyway for consistency.
*/
if (pcf8563_rtc_read_time(dev, &tm))
/*pcf8563_rtc_read_time()返回值为0,表示读到时间和年月日,保存到tm结构中;*/
/*pcf8563_rtc_read_time()返回值为负值,表示电压过低,没有读到时间和年月日;*/
pcf8563_rtc_set_time(dev, &tm);
/*电压过低,设置时间为00:00:00,星期为0,年月日为00-00-00*/
/* Clear the cached value. */
pcf8563->voltage_low = 0;/*清除"电压过低"的记录*/
return 0;
default:
return -ENOIOCTLCMD;
}
}
#else
#define pcf8563_rtc_ioctl NULL
#endif
/*
函数功能:
将"闹钟分钟寄存器","闹钟小时寄存器","闹钟日期寄存器","闹钟星期寄存器"的数值,保存到tm结构中;
tm->enabled保存的是闹钟使能位;
tm->pending保存的是"闹钟标志位";
*/
static int pcf8563_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *tm)
{
struct i2c_client *client = to_i2c_client(dev);
/*已知i2c_client结构中的dev成员的指针值为dev,
计算出i2c_client型结构变量的首地址*/
unsigned char buf[4];
int err;
err = pcf8563_read_block_data(client, PCF8563_REG_AMN, 4, buf);
/*从PCF8563的"闹钟分钟寄存器"开始连续读取4个数据,所读数据保存在buf中*/
if (err)
return err;
dev_dbg(&client->dev,
"%s: raw data is min=%02x, hr=%02x, mday=%02x, wday=%02x\n",
__func__, buf[0], buf[1], buf[2], buf[3]);
tm->time.tm_sec = 0;
tm->time.tm_min = bcd2bin(buf[0] & 0x7F);/*保存"闹钟分钟寄存器"的数值*/
tm->time.tm_hour = bcd2bin(buf[1] & 0x3F);/*保存"闹钟小时寄存器"的数值*/
tm->time.tm_mday = bcd2bin(buf[2] & 0x3F);/*保存"闹钟日期寄存器"的数值*/
tm->time.tm_wday = bcd2bin(buf[3] & 0x7);/*保存"闹钟星期寄存器"的数值*/
err = pcf8563_get_alarm_mode(client, &tm->enabled, &tm->pending);
/*
如果&tm->enabled指针为非空指针,则返回配置的"闹钟中断使能位";
如果&tm->pending指针为非空指针,则返回"闹钟标志位";
*/
if (err < 0)
return err;
dev_dbg(&client->dev, "%s: tm is mins=%d, hours=%d, mday=%d, wday=%d,"
" enabled=%d, pending=%d\n", __func__, tm->time.tm_min,
tm->time.tm_hour, tm->time.tm_mday, tm->time.tm_wday,
tm->enabled, tm->pending);
return 0;
}
/*
函数功能:
设置闹钟时间;
如果tm->enabled=1,则配置PCF8563闹钟中断允许;
*/
static int pcf8563_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *tm)
{
struct i2c_client *client = to_i2c_client(dev);
/*已知i2c_client结构中的dev成员的指针值为dev,
计算出i2c_client型结构变量的首地址*/
unsigned char buf[4];
int err;
/* The alarm has no seconds, round up to nearest minute */
if (tm->time.tm_sec) {
time64_t alarm_time = rtc_tm_to_time64(&tm->time);
/*将年月日时分秒和星期几转换为64位的alarm_time*/
alarm_time += 60 - tm->time.tm_sec;
rtc_time64_to_tm(alarm_time, &tm->time);
/*将alarm_time转换为年月日时分秒和星期几*/
}
dev_dbg(dev, "%s, min=%d hour=%d wday=%d mday=%d "
"enabled=%d pending=%d\n", __func__,
tm->time.tm_min, tm->time.tm_hour, tm->time.tm_wday,
tm->time.tm_mday, tm->enabled, tm->pending);
buf[0] = bin2bcd(tm->time.tm_min);
buf[1] = bin2bcd(tm->time.tm_hour);
buf[2] = bin2bcd(tm->time.tm_mday);
buf[3] = tm->time.tm_wday & 0x07;
err = pcf8563_write_block_data(client, PCF8563_REG_AMN, 4, buf);
/*将buf中数据写入PCF8563,
字节数量为4;
源数据首地址为"闹钟分钟寄存器"
*/
if (err)
return err;
return pcf8563_set_alarm_mode(client, !!tm->enabled);
/*tm->enabled=1,配置PCF8563闹钟中断允许;*/
}
/*
函数功能:
如果enabled=1,则配置PCF8563闹钟中断允许;
*/
static int pcf8563_irq_enable(struct device *dev, unsigned int enabled)
{
dev_dbg(dev, "%s: en=%d\n", __func__, enabled);
return pcf8563_set_alarm_mode(to_i2c_client(dev), !!enabled);
/*已知i2c_client结构中的dev成员的指针值为dev,
计算出i2c_client型结构变量的首地址*/
}
#ifdef CONFIG_COMMON_CLK
/*
* Handling of the clkout
*/
#define clkout_hw_to_pcf8563(_hw) container_of(_hw, struct pcf8563, clkout_hw)
/*根据pcf8563结构中的clkout_hw成员和这个成员的指针值_hw,
计算出pcf8563型结构变量的首地址*/
/*CLKOUT引脚输出指定的时钟频率*/
static int clkout_rates[] = {
32768,
1024,
32,
1,
};
/*读CLKOUT引脚输出指定的时钟频率*/
static unsigned long pcf8563_clkout_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
/*已知道hw是pcf8563结构中的clkout_hw成员的指针值,
计算出pcf8563型结构变量的首地址*/
struct i2c_client *client = pcf8563->client;
unsigned char buf;
int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);
/*从PCF8563的"CLKOUT引脚控制寄存器"读取1个数据,所读数据保存在buf中*/
if (ret < 0)
return 0;
buf &= PCF8563_REG_CLKO_F_MASK;/*保存CLKOUT引脚控制寄存器的FD[1:0]*/
return clkout_rates[buf];/*返回CLKOUT引脚输出指定的时钟频率*/
}
/*
读CLKOUT引脚输出频率
若1024<rate<=32768,则返回32768;
若32<rate<=1024,则返回1024;
若1<rate<=32,则返回32;
若rate<=1,则返回1;
*/
static long pcf8563_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
int i;
for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
if (clkout_rates[i] <= rate)
return clkout_rates[i];
return 0;
}
/*设置CLKOUT引脚输出指定的时钟频率为rate
rate取值为32768,1024,32,1;
*/
static int pcf8563_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
/*已知hw是pcf8563结构中的clkout_hw成员的指针值,
计算出pcf8563型结构变量的首地址*/
struct i2c_client *client = pcf8563->client;
unsigned char buf;
int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);
/*从PCF8563的"CLKOUT引脚控制寄存器"读取1个数据,所读数据保存在buf中*/
int i;
if (ret < 0)
return ret;
for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
if (clkout_rates[i] == rate) {
buf &= ~PCF8563_REG_CLKO_F_MASK;
buf |= i;
ret = pcf8563_write_block_data(client,PCF8563_REG_CLKO, 1,&buf);
/*将buf中数据写入PCF8563的"CLKOUT引脚控制寄存器",字节数量为1;*/
return ret;
}
return -EINVAL;
}
/*
enable=1,使能CLKOUT引脚输出指定的时钟频率;
enable=0,禁止CLKOUT引脚输出,输出为高阻抗;
*/
static int pcf8563_clkout_control(struct clk_hw *hw, bool enable)
{
struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
/*已知hw是pcf8563结构中的clkout_hw成员的指针值,
计算出pcf8563型结构变量的首地址*/
struct i2c_client *client = pcf8563->client;
unsigned char buf;
int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);
/*从PCF8563的"CLKOUT引脚控制寄存器"读取1个数据,所读数据保存在buf中*/
if (ret < 0)
return ret;
if (enable)
buf |= PCF8563_REG_CLKO_FE;/*FE=1,使能CLKOUT引脚输出指定的时钟频率;*/
else
buf &= ~PCF8563_REG_CLKO_FE;/*FE=0,禁止CLKOUT引脚输出,输出为高阻抗*/
ret = pcf8563_write_block_data(client, PCF8563_REG_CLKO, 1, &buf);
/*将buf中数据写入PCF8563的"CLKOUT引脚控制寄存器",字节数量为1;*/
return ret;
}
/*使能CLKOUT引脚输出指定的时钟频率;*/
static int pcf8563_clkout_prepare(struct clk_hw *hw)
{
return pcf8563_clkout_control(hw, 1);
/*enable=1,使能CLKOUT引脚输出指定的时钟频率;*/
}
/*禁止CLKOUT引脚输出,输出为高阻抗;*/
static void pcf8563_clkout_unprepare(struct clk_hw *hw)
{
pcf8563_clkout_control(hw, 0);
/*enable=0,禁止CLKOUT引脚输出,输出为高阻抗;*/
}
/*读"CLKOUT引脚控制寄存器"的FE位值*/
static int pcf8563_clkout_is_prepared(struct clk_hw *hw)
{
struct pcf8563 *pcf8563 = clkout_hw_to_pcf8563(hw);
/*已知hw是pcf8563结构中的clkout_hw成员的指针值,
计算出pcf8563型结构变量的首地址*/
struct i2c_client *client = pcf8563->client;
unsigned char buf;
int ret = pcf8563_read_block_data(client, PCF8563_REG_CLKO, 1, &buf);
/*从PCF8563的"CLKOUT引脚控制寄存器"读取1个数据,所读数据保存在buf中*/
if (ret < 0)
return ret;
return !!(buf & PCF8563_REG_CLKO_FE);
/*"CLKOUT引脚控制寄存器"的FE位值*/
}
/*管理"CLKOUT引脚输出指定的时钟频率"的函数集合*/
static const struct clk_ops pcf8563_clkout_ops = {
.prepare = pcf8563_clkout_prepare,/*使能CLKOUT引脚输出指定的时钟频率;*/
.unprepare = pcf8563_clkout_unprepare,/*禁止CLKOUT引脚输出,输出为高阻抗;*/
.is_prepared = pcf8563_clkout_is_prepared,/*读"CLKOUT引脚控制寄存器"的FE位值*/
.recalc_rate = pcf8563_clkout_recalc_rate,/*读CLKOUT引脚输出指定的时钟频率*/
.round_rate = pcf8563_clkout_round_rate,/*读CLKOUT引脚输出频率*/
.set_rate = pcf8563_clkout_set_rate,/*设置CLKOUT引脚输出指定的时钟频率为rate*/
};
//在通用CLK框架中注册CLK
static struct clk *pcf8563_clkout_register_clk(struct pcf8563 *pcf8563)
{
struct i2c_client *client = pcf8563->client;
struct device_node *node = client->dev.of_node;
struct clk *clk;
struct clk_init_data init;
int ret;
unsigned char buf;
/* disable the clkout output */
buf = 0;
ret = pcf8563_write_block_data(client, PCF8563_REG_CLKO, 1, &buf);
/*将buf中数据写入PCF8563的"CLKOUT引脚控制寄存器",字节数量为1;*/
if (ret < 0)
return ERR_PTR(ret);
init.name = "pcf8563-clkout";
init.ops = &pcf8563_clkout_ops;/*管理"CLKOUT引脚输出指定的时钟频率"的函数集合*/
init.flags = 0;
init.parent_names = NULL;
init.num_parents = 0;
pcf8563->clkout_hw.init = &init;
/* optional override of the clockname */
of_property_read_string(node, "clock-output-names", &init.name);
//指定的设备节点node
//proname="clock-output-names",给定要读取的属性名字
//out_string=init.name="pcf8563-clkout":返回读取到的属性值
//返回值:0,读取成功,负值,读取失败。
/* register the clock */
clk = devm_clk_register(&client->dev, &pcf8563->clkout_hw);
/*分配一个新的时钟,注册它,并返回一个clk结构体*/
if (!IS_ERR(clk))
of_clk_add_provider(node, of_clk_src_simple_get, clk);
/*为节点注册一个时钟提供商*/
return clk;
}
#endif
/*声明rtc_class_ops结构变量pcf8563_rtc_ops*/
/*它是指向设备的操作函数集合变量*/
static const struct rtc_class_ops pcf8563_rtc_ops = {
.ioctl = pcf8563_rtc_ioctl,
.read_time = pcf8563_rtc_read_time,
.set_time = pcf8563_rtc_set_time,
.read_alarm = pcf8563_rtc_read_alarm,
.set_alarm = pcf8563_rtc_set_alarm,
.alarm_irq_enable = pcf8563_irq_enable,
};
static int pcf8563_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct pcf8563 *pcf8563;
int err;
unsigned char buf;
dev_dbg(&client->dev, "%s\n", __func__);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
/*确定适配器是否支持I2C_FUNC_I2C*/
return -ENODEV;
pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563),
GFP_KERNEL);
/*向内核申请一块内存,当设备驱动程序被卸载时,内存会被自动释放*/
if (!pcf8563)
return -ENOMEM;
i2c_set_clientdata(client, pcf8563);
/*将pcf8563变量的地址绑定client*/
/*就可以通过i2c_get_clientdata(client)获取pcf8563变量指针*/
pcf8563->client = client;
device_set_wakeup_capable(&client->dev, 1);
/* Set timer to lowest frequency to save power (ref Haoyu datasheet) */
buf = PCF8563_TMRC_1_60;//选择时钟源为1/60Hz
err = pcf8563_write_block_data(client, PCF8563_REG_TMRC, 1, &buf);
/*将buf中数据写入PCF8563的"定时器控制寄存器",字节数量为1;*/
if (err < 0) {
dev_err(&client->dev, "%s: write error\n", __func__);
return err;
}
/* Clear flags and disable interrupts */
buf = 0;
err = pcf8563_write_block_data(client, PCF8563_REG_ST2, 1, &buf);
/*将buf中数据写入PCF8563的"控制/状态寄存器2",字节数量为1;*/
if (err < 0) {
dev_err(&client->dev, "%s: write error\n", __func__);
return err;
}
pcf8563->rtc = devm_rtc_allocate_device(&client->dev);
if (IS_ERR(pcf8563->rtc))
return PTR_ERR(pcf8563->rtc);
pcf8563->rtc->ops = &pcf8563_rtc_ops;
/* the pcf8563 alarm only supports a minute accuracy */
pcf8563->rtc->uie_unsupported = 1;
pcf8563->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
pcf8563->rtc->range_max = RTC_TIMESTAMP_END_2099;
pcf8563->rtc->set_start_time = true;
if (client->irq > 0) {
err = devm_request_threaded_irq(&client->dev, client->irq,
NULL, pcf8563_irq,
IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
pcf8563_driver.driver.name, client);
/*RTC的中断服务函数为pcf8563_irq()*/
if (err) {
dev_err(&client->dev, "unable to request IRQ %d\n",
client->irq);
return err;
}
}
err = rtc_register_device(pcf8563->rtc);/*注册RTC类设备*/
if (err)
return err;
#ifdef CONFIG_COMMON_CLK
/* register clk in common clk framework */
pcf8563_clkout_register_clk(pcf8563);/*在通用CLK框架中注册CLK*/
#endif
return 0;
}
/*传统匹配方式ID列表*/
static const struct i2c_device_id pcf8563_id[] = {
{ "pcf8563", 0 },
{ "rtc8564", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, pcf8563_id);
#ifdef CONFIG_OF
/*设备树匹配列表*/
static const struct of_device_id pcf8563_of_match[] = {
{ .compatible = "nxp,pcf8563" },
/*在stm32mp157d-atk.dts设备树文件中,定义"compatible = "zgq,ap3216c"*/
{ .compatible = "epson,rtc8564" },
{ .compatible = "microcrystal,rv8564" },
{
/*这是一个空元素,在编写of_device_id时最后一个元素一定要为空*/
/* Sentinel */
}
};
MODULE_DEVICE_TABLE(of, pcf8563_of_match);
#endif
/*声明i2c_driver结构变量pcf8563_driver,并初始化结构变量*/
static struct i2c_driver pcf8563_driver = {
.driver = {
.name = "rtc-pcf8563",/* 驱动名字,用于和设备匹配 */
.of_match_table = of_match_ptr(pcf8563_of_match),/*设备树匹配表*/
},
.probe = pcf8563_probe,/*platform的probe函数为pcf8563_probe()*/
.id_table = pcf8563_id,/*传统匹配方式ID列表*/
};
module_i2c_driver(pcf8563_driver);
/*
打开"include/linux/device.h"
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
打开"include/linux/i2c.h"
#define module_i2c_driver(__i2c_driver) module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)
因此module_driver(pcf8563_driver, i2c_add_driver, i2c_del_driver)展开后,就是下面的内容:
static int __init pcf8563_driver_init(void)
{
return i2c_add_driver( &(pcf8563_driver) );
}
module_init(pcf8563_driver_init);
static void __exit pcf8563_driver_exit(void)
{
i2c_del_driver(&(pcf8563_driver) );
}
module_exit(pcf8563_driver_exit);
*/
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");//添加作者名字
MODULE_DESCRIPTION("Philips PCF8563/Epson RTC8564 RTC driver");//模块介绍
MODULE_LICENSE("GPL");//LICENSE采用"GPL协议"
9、编译设备树
①打开VSCode中的终端,输入"make uImage dtbs LOADADDR=0XC2000040 -j8回车",执行编译"Image"和"dtbs",并指定装载的起始地址为0XC2000040,j8表示指定采用8线程执行。"make dtbs",用来指定编译设备树。见下图:
②输入"ls arch/arm/boot/uImage -l"
查看是否生成了新的"uImage"文件
③输入"ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l"
查看是否生成了新的"stm32mp157d-atk.dtb"文件
10、拷贝输出的文件:
①输入"cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车",执行文件拷贝,准备烧录到EMMC;
②输入"cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车",执行文件拷贝,准备烧录到EMMC
③输入"cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车",执行文件拷贝,准备从tftp下载;
④输入"cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车",执行文件拷贝,准备从tftp下载;
⑤输入"ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车",查看"/home/zgq/linux/atk-mp1/linux/bootfs/"目录下的所有文件和文件夹
⑥输入"ls -l /home/zgq/linux/tftpboot/回车",查看"/home/zgq/linux/tftpboot/"目录下的所有文件和文件夹
⑦输入"chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车"
给"stm32mp157d-atk.dtb"文件赋予可执行权限
⑧输入"chmod 777 /home/zgq/linux/tftpboot/uImage回车" ,给"uImage"文件赋予可执行权限
⑨输入"ls /home/zgq/linux/tftpboot/ -l回车",查看"/home/zgq/linux/tftpboot/"目录下的所有文件和文件夹
11、测试
①给开发板上电,启动开发板,从网络下载程序;
当系统第一次启动,由于没有设置PCF8563时间,启动过程提示信息如下:
![](https://i-blog.csdnimg.cn/direct/f2edbace74ab4ba8b5b5b73b5024c505.png)
系统已经识别出了PCF8563,但是,提示检测到低电压,日期和时间无效。这是因为我们没有设置时间。
②输入"date -s "2025-02-15 22:53:00"回车",修改当前时间,但还没有写入到STM32MP1内部RTC里面或其他的RTC芯片里面,因此,系统重启以后时间又会丢失。
③输入"date回车",查看时间。
④输入"hwclock -w回车",将当前的系统时间写入到RTC里。
![](https://i-blog.csdnimg.cn/direct/3607324348224732990ed83671b6a22d.png)
⑤重启开发板,读到正确的时间信息,开发板掉电后,PCF8563会继续计时,因为有一个纽扣电池供电。
![](https://i-blog.csdnimg.cn/direct/0f693192b0634027b1a4120c75b53f8f.png)