【正点原子Linux连载】第二十九章 Linux RTC驱动实验摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第二十九章 Linux RTC驱动实验

RTC也就是实时时钟,用于记录当前系统时间,对于Linux系统而言时间是非常重要的,就和我们使用Windows电脑或手机查看时间一样,我们在使用Linux设备的时候也需要查看时间。本章我们就来学习一下如何编写Linux下的RTC驱动程序。

29.1 Linux内核RTC驱动简介
RTC设备驱动是一个标准的字符设备驱动,应用程序通过open、release、read、write和ioctl等函数完成对RTC设备的操作,本章我们主要学习如何使用RK3568核心板上的RK809自带的RTC外设。
Linux内核将RTC设备抽象为rtc_device结构体,因此RTC设备驱动就是申请并初始化rtc_device,最后将rtc_device注册到Linux内核里面,这样Linux内核就有一个RTC设备的。至于RTC设备的操作肯定是用一个操作集合(结构体)来表示的,我们先来看一下rtc_device构体,此结构体定义在include/linux/rtc.h文件中,结构体内容如下(删除条件编译):
示例代码29.1.1 rtc_device结构体

100 struct rtc_device {
101     struct device dev;                  	/*  设备          	*/
102     struct module *owner;
103
104     int id;                             		/* ID             	*/
105
106     const struct rtc_class_ops *ops;  	/* RTC设备底层操作函数 */
107     struct mutex ops_lock;
108
109     struct cdev char_dev;               	/* 字符设备        	*/
110     unsigned long flags;
111
112     unsigned long irq_data;
113     spinlock_t irq_lock;
114     wait_queue_head_t irq_queue;
115     struct fasync_struct *async_queue;
116
117     int irq_freq;
118     int max_user_freq;
119
120     struct timerqueue_head timerqueue;
121     struct rtc_timer aie_timer;
122     struct rtc_timer uie_rtctimer;
123     struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
124     int pie_enabled;
125     struct work_struct irqwork;
126     /* Some hardware can't support UIE mode */
127     int uie_unsupported;
......
159 };
我们需要重点关注的是ops成员变量,这是一个rtc_class_ops类型的指针变量,rtc_class_ops为RTC设备的最底层操作函数集合,包括从RTC设备中读取时间、向RTC设备写入新的时间值等。因此,rtc_class_ops是需要用户根据所使用的RTC设备编写的,此结构体定义在include/linux/rtc.h文件中,内容如下:

示例代码29.1.2 rtc_class_ops 结构体

75  struct rtc_class_ops {
76      int (*ioctl)(struct device *, unsigned int, unsigned long);
77      int (*read_time)(struct device *, struct rtc_time *);
78      int (*set_time)(struct device *, struct rtc_time *);
79      int (*read_alarm)(struct device *, struct rtc_wkalrm *);
80      int (*set_alarm)(struct device *, struct rtc_wkalrm *);
81      int (*proc)(struct device *, struct seq_file *);
82      int (*alarm_irq_enable)(struct device *, unsigned int enabled);
83      int (*read_offset)(struct device *, long *offset);
84      int (*set_offset)(struct device *, long offset);
85  };
看名字就知道rtc_class_ops操作集合中的这些函数是做什么的了,但是我们要注意,rtc_class_ops中的这些函数只是最底层的RTC设备操作函数,并不是提供给应用层的file_operations函数操作集。RTC是个字符设备,那么肯定有字符设备的file_operations函数操作集,Linux内核提供了一个RTC通用字符设备驱动文件,文件名为drivers/rtc/rtc-dev.c,rtc-dev.c文件提供了所有RTC设备共用的file_operations函数操作集,如下所示:

示例代码29.1.3 RTC通用 file_operations操作集

