Linux第107步_Linux之PCF8563实验
使用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”,见下图:

6)、按下Y键,使能外部RTC的功能PCF8563;
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会继续计时,因为有一个纽扣电池供电。

相关文章:
Linux第107步_Linux之PCF8563实验
使用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 seco…...
功能说明并准备静态结构
功能说明并准备静态结构 <template><div class"card-container"><!-- 搜索区域 --><div class"search-container"><span class"search-label">车牌号码:</span><el-input clearable placeho…...
pip 与 conda 的故事
pip 换源 pip 官方源 -i https://pypi.python.org/simple pip 清华源 -i https://pypi.tuna.tsinghua.edu.cn/simple pip 阿里源 -i https://mirrors.aliyun.com/pypi/simple PyTorch 安装 pip3 install torch torchvision torchaudio pip3 install torch torchvision torchaud…...
【05】RUST错误处理
文章目录 错误处理panic代码运行 ResutResult中的一些方法介绍传播错误?运算符 错误处理 建议是尽量用Result由调用者自行决定是否恢复,不恢复也可直接在Err中调用panic。代码分支不可能走的分支可panic。 需要panic的情况: 有害状态&#x…...
[免费]SpringBoot公益众筹爱心捐赠系统【论文+源码+SQL脚本】
大家好,我是老师,看到一个不错的SpringBoot公益众筹爱心捐赠系统,分享下哈。 项目介绍 公益捐助平台的发展背景可以追溯到几十年前,当时人们已经开始通过各种渠道进行公益捐助。随着互联网的普及,本文旨在探讨公益事业…...
算法【动态规划中使用观察优化枚举】
动态规划的问题中,已经写出了记忆化搜索的版本,还要写出严格位置依赖的版本,意义在于不仅可以进行空间压缩优化;关键还在于,很多时候通过进一步观察,可以优化枚举,让时间复杂度更好。优化枚举的…...
ML.Net二元分类
ML.Net二元分类 文章目录 ML.Net二元分类前言项目的创建机器学习模型的创建添加模型选择方案训练环境的选择训练数据的添加训练数据的选择训练数据的格式要预测列的选择模型评估模型的使用总结前言 ML.NET是由Microsoft为.NET开发者平台创建的免费、开源、跨平台的机器学习…...
visutal studio 2022使用qcustomplot基础教程
编译 下载,2.1.1版支持到Qt6.4 。 拷贝qcustomplot.h和qcustomplot.cpp到项目源目录(Qt project)。 在msvc中将它俩加入项目中。 使用Qt6.8,需要修改两处代码: L6779 # if QT_VERSION > QT_VERSION_CHECK(5, 2, …...
本地搭建自己的专属客服之OneApi关联Ollama部署的大模型并创建令牌《下》
这里写目录标题 OneApi1、渠道设置2、令牌创建 配置文件修改修改配置文件docker-compose.yml修改config.json到此结束 上文讲了如何本地docker部署fastGtp,相信大家也都已经部署成功了!!! 今天就说说怎么让他们连接在一起 创建你的…...
c#自动更新-源码
软件维护与升级 修复漏洞和缺陷:软件在使用过程中可能会发现各种漏洞和缺陷,自动更新可以及时推送修复程序,增强软件的稳定性和安全性,避免因漏洞被利用而导致数据泄露、系统崩溃等问题。提升性能:通过自动更新&#x…...
SIP中常见的服务器类型
在SIP(Session Initiation Protocol)网络中,除了B2BUA(Back-to-Back User Agent)、路由代理和媒体服务器外,还有其他类型的服务器。以下是所有类型的服务器及其作用、示例和其他相关信息的表格:…...
【C】初阶数据结构4 -- 双向循环链表
之前学习的单链表相比于顺序表来说,就是其头插和头删的时间复杂度很低,仅为O(1) 且无需扩容;但是对于尾插和尾删来说,由于其需要从首节点开始遍历找到尾节点,所以其复杂度为O(n)。那么有没有一种结构是能使得头插和头删…...
小爱音箱控制手机和电视听歌的尝试
最近买了小爱音箱pro,老婆让我扔了,吃灰多年的旧音箱。当然舍不得,比小爱还贵,刚好还有一台红米手机,能插音箱,为了让音箱更加灵活,买了个2元的蓝牙接收模块Type-c供电3.5接口。这就是本次尝试起…...
Kotlin Lambda
Kotlin Lambda 在探索Kotlin Lambda之前,我们先回顾下Java中的Lambda表达式,Java 的 Lambda 表达式是 Java 8 引入的一项强大的功能,它使得函数式编程风格的代码更加简洁和易于理解。Lambda 表达式允许你以一种更简洁的方式表示实现接口&…...
动态库与静态库:深入解析与应用
在软件开发中,库(Library)是预编译的代码集合,用于在多个程序之间共享功能。根据链接方式的不同,库主要分为两种类型:静态库(Static Library) 和 动态库(Dynamic Library…...
List对象进行排序
目录 一、List对象中某个值进行排序 代码示例 注意事项 二、List.sort 和 Collections.sort 异同 1. 方法所属 2. 使用方式 3. 是否修改原列表 4. 泛型支持 5. 性能 6. 适用场景 7. 示例代码对比 使用 testList.sort 使用 Collections.sort 8. 总结 三、为对象多…...
Java 设计模式之备忘录模式
文章目录 Java 设计模式之备忘录模式概述UML代码实现 Java 设计模式之备忘录模式 概述 备忘录(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。方便对该对象恢复到原先保存的状态。 UML Originnato…...
vue3搭建实战项目笔记二
vue3搭建实战项目笔记二 2.1.git管理项目2.2.隐藏tabBar栏2.2.1 方案一:在路由元信息中设置一个参数是否显示tabBar2.2.2 方案二:通过全局设置相对定位样式 2.3.项目里封装axios2.3.1 发送网络请求的两种做法2.3.2 封装axios并发送网络请求2.3.2.1 对axi…...
【原创】解决vue-element-plus-admin无法实现下拉框动态控制表单功能,动态显隐输入框
前言 目前使用vue-element-plus-admin想要做一个系统定时任务功能,可以选择不同的定时任务类型,比如使用cron表达式、周期执行、指定时间执行等。每种类型对应不同的输入框,需要动态显隐输入框才行,但是这个vue-element-plus-adm…...
大疆无人机需要的kml文件如何制作kml导出(大疆KML文件)
大疆无人机需要的轨迹kml文件,是一种专门的格式,这个kml里面只有轨迹点,其它的属性信息都不需要。 BigemapPro提供了专门的大疆格式输出, 软件这里下载 www.bigemap.com 安装后,kml导入如下图: 然后选择…...
前端知识速记--css篇:CSS3中的常见动画及实现方式
前端知识速记–css篇:CSS3中的常见动画及实现方式 常见的CSS3动画 1. 过渡 (Transitions) 过渡是一种非常简单的动画效果,允许你在元素的状态变更时平滑过渡到新状态。 语法格式: transition: property duration timing-function delay;…...
YOLOV8的学习记录(二) yolo8的几个内置模型简介
YOLOv8 是一个多功能的计算机视觉框架,支持多种任务,包括分类(Classify)、检测(Detect)、旋转目标检测(OBB)、姿态估计(Pose)、实例分割(Segment&…...
免费deepseek的API获取教程及将API接入word或WPS中
免费deepseek的API获取教程: 1 https://cloud.siliconflow.cn/中注册时填写邀请码:GAejkK6X即可获取2000 万 Tokens; 2 按照图中步骤进行操作 将API接入word或WPS中 1 打开一个word,文件-选项-自定义功能区-勾选开发工具-左侧的信任中心-信任中心设置…...
Windows操作系统部署Tomcat详细讲解
Tomcat是一个开源的Java Servlet容器,用于处理Java Web应用程序的请求和响应。以下是关于Tomcat的用法大全: 一、安装Tomcat 下载 访问Apache Tomcat官方网站(https://tomcat.apache.org/),根据你的操作系统…...
深入解析A2DP v1.4协议:蓝牙高质量音频传输的技术与实现
1. A2DP概述 A2DP(Advanced Audio Distribution Profile)是一种高质量音频流媒体协议,旨在实现高质量音频内容的分发,通常用于通过蓝牙设备传输音频数据,例如将音乐从便携式播放器传输到耳机或扬声器。与传统的蓝牙语…...
(三)Axure制作转动的唱片
效果图 属性: 图标库:iconfont-阿里巴巴矢量图标库 方形图片转为圆角图片,裁剪,然后加圆角, 唱片和底图是两个图片,点击播放,唱片在旋转。 主要是播放按钮和停止按钮,两个动态面板…...
VueRouter 实例
分析下列代码 const router new VueRouter({mode:history,routes }) 1.const router new VueRouter({ ... })用来创建一个 Vue Router 实例,用于管理 Vue.js 应用的路由。2.mode: history: 作用:启用 HTML5 History 模式,去除…...
Docker 镜像标签使用
写在前面 当使用命令 docker pull mysql 拉取镜像时,其实等价于如下命令 docker pull mysql:latest latest 是默认的标签,字面上理解为最新版本的镜像,实质上 latest 只是镜像的标签名称,跟具体某个版本号地位一样,…...
ASP.NET Core SixLabors.ImageSharp 位图图像创建和下载
从 MVC 控制器内部创建位图图像并将其发送到浏览器;用 C# 编写并与 Linux 和 Windows 服务器兼容。 使用从 ASP.NET MVC 中的控制器下载任何文件类型File。 此示例创建一个位图 (jpeg) 并将其发送到浏览器。它需要 NuGet 包SixLabors.ImageSharp v1.0.4。 另请参…...
蓝桥杯篇---超声波距离测量频率测量
文章目录 简介第一部分:超声波的简介工作原理1.发射超声波2.接收反射波3.计算时间差4.计算距离 硬件连接1.Trig2.Echo 示例代码代码说明注意事项1.声速2.延时精度3.硬件连接 第二部分:频率测量简介频率测量原理1.信号输入2.计数3.计算频率 硬件连接示例代…...
