使用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时,定时器不工作;
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,星期报警使能;
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原理图
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”,见下图:
4)、按下回车键,移动向下光标键至“Real Time Clock”,见下图:
5)、按下Y键,按下回车键,移动向下光标键至“Philips PCF8563/Epson RTC8564”,见下图:
7)、按下回车键,移动向下光标键至“STM32 RTC”,见下图:
8)、按“N”,取消Linux内核的RTC;
9)、先“保存”,按“TAB键”至“Save”,按下“回车键”,得到下面的界面。
10)、输入“./arch/arm/configs/stm32mp1_atk_defconfig”,移动“向下光标键”至“Ok”,得到下图:
11)、按“回车键”,保存完成。得到下面的界面。
12)、按“回车键”,退出保存界面。然后按“ESC键”,直到得到下面的界面:
13)、输入“make stm32mp1_atk_defconfig回车”,注意:如果忘记执行,可能再次打开时会发现“.config”没有被更新,得到下图:
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_CLKstruct 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时,闹钟中断允许*/elsebuf &= ~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引脚输出指定的时钟频率;*/elsebuf &= ~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/60Hzerr = 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*/
#endifreturn 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时间,启动过程提示信息如下:
系统已经识别出了PCF8563,但是,提示检测到低电压,日期和时间无效。这是因为我们没有设置时间。
②输入“date -s "2025-02-15 22:53:00"回车”,修改当前时间,但还没有写入到STM32MP1内部RTC里面或其他的RTC芯片里面,因此,系统重启以后时间又会丢失。
③输入“date回车”,查看时间。
④输入“hwclock -w回车”,将当前的系统时间写入到RTC里。
⑤重启开发板,读到正确的时间信息,开发板掉电后,PCF8563会继续计时,因为有一个纽扣电池供电。