431 static const struct file_operations rtc_dev_fops = {
432     .owner   	= THIS_MODULE,
433     .llseek  	= no_llseek,
434     .read     	= rtc_dev_read,
435     .poll     	= rtc_dev_poll,
436     .unlocked_ioctl = rtc_dev_ioctl,
437     .open     	= rtc_dev_open,
438     .release 	= rtc_dev_release,
439     .fasync   	= rtc_dev_fasync,
440 };

看到示例代码29.1.3是不是很熟悉了,标准的字符设备操作集。应用程序可以通过ioctl函数来设置/读取时间、设置/读取闹钟的操作,对应的rtc_dev_ioctl函数就会执行。 rtc_dev_ioctl最终会通过操作rtc_class_ops中的read_time、set_time等函数来对具体RTC设备的读写操作。我们简单来看一下rtc_dev_ioctl函数,函数内容如下(有省略):
示例代码29.1.4 rtc_dev_ioctl函数代码段

202 static long rtc_dev_ioctl(struct file *file,
203               unsigned int cmd, unsigned long arg)
204 {
205     int err = 0;
206     struct rtc_device *rtc = file->private_data;
207     const struct rtc_class_ops *ops = rtc->ops;
208     struct rtc_time tm;
209     struct rtc_wkalrm alarm;
210     void __user *uarg = (void __user *)arg;
211
212     err = mutex_lock_interruptible(&rtc->ops_lock);
213     if (err)
214         return err;
......
253     switch (cmd) {
......
317     case RTC_RD_TIME:       /* 读取时间 */
318         mutex_unlock(&rtc->ops_lock);
319
320         err = rtc_read_time(rtc, &tm);
321         if (err < 0)
322             return err;
323
324         if (copy_to_user(uarg, &tm, sizeof(tm)))
325             err = -EFAULT;
326         return err;
327
328     case RTC_SET_TIME:      /* 设置时间 */
329         mutex_unlock(&rtc->ops_lock);
330
331         if (copy_from_user(&tm, uarg, sizeof(tm)))
332             return -EFAULT;
333
334         return rtc_set_time(rtc, &tm);
.....
385     default:
386         /* Finally try the driver's ioctl interface */
387         if (ops->ioctl) {
388             err = ops->ioctl(rtc->dev.parent, cmd, arg);
389             if (err == -ENOIOCTLCMD)
390                 err = -ENOTTY;
391         } else {
392             err = -ENOTTY;
393         }
394         break;
395     }
396
397 done:
398     mutex_unlock(&rtc->ops_lock);
399     return err;
400 }

第317行,RTC_RD_TIME为时间读取命令。
第320行,如果是读取时间命令的话就调用rtc_read_time函数获取当前RTC时钟, rtc_read_time会调用__rtc_read_time函数,__rtc_read_time函数内容如下:
示例代码29.1.5 __rtc_read_time函数代码段

84 static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
85 {
86     int err;
87 
88     if (!rtc->ops) {
89         err = -ENODEV;
90     } else if (!rtc->ops->read_time) {
91         err = -EINVAL;
92     } else {
93         memset(tm, 0, sizeof(struct rtc_time));
94         err = rtc->ops->read_time(rtc->dev.parent, tm);
95         if (err < 0) {
96             dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",
97                 err);
98             return err;
99         }
100 
101         rtc_add_offset(rtc, tm);
102 
103         err = rtc_valid_tm(tm);
104         if (err < 0)
105             dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");
106     }
107     return err;
108 }

从第94行可以看出,__rtc_read_time函数会通过调用rtc_class_ops中的read_time成员变量来从RTC设备中获取当前时间。rtc_dev_ioctl函数对其他的命令处理都是类似的,比如RTC_ALM_READ命令会通过rtc_read_alarm函数获取到闹钟值,而rtc_read_alarm函数经过层层调用,最终会调用rtc_class_ops中的read_alarm函数来获取闹钟值。
至此,Linux内核中RTC驱动调用流程就很清晰了,如图29.1.1所示:
在这里插入图片描述

