软硬件环境:
• 主控: Amlogic S905X3
• 系统: Android9.0
• Kernel: 4.9.113
概述:
因S905X3 内部无专用RTC,采用的是定时器实现rtc,因项目需要,需要提供准确的时间,并能够掉电保存时间,故选用昊昱微半导体的HYM8563,作为本次的主角。
移植以及问题解决分析过程:
1: 查看内核,发现已经支持了此芯片,故直接修改:common/arch/arm/configs/meson64_a32_defconfig 添加 CONFIG_RTC_DRV_HYM8563=y
2:查看HYM8563硬件设计原理图,
确定I2C引脚为(GPIOAO_2_I2C_AO_MO_SCL,GPIOAO_3_I2C_AO_M0_SDA):
3:查看I2C相关描述:
/* i2c_ao */
static const unsigned int i2c_ao_sck_pins[] = {GPIOAO_2};
static const unsigned int i2c_ao_sda_pins[] = {GPIOAO_3};
# 查看 mesonsm1.dtsi中关于ao_i2c_master_pins1相关描述
ao_i2c_master_pins1:ao_i2c_pins1 {
mux {
groups = "i2c_ao_sck",
"i2c_ao_sda";
function = "i2c_ao";
drive-strength = <2>;
};
};
4:在dts中添加关于HYM8563相应描述,PS:由于我们不使用HYM8563的中断输出功能,暂时不配置与HYM8563中断相连的引脚。
&i2c_AO {
status = "okay";
pinctrl-names="default";
pinctrl-0=<&ao_i2c_master_pins1>;
clock-frequency = <100000>; /* default 100k */
/* for rtc hym8563 */
hym8563: hym8563@51 {
compatible = "haoyu,hym8563";
reg = <0x51>;
#clock-cells = <0>;
};
};
5:重新编译内核(boot.img),以及dts(dt.img)
make bootimage -j16
make dtbimage -j16
6:烧录img文件
adb reboot fastboot
fastboot flashing unlock_critical
fastboot flashing unlock
fastboot flash boot boot.img
fastboot flash dts dt.img
fastboot reboot
7:此处是不是应该欢呼雀跃,驱动已经移植完成?那你就想多了,开发要是这么简单就好了。
- A: hym8563 驱动抛错:“no valid clock/calendar values available”,“hctosys: unable to read the hardware clock”
bh905x3:/ # date
Thu Jan 1 08:03:21 GMT 2009
bh905x3:/ # dmesg |grep rtc
[ 3.314649] rtc-hym8563 4-0051: no valid clock/calendar values available
[ 3.316066] rtc-hym8563 4-0051: rtc core: registered hym8563 as rtc0
[ 3.720134] aml_vrtc rtc: rtc core: registered aml_vrtc as rtc1
[ 3.720348] input: aml_vkeypad as /devices/platform/rtc/input/input1
[ 4.576520] rtc-hym8563 4-0051: no valid clock/calendar values available
[ 4.576523] rtc-hym8563 4-0051: hctosys: unable to read the hardware clock
[ 15.719979] type=1400 audit(15.600:29): avc: denied { open } for pid=3472 comm="system_server" path="/sys/devices/platform/soc/ff800000.aobus/ff805000.i2c/i2c-4/4-0051/rtc/rtc0/hctosys" dev="sysfs" ino=17355 scontext=u:r:system_server:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1
[ 15.845900] type=1400 audit(15.600:29): avc: denied { open } for pid=3472 comm="system_server" path="/sys/devices/platform/soc/ff800000.aobus/ff805000.i2c/i2c-4/4-0051/rtc/rtc0/hctosys" dev="sysfs" ino=17355 scontext=u:r:system_server:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1
- B: 我们使用hwclock 查看一下,插上网线,让系统通过NTP服务器校时,通过logcat 查看一下HAL调用情况(logcat |grep AlarmManagerService)结果报错,找不到设备。难道要像网上说的手动去创建“/dev/misc/rtc”?答案是否定的。
bh905x3:/ # hwclock
hwclock: /dev/misc/rtc: No such file or directory
bh905x3:/ # logcat |grep AlarmManagerService
01-01 08:00:21.111 3505 3691 D SntpClient: round trip: 304ms, clock offset: 396574693935ms
01-01 08:00:21.112 3505 3691 D AlarmManagerService: Setting time of day to sec=1627371515
07-27 07:38:35.046 3505 3691 W AlarmManagerService: Unable to set rtc to 1627371515: No such device
- C:我们查看hwclock(externa/toybox/toys/other/hwclock.c)源码,以及HAL层 com_android_server_AlarmManagerService.cpp 源码(frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp)来分析:/sys/class/rtc/rtc0/hctosys 的值要为1才行。
/*hwclock.c*/
static int rtc_find(struct dirtree* node)
{
FILE *fp;
if (!node->parent) return DIRTREE_RECURSE;
sprintf(toybuf, "/sys/class/rtc/%s/hctosys", node->name);
fp = fopen(toybuf, "r");
if (fp) {
int hctosys = 0, items = fscanf(fp, "%d", &hctosys);
fclose(fp);
if (items == 1 && hctosys == 1) {
sprintf(toybuf, "/dev/%s", node->name);
TT.fname = toybuf;
return DIRTREE_ABORT;
}
}
return 0;
}
/*com_android_server_AlarmManagerService.cpp*/
static const char rtc_sysfs[] = "/sys/class/rtc";
static bool rtc_is_hctosys(unsigned int rtc_id)
{
android::String8 hctosys_path = String8::format("%s/rtc%u/hctosys",
rtc_sysfs, rtc_id);
FILE *file = fopen(hctosys_path.string(), "re");
if (!file) {
ALOGE("failed to open %s: %s", hctosys_path.string(), strerror(errno));
return false;
}
unsigned int hctosys;
bool ret = false;
int err = fscanf(file, "%u", &hctosys);
if (err == EOF)
ALOGE("failed to read from %s: %s", hctosys_path.string(),
strerror(errno));
else if (err == 0)
ALOGE("%s did not have expected contents", hctosys_path.string());
else
ret = hctosys;
fclose(file);
return ret;
}
static int wall_clock_rtc()
{
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(rtc_sysfs), closedir);
if (!dir.get()) {
ALOGE("failed to open %s: %s", rtc_sysfs, strerror(errno));
return -1;
}
struct dirent *dirent;
while (errno = 0, dirent = readdir(dir.get())) {
unsigned int rtc_id;
int matched = sscanf(dirent->d_name, "rtc%u", &rtc_id);
if (matched < 0)
break;
else if (matched != 1)
continue;
if (rtc_is_hctosys(rtc_id)) {
ALOGV("found wall clock RTC %u", rtc_id);
return rtc_id;
}
}
if (errno == 0)
ALOGW("no wall clock RTC found");
else
ALOGE("failed to enumerate RTCs: %s", strerror(errno));
return -1;
}
8:那如何才能让/sys/class/rtc/rtc0/hctosys的值为1呢?
- A:通过分析内核源码(rtc-sysfs.c, hctosys.c),需要 rtc_hctosys_ret 为 0 ,则函数(rtc_hctosys)需要成功调用返回 0 ,即(rtc_read_time)函数需返回 0 ,通过7中日志可以看出 HYM8563,返回的是“-EPERM”。
**********************************rtc-sysfs.c***********************************
/**
* rtc_sysfs_show_hctosys - indicate if the given RTC set the system time
*
* Returns 1 if the system clock was set by this RTC at the last
* boot or resume event.
*/
static ssize_t
hctosys_show(struct device *dev, struct device_attribute *attr, char *buf)
{
#ifdef CONFIG_RTC_HCTOSYS_DEVICE
if (rtc_hctosys_ret == 0 &&
strcmp(dev_name(&to_rtc_device(dev)->dev),
CONFIG_RTC_HCTOSYS_DEVICE) == 0)
return sprintf(buf, "1\n");
else
#endif
return sprintf(buf, "0\n");
}
static DEVICE_ATTR_RO(hctosys);
**********************************rtc-hym8563.c***********************************
#define HYM8563_SEC 0x02
#define HYM8563_SEC_VL BIT(7)
/*
* RTC handling
*/
static int hym8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
struct hym8563 *hym8563 = i2c_get_clientdata(client);
u8 buf[7];
int ret;
if (!hym8563->valid) {
dev_warn(&client->dev, "no valid clock/calendar values available\n");
return -EPERM;
}
ret = i2c_smbus_read_i2c_block_data(client, HYM8563_SEC, 7, buf);
tm->tm_sec = bcd2bin(buf[0] & HYM8563_SEC_MASK);
tm->tm_min = bcd2bin(buf[1] & HYM8563_MIN_MASK);
tm->tm_hour = bcd2bin(buf[2] & HYM8563_HOUR_MASK);
tm->tm_mday = bcd2bin(buf[3] & HYM8563_DAY_MASK);
tm->tm_wday = bcd2bin(buf[4] & HYM8563_WEEKDAY_MASK); /* 0 = Sun */
tm->tm_mon = bcd2bin(buf[5] & HYM8563_MONTH_MASK) - 1; /* 0 = Jan */
tm->tm_year = bcd2bin(buf[6]) + 100;
return 0;
}
static int hym8563_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
....................................................................
/* check state of calendar information */
ret = i2c_smbus_read_byte_data(client, HYM8563_SEC);
if (ret < 0)
return ret;
hym8563->valid = !(ret & HYM8563_SEC_VL);
dev_dbg(&client->dev, "rtc information is %s\n",
hym8563->valid ? "valid" : "invalid");
....................................................................
}
-
B:通过源码分析 hym8563->valid 通过HYM8563_SEC的寄存器第BIT(7)位来确定。查看数据手册可知,当未给hym8563设置时间时,hym8563,的02H寄存器的第七位为 1 (VL=1:不保证准确的时钟/日历数据)。
-
C:解决办法呼之欲出了,在读取到VL=1的时候,我们给hym8563设置一个默认的日期,即可解决。(也可以在HAL层修改,给rtc一个默认值。但博主觉得,在内核层修改更具通用性,因为不管你在dts设置何值,最终都会通过NTP校时获取到当前时间。最终如何修改就看各位观众老爷们的喜好了)。
最终修改方案,供大家参考:
一 :在dts中添加init_date项,当hym8563_probe的时候,检测到系统如果未设置时间,则给时钟芯片一个默认值(init_date 设置的值)。
• DTS修改信息:
&i2c_AO {
status = "okay";
pinctrl-names="default";
pinctrl-0=<&ao_i2c_master_pins1>;
clock-frequency = <100000>; /* default 100k */
/* for rtc hym8563 */
hym8563: hym8563@51 {
compatible = "haoyu,hym8563";
reg = <0x51>;
init_date = "2021/07/28";
#clock-cells = <0>;
};
};
二:修改内核驱动:drivers/rtc/rtc-hym8563.c
• rtc-hym8563.c的修改记录
Signed-off-by: mleaf <mleaf90@gmail.com>
---
diff --git a/drivers/rtc/rtc-hym8563.c b/drivers/rtc/rtc-hym8563.c
index e5ad527cb75e..acf00fe25ae6 100644
--- a/drivers/rtc/rtc-hym8563.c
+++ b/drivers/rtc/rtc-hym8563.c
@@ -524,11 +524,96 @@ static int hym8563_resume(struct device *dev)
static SIMPLE_DEV_PM_OPS(hym8563_pm_ops, hym8563_suspend, hym8563_resume);
+#define TIME_LEN 10
+static int parse_init_date(const char *date, struct rtc_time *rtm)
+{
+ unsigned int init_date;
+ char local_str[TIME_LEN + 1];
+ char *year_s, *month_s, *day_s, *str;
+ unsigned int year_d, month_d, day_d;
+ int ret;
+
+ if (strlen(date) != 10)
+ return -1;
+ memset(local_str, 0, TIME_LEN + 1);
+ strncpy(local_str, date, TIME_LEN);
+ str = local_str;
+ year_s = strsep(&str, "/");
+ if (!year_s)
+ return -1;
+ month_s = strsep(&str, "/");
+ if (!month_s)
+ return -1;
+ day_s = str;
+ pr_debug("year: %s\nmonth: %s\nday: %s\n", year_s, month_s, day_s);
+ ret = kstrtou32(year_s, 10, &year_d);
+ if (ret < 0 || year_d > 2100 || year_d < 1900)
+ return -1;
+ ret = kstrtou32(month_s, 10, &month_d);
+ if (ret < 0 || month_d > 12)
+ return -1;
+ ret = kstrtou32(day_s, 10, &day_d);
+ if (ret < 0 || day_d > 31)
+ return -1;
+ init_date = mktime(year_d, month_d, day_d, 0, 0, 0);
+
+ rtc_time_to_tm(init_date, rtm);
+
+ return rtc_valid_tm(rtm);
+}
+
+static int hym8563_rtc_init_time(struct i2c_client *client, struct rtc_time *tm)
+{
+ u8 buf[7];
+ int ret;
+
+ /* Years >= 2100 are to far in the future, 19XX is to early */
+ if (tm->tm_year < 100 || tm->tm_year >= 200)
+ return -EINVAL;
+
+ buf[0] = bin2bcd(tm->tm_sec);
+ buf[1] = bin2bcd(tm->tm_min);
+ buf[2] = bin2bcd(tm->tm_hour);
+ buf[3] = bin2bcd(tm->tm_mday);
+ buf[4] = bin2bcd(tm->tm_wday);
+ buf[5] = bin2bcd(tm->tm_mon + 1);
+
+ /*
+ * While the HYM8563 has a century flag in the month register,
+ * it does not seem to carry it over a subsequent write/read.
+ * So we'll limit ourself to 100 years, starting at 2000 for now.
+ */
+ buf[6] = bin2bcd(tm->tm_year - 100);
+
+ /*
+ * CTL1 only contains TEST-mode bits apart from stop,
+ * so no need to read the value first
+ */
+ ret = i2c_smbus_write_byte_data(client, HYM8563_CTL1,
+ HYM8563_CTL1_STOP);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_i2c_block_data(client, HYM8563_SEC, 7, buf);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(client, HYM8563_CTL1, 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static int hym8563_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct hym8563 *hym8563;
int ret;
+ struct device_node *node = client->dev.of_node;
+ const char *str = NULL;
+ int init_date_valid = -1;
+ struct rtc_time tm;
hym8563 = devm_kzalloc(&client->dev, sizeof(*hym8563), GFP_KERNEL);
if (!hym8563)
@@ -556,7 +641,12 @@ static int hym8563_probe(struct i2c_client *client,
return ret;
}
}
-
+ /* optional override of the init_date */
+ ret = of_property_read_string(node, "init_date", &str);
+ if (!ret) {
+ dev_dbg(&client->dev, "init_date: %s\n", str);
+ init_date_valid = parse_init_date(str, &tm);
+ }
/* check state of calendar information */
ret = i2c_smbus_read_byte_data(client, HYM8563_SEC);
if (ret < 0)
@@ -565,7 +655,17 @@ static int hym8563_probe(struct i2c_client *client,
hym8563->valid = !(ret & HYM8563_SEC_VL);
dev_dbg(&client->dev, "rtc information is %s\n",
hym8563->valid ? "valid" : "invalid");
-
+ if (!hym8563->valid) {
+ dev_warn(&client->dev, "no valid clock/calendar values available\n");
+ if(init_date_valid == 0){
+ dev_info(&client->dev, "setting hym8563 clock to %s\n", str);
+ ret = hym8563_rtc_init_time(client, &tm);
+ if (!ret) {
+ dev_dbg(&client->dev, "hym8563_rtc_init_time: %s\n", str);
+ hym8563->valid = true;
+ }
+ }
+ }
hym8563->rtc = devm_rtc_device_register(&client->dev, client->name,
&hym8563_rtc_ops, THIS_MODULE);
if (IS_ERR(hym8563->rtc))
- selinux权限添加:
# 修改system_server.te
#add by mleaf for rtc
allow system_server sysfs:file open;
//libsepol.report_failure: neverallow on line 31 of system/sepolicy/private/domain.te (or line 26822 of policy.conf) violated by allow system_server sysfs:file { open };
//libsepol.check_assertions: 1 neverallow failures occurred
# 修改 system/sepolicy/private/domain.te
neverallow {
coredomain
-init
-ueventd
-vold
-system_server
} sysfs:file no_rw_file_perms;
#修改后拷贝到system/sepolicy/prebuilts/api/28.0/private/,使两个文件保持一致
cp system/sepolicy/private/domain.te system/sepolicy/prebuilts/api/28.0/private/domain.te
• 调试log信息:
bh905x3:/ # dmesg |grep rtc
[ 3.317770] rtc-hym8563 4-0051: no valid clock/calendar values available
[ 3.319012] rtc-hym8563 4-0051: setting hym8563 clock to 2021/07/28
[ 3.326972] rtc-hym8563 4-0051: hym8563_rtc_init_time: 2021/07/28
[ 3.338555] rtc-hym8563 4-0051: rtc core: registered hym8563 as rtc0
[ 3.749774] aml_vrtc rtc: rtc core: registered aml_vrtc as rtc1
[ 3.750000] input: aml_vkeypad as /devices/platform/rtc/input/input1
[ 4.608679] rtc-hym8563 4-0051: setting system clock to 2021-07-28 00:00:01 UTC (1627430401)
bh905x3:/ # cat /sys/class/rtc/rtc0/hctosys
1
#通过ntp校时后时间也能正确设置,不会报错。
bh905x3:/ # logcat |grep AlarmManagerService
07-28 00:00:28.062 3492 3694 D AlarmManagerService: Setting time of day to sec=1627441608
bh905x3:/ # date
Wed Jul 28 03:07:36 GMT 2021
bh905x3:/ # cat /sys/class/rtc/rtc0/date
2021-07-28
bh905x3:/ # cat /sys/class/rtc/rtc0/time
03:07:47
bh905x3:/ #
参考:
• common/arch/arm/boot/dts/amlogic/mesonsm1.dtsi
rtc{
compatible = "amlogic, aml_vrtc";
alarm_reg_addr = <0xff8000a8>;
timer_e_addr = <0xffd0f188>;
init_date = "2015/01/01";
status = "okay";
};
• common/drivers/amlogic/vrtc/aml_vrtc.c