软硬件环境:

• 主控: 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):

s905 gpu qudong_s905 gpu qudong

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

  • 附上HYM8563 datasheet 下载地址