图29.1.1 Linux RTC驱动调用流程
当rtc_class_ops准备好以后需要将其注册到Linux内核中,这里我们可以使用rtc_device_register函数完成注册工作。此函数会申请一个rtc_device并且初始化这个rtc_device,最后向调用者返回这个rtc_device,此函数原型如下:

struct rtc_device *rtc_device_register(const char 				*name, 
							  struct device 			*dev,
							  const struct rtc_class_ops 	*ops,
							  struct module 			*owner)

函数参数和返回值含义如下:
name:设备名字。
dev:设备。
ops:RTC底层驱动函数集。
owner:驱动模块拥有者。
返回值:注册成功的话就返回rtc_device,错误的话会返回一个负值。
当卸载RTC驱动的时候需要调用rtc_device_unregister函数来注销注册的rtc_device,函数原型如下:
void rtc_device_unregister(struct rtc_device *rtc)
函数参数和返回值含义如下:
rtc:要删除的rtc_device。
返回值:无。
还有另外一对rtc_device注册函数devm_rtc_device_register和devm_rtc_device_unregister,分别为注册和注销rtc_device。
29.2 ATK-DLRK3568核心板RTC驱动分析
先直接告诉大家,RK3568的RTC驱动我们不用自己编写,因为RK已经写好了。其实对于大多数的SOC来讲,内部RTC驱动都不需要我们去编写,半导体厂商会编写好。但是这不代表我们就偷懒了,虽然不用编写RTC驱动,但是我们得看一下这些原厂是怎么编写RTC驱动的。
ATK-DLRK3568核心板有一个电源管理芯片RK809挂载在i2c0上。RK809 是一款高性能 PMIC,RK809 集成 5 个大电流 DCDC、9 个 LDO、2 个 开关SWITCH、 1个RTC、1个 高性能CODEC、可调上电时序等功能。
分析驱动,先从设备树入手,打开arch/arm64/boot/dts/rockchip/rk3568-evb.dtsi,在里面找到如下rtc设备节点,节点内容如下所示:
示例代码29.2.1 rk3568-evb.dtsi文件pmic节点

1121 rk809: pmic@20 {
1122         compatible = "rockchip,rk809";
1123         reg = <0x20>;
1124         interrupt-parent = <&gpio0>;
1125         interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
1126 
1127         pinctrl-names = "default", "pmic-sleep",
1128                         "pmic-power-off", "pmic-reset";
1129         pinctrl-0 = <&pmic_int>;
1130         pinctrl-1 = <&soc_slppin_slp>, <&rk817_slppin_slp>;
1131         pinctrl-2 = <&soc_slppin_gpio>, <&rk817_slppin_pwrdn>;
1132         pinctrl-3 = <&soc_slppin_gpio>, <&rk817_slppin_rst>;
1133 
1134         rockchip,system-power-controller;
1135         wakeup-source;
1136         #clock-cells = <1>;
1137         clock-output-names = "rk808-clkout1", "rk808-clkout2";
1138         //fb-inner-reg-idxs = <2>;
1139         /* 1: rst regs (default in codes), 0: rst the pmic */
1140         pmic-reset-func = <0>;
1141         /* not save the PMIC_POWER_EN register in uboot */
1142         not-save-power-en = <1>;
1143 
1144         vcc1-supply = <&vcc3v3_sys>;
1145         vcc2-supply = <&vcc3v3_sys>;
1146         vcc3-supply = <&vcc3v3_sys>;
1147         vcc4-supply = <&vcc3v3_sys>;
1148         vcc5-supply = <&vcc3v3_sys>;
1149         vcc6-supply = <&vcc3v3_sys>;
......
};
第1122行设置兼容属性compatible的值为“rockchip,rk809”,因此在Linux内核源码中搜索此字符串即可找到对应的驱动文件,此文件为drivers/mfd/rk808.c,在drivers/mfd/rk808.c文件中找到如下所示内容:

