赞
踩
1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
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校时而改变。)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。