示例代码29.2.2 设备 platform驱动框架

1178 static const struct of_device_id rk808_of_match[] = {
1179         { .compatible = "rockchip,rk805" },
1180         { .compatible = "rockchip,rk808" },
1181         { .compatible = "rockchip,rk809" },
1182         { .compatible = "rockchip,rk816" },
1183         { .compatible = "rockchip,rk817" },
1184         { .compatible = "rockchip,rk818" },
1185         { },
1186 };
1187 MODULE_DEVICE_TABLE(of, rk808_of_match);
......
1571 static struct i2c_driver rk808_i2c_driver = {
1572         .driver = {
1573                 .name = "rk808",
1574                 .of_match_table = rk808_of_match,
1575                 .pm = &rk8xx_pm_ops,
1576         },
1577         .probe    = rk808_probe,
1578         .remove   = rk808_remove,
1579 };
第1179~1184行,设备树ID表。第1181行,刚好有一个compatible属性和设备树的pmic的compatible属性值一样,所以pmic设备节点会和此驱动匹配。
第1571~1579行,标准的platform驱动框架,当设备和驱动匹配成功以后rk808_probe函数就会执行,我们来看一下rk808_probe函数,函数内容如下(有省略):

示例代码29.2.3 rk808_probe函数代码段

1189 static int rk808_probe(struct i2c_client *client,
1190                        const struct i2c_device_id *id)
1191 {
1192         struct device_node *np = client->dev.of_node;
1193         struct rk808 *rk808;
1194         const struct rk808_reg_data *pre_init_reg;
1195         const struct regmap_irq_chip *battery_irq_chip = NULL;
1196         const struct mfd_cell *cells;
1197         unsigned char pmic_id_msb, pmic_id_lsb;
1198         u8 on_source = 0, off_source = 0;
1199         unsigned int on, off;
1200         int pm_off = 0, msb, lsb;
1201         int nr_pre_init_regs;
1202         int nr_cells;
1203         int ret;
1204         int i;
......
1212 
1213         if (of_device_is_compatible(np, "rockchip,rk817") ||
1214             of_device_is_compatible(np, "rockchip,rk809")) {
1215                 pmic_id_msb = RK817_ID_MSB;
1216                 pmic_id_lsb = RK817_ID_LSB;
1217         } else {
1218                 pmic_id_msb = RK808_ID_MSB;
1219                 pmic_id_lsb = RK808_ID_LSB;
1220         }
1221 
1222         /* Read chip variant */
1223         msb = i2c_smbus_read_byte_data(client, pmic_id_msb);
1224         if (msb < 0) {
1225                 dev_err(&client->dev, "failed to read the chip id at 0x%x\n",
1226                         RK808_ID_MSB);
1227                 return msb;
1228         }
1229 
1230         lsb = i2c_smbus_read_byte_data(client, pmic_id_lsb);
1231         if (lsb < 0) {
1232                 dev_err(&client->dev, "failed to read the chip id at 0x%x\n",
1233                         RK808_ID_LSB);
1234                 return lsb;
1235         }
1236 
1237         rk808->variant = ((msb << 8) | lsb) & RK8XX_ID_MSK;
1238         dev_info(&client->dev, "chip id: 0x%x\n", (unsigned int)rk808->variant);
1239 
1240         switch (rk808->variant) {
1241         case RK805_ID:
1242                 rk808->regmap_cfg = &rk805_regmap_config;
1243                 rk808->regmap_irq_chip = &rk805_irq_chip;
1244                 pre_init_reg = rk805_pre_init_reg;
1245                 nr_pre_init_regs = ARRAY_SIZE(rk805_pre_init_reg);
1246                 cells = rk805s;
1247                 nr_cells = ARRAY_SIZE(rk805s);
1248                 on_source = RK805_ON_SOURCE_REG;
1249                 off_source = RK805_OFF_SOURCE_REG;
1250                 suspend_reg = rk805_suspend_reg;
1251                 suspend_reg_num = ARRAY_SIZE(rk805_suspend_reg);
1252                 resume_reg = rk805_resume_reg;
1253                 resume_reg_num = ARRAY_SIZE(rk805_resume_reg);
1254                 rk808->pm_pwroff_fn = rk805_device_shutdown;
1255                 rk808->pm_pwroff_prep_fn = rk805_device_shutdown_prepare;
1256                 break;
1257         case RK808_ID:
1258                 rk808->regmap_cfg = &rk808_regmap_config;
1259                 rk808->regmap_irq_chip = &rk808_irq_chip;
1260                 pre_init_reg = rk808_pre_init_reg;
1261                 nr_pre_init_regs = ARRAY_SIZE(rk808_pre_init_reg);
1262                 cells = rk808s;
1263                 nr_cells = ARRAY_SIZE(rk808s);
1264                 rk808->pm_pwroff_fn = rk808_device_shutdown;
1265                 break;
1266         case RK816_ID:
1267                 rk808->regmap_cfg = &rk816_regmap_config;
1268                 rk808->regmap_irq_chip = &rk816_irq_chip;
1269                 battery_irq_chip = &rk816_battery_irq_chip;
1270                 pre_init_reg = rk816_pre_init_reg;
1271                 nr_pre_init_regs = ARRAY_SIZE(rk816_pre_init_reg);
1272                 cells = rk816s;
1273                 nr_cells = ARRAY_SIZE(rk816s);
1274                 on_source = RK816_ON_SOURCE_REG;
1275                 off_source = RK816_OFF_SOURCE_REG;
1276                 suspend_reg = rk816_suspend_reg;
1277                 suspend_reg_num = ARRAY_SIZE(rk816_suspend_reg);
1278                 resume_reg = rk816_resume_reg;
1279                 resume_reg_num = ARRAY_SIZE(rk816_resume_reg);
1280                 rk808->pm_pwroff_fn = rk816_device_shutdown;
1281                 break;
1282         case RK818_ID:
1283                 rk808->regmap_cfg = &rk818_regmap_config;
1284                 rk808->regmap_irq_chip = &rk818_irq_chip;
1285                 pre_init_reg = rk818_pre_init_reg;
1286                 nr_pre_init_regs = ARRAY_SIZE(rk818_pre_init_reg);
1287                 cells = rk818s;
1288                 nr_cells = ARRAY_SIZE(rk818s);
1289                 on_source = RK818_ON_SOURCE_REG;
1290                 off_source = RK818_OFF_SOURCE_REG;
1291                 suspend_reg = rk818_suspend_reg;
1292                 suspend_reg_num = ARRAY_SIZE(rk818_suspend_reg);
1293                 resume_reg = rk818_resume_reg;
1294                 resume_reg_num = ARRAY_SIZE(rk818_resume_reg);
1295                 rk808->pm_pwroff_fn = rk818_device_shutdown;
1296                 break;
1297         case RK809_ID:
1298         case RK817_ID:
1299                 rk808->regmap_cfg = &rk817_regmap_config;
1300                 rk808->regmap_irq_chip = &rk817_irq_chip;
1301                 pre_init_reg = rk817_pre_init_reg;
1302                 nr_pre_init_regs = ARRAY_SIZE(rk817_pre_init_reg);
1303                 cells = rk817s;
1304                 nr_cells = ARRAY_SIZE(rk817s);
1305                 on_source = RK817_ON_SOURCE_REG;
1306                 off_source = RK817_OFF_SOURCE_REG;
1307                 rk808->pm_pwroff_prep_fn = rk817_shutdown_prepare;
1308                 of_property_prepare_fn = rk817_of_property_prepare;
1309                 pinctrl_init = rk817_pinctrl_init;
1310                 break;
1311         default:
1312                 dev_err(&client->dev, "Unsupported RK8XX ID %lu\n",
1313                         rk808->variant);
1314                 return -EINVAL;
1315         }
1316 
......
1391 
1392         ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
1393                               cells, nr_cells, NULL, 0,
1394                               regmap_irq_get_domain(rk808->irq_data));
1395         if (ret) {
1396                 dev_err(&client->dev, "failed to add MFD devices %d\n", ret);
1397                 goto err_irq;
1398         }
......
1429 }

1213~1214行,判断compatible是否为“rockchip,rk809”则设置读取对应的寄存器。
1240行,判断读取出来的ID,然后做相应的设置。
1297行,判断是否匹配RK809芯片,若匹配,RK817S芯片的不同功能单元(MFD cell)那么赋值为rk817s静态结构体数组。
1382~1394行,这里添加了平台设备,rtc设备也是在这个devm_mfd_add_devices函数里添加的。
rk817s静态结构体数组内容如下所示:
示例代码29.2.4 rk817s结构体数组

 263 static const struct mfd_cell rk817s[] = {
 264         { .name = "rk808-clkout",},
 265         { .name = "rk808-regulator",},
 266         { .name = "rk817-battery", .of_compatible = "rk817,battery", },
 267         { .name = "rk817-charger", .of_compatible = "rk817,charger", },
 268         {
 269                 .name = "rk805-pwrkey",
 270                 .num_resources = ARRAY_SIZE(rk817_pwrkey_resources),
 271                 .resources = &rk817_pwrkey_resources[0],
 272         },
 273         {
 274                 .name = "rk808-rtc",
 275                 .num_resources = ARRAY_SIZE(rk817_rtc_resources),
 276                 .resources = &rk817_rtc_resources[0],
 277         },
 278         {
 279                 .name = "rk817-codec",
 280                 .of_compatible = "rockchip,rk817-codec",
 281         },
 282 };

通过定义不同的功能单元和相关的资源,可以将这些功能单元配置到RK817S芯片的MFD子系统中,以实现各种功能,如时钟输出、电源管理、电池管理、充电管理和电源按键等。可以看到第274行就有“rk808-rtc”。 platform驱动的name字段相同,否则的话设备就无法匹配到对应的驱动。
平台设备已经添加了,那么我们要找到“rk808-rtc”的驱动实现。打开drivers/rtc/rtc-rk808.c文件,平台设备name字段与平台驱动name字段匹配,那么rk808_rtc_probe才会执行。
示例代码29.2.5 rk817s结构体数组

512 static struct platform_driver rk808_rtc_driver = {
513         .probe = rk808_rtc_probe,
514         .driver = {
515                 .name = "rk808-rtc",
516                 .pm = &rk808_rtc_pm_ops,
517         },
518 };
	rk808_rtc_probe函数内容如下:
417 static int rk808_rtc_probe(struct platform_device *pdev)
418 {
419         struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent);
420         struct rk808_rtc *rk808_rtc;
421         struct device_node *np;
422         int ret;
423 
424         switch (rk808->variant) {
425         case RK805_ID:
426         case RK808_ID:
427         case RK816_ID:
428         case RK818_ID:
429                 np = of_get_child_by_name(pdev->dev.parent->of_node, "rtc");
430                 if (np && !of_device_is_available(np)) {
431                         dev_info(&pdev->dev, "device is disabled\n");
432                         return -EINVAL;
433                 }
434                 break;
435         default:
436                 break;
437         }
438 
439         rk808_rtc = devm_kzalloc(&pdev->dev, sizeof(*rk808_rtc), GFP_KERNEL);
440         if (rk808_rtc == NULL)
441                 return -ENOMEM;
442 
443         switch (rk808->variant) {
444         case RK808_ID:
445         case RK818_ID:
446                 rk808_rtc->creg = &rk808_creg;
447                 rk808_rtc->flag |= RTC_NEED_TRANSITIONS;
448                 break;
449         case RK805_ID:
450         case RK816_ID:
451                 rk808_rtc->creg = &rk808_creg;
452                 break;
453         case RK809_ID:
454         case RK817_ID:
455                 rk808_rtc->creg = &rk817_creg;
456                 break;
457         default:
458                 rk808_rtc->creg = &rk808_creg;
459                 break;
460         }
461         platform_set_drvdata(pdev, rk808_rtc);
462         rk808_rtc->rk808 = rk808;
463 
464         /* start rtc running by default, and use shadowed timer. */
465         ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,
466                                  BIT_RTC_CTRL_REG_STOP_RTC_M |
467                                  BIT_RTC_CTRL_REG_RTC_READSEL_M,
468                                  BIT_RTC_CTRL_REG_RTC_READSEL_M);
469         if (ret) {
470                 dev_err(&pdev->dev,
471                         "Failed to update RTC control: %d\n", ret);
472                 return ret;
473         }
474 
475         ret = regmap_write(rk808->regmap, rk808_rtc->creg->status_reg,
476                            RTC_STATUS_MASK);
477         if (ret) {
478                 dev_err(&pdev->dev,
479                         "Failed to write RTC status: %d\n", ret);
480                         return ret;
481         }
482 
483         device_init_wakeup(&pdev->dev, 1);
484 
485         rk808_rtc->rtc = devm_rtc_allocate_device(&pdev->dev);
486         if (IS_ERR(rk808_rtc->rtc))
487                 return PTR_ERR(rk808_rtc->rtc);
488 
489         rk808_rtc->rtc->ops = &rk808_rtc_ops;
490 
491         rk808_rtc->irq = platform_get_irq(pdev, 0);
492         if (rk808_rtc->irq < 0) {
493                 if (rk808_rtc->irq != -EPROBE_DEFER)
494                         dev_err(&pdev->dev, "Wake up is not possible as irq = %d\n",
495                                 rk808_rtc->irq);
496                 return rk808_rtc->irq;
497         }
498 
499         /* request alarm irq of rk808 */
500         ret = devm_request_threaded_irq(&pdev->dev, rk808_rtc->irq, NULL,
501                                         rk808_alarm_irq, 0,
502                                         "RTC alarm", rk808_rtc);
503         if (ret) {
504                 dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n",
505                         rk808_rtc->irq, ret);
506                 return ret;
507         }
508 
509         return rtc_register_device(rk808_rtc->rtc);
510 }

第439行,调用devm_kzalloc申请rtc大小的空间,返回申请空间的首地址。
第489行,RTC 底层驱动集为rk808_rtc_ops。rk808_rtc_ops操作集包含了读取/设置RTC时间,读取/设置闹钟等函数。
第500行,调用devm_request_threaded_irq函数请求RTC中断,中断服务函数为rk808_alarm_irq,用于RTC闹钟中断。
第509行,调用 rtc_register_device函数向系统注册 rtc_devcie。
29.3 RTC时间查看与设置
29.3.1 使能RK809内部RTC
从上面几节我们可以知道,RK809内部RTC的使能需要先使能RK809,默认已经使能,我们打开设备树rk3568-evb.dtsi:
示例代码29.3.1.1 pmic节点信息

1121 rk809: pmic@20 {
1122         compatible = "rockchip,rk809";
1123         reg = <0x20>;
1124         interrupt-parent = <&gpio0>;
1125         interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
1126 
1127         pinctrl-names = "default", "pmic-sleep",
1128                         "pmic-power-off", "pmic-reset";
1129         pinctrl-0 = <&pmic_int>;
1130         pinctrl-1 = <&soc_slppin_slp>, <&rk817_slppin_slp>;
1131         pinctrl-2 = <&soc_slppin_gpio>, <&rk817_slppin_pwrdn>;
1132         pinctrl-3 = <&soc_slppin_gpio>, <&rk817_slppin_rst>;
1133 
1134         rockchip,system-power-controller;
1135         wakeup-source;
1136         #clock-cells = <1>;
1137         clock-output-names = "rk808-clkout1", "rk808-clkout2";
1138         //fb-inner-reg-idxs = <2>;
1139         /* 1: rst regs (default in codes), 0: rst the pmic */
1140         pmic-reset-func = <0>;
1141         /* not save the PMIC_POWER_EN register in uboot */
1142         not-save-power-en = <1>;
1143 
1144         vcc1-supply = <&vcc3v3_sys>;
1145         vcc2-supply = <&vcc3v3_sys>;
1146         vcc3-supply = <&vcc3v3_sys>;
1147         vcc4-supply = <&vcc3v3_sys>;
1148         vcc5-supply = <&vcc3v3_sys>;
1149         vcc6-supply = <&vcc3v3_sys>;
1150         vcc7-supply = <&vcc3v3_sys>;
1151         vcc8-supply = <&vcc3v3_sys>;
1152         vcc9-supply = <&vcc3v3_sys>;
... ...
};
上面status状态没写,默认就是“okay”的。
同时我们需要在menuconfig 里对应的宏配置为CONFIG_RTC_DRV_RK808。> Device Drivers > Real Time Clock选中CONFIG_RTC_DRV_RK808。如下图。

在这里插入图片描述

图29.3.1 RK808 RTC驱动使能
29.3.2 查看时间
RTC是用来记时的,因此最基本的就是查看时间,Linux内核启动的时候可以看到系统时钟设置信息,如图29.3.2.1所示:
在这里插入图片描述

图29.3.2.1 Linux启动log信息
从图29.3.2.1中可以看出可以看到rk808-rtc已经注册为rtc0,有rtc0那么可能会有rtc1,没错rtc可以存在多个,比如ATK-DLRK3568开发板就有两个rtc,一个是pcf8563 rtc外部时钟芯片。
如果要查看时间的话输入“date”命令即可,结果如图29.3.2.2所示:
在这里插入图片描述

图29.3.2.2 当前时间值
从上面可看到内核启动RTC时间采用的是UTC标准,而系统启动后采用的是CST标准,时间恰好相差8个小时。UTC(协调世界时)和CST(中部标准时间)是两个不同的时间标准,在中国,CST通常被解释为"China Standard Time"(中国标准时间),而不是"Central Standard Time"(中部标准时间)。中国CST与协调世界时(UTC)相差8小时,即UTC+8。
RTC时间设置也是使用的date命令,输入“date --help”命令即可查看date命令如何设置系统时间,结果如图29.3.2.3所示:
在这里插入图片描述

图29.3.2.3 date命令帮助信息
比如现在设置当前时间为2023年7月10日 14:00:00,因此输入如下命令:
date -s “2023-07-10 14:10:00”
设置完成以后再次使用date命令查看一下当前时间就会发现时间改过来了,如图29.3.2.4所示:
在这里插入图片描述

图43.3.2.4 当前时间
大家注意我们使用“date -s”命令仅仅是修改了当前时间,此时间还没有写入到RK809内部RTC里面或其他的RTC芯片里面,因此系统重启以后时间又会丢失。我们需要将当前的时间写入到RTC里面,这里要用到hwclock命令,输入如下命令将系统时间写入到RTC里面:
hwclock -w //将当前系统时间写入到RTC里面
时间写入到RTC里面以后就不怕系统重启以后时间丢失了,如果ATK-DLRK3568开发板底板接了纽扣电池,那么开发板即使断电了时间也不会丢失。大家可以尝试一下不断电重启和断电重启这两种情况下开发板时间会不会丢失。(请注意:由于系统使用了ntp校时,如果我们有插网线或者WIFI联网,并且能上网,那么输入date查看的时间就是最新的系统时间,RK809内部硬件时间并不会因ntp校时而改变。)

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值