From: Duncan Laurie <dlau...@google.com>

This Embedded Controller has an internal RTC that is exposed
as a standard RTC class driver with read/write functionality.

> hwclock --show --rtc /dev/rtc1
2007-12-31 16:01:20.460959-08:00
> hwclock --systohc --rtc /dev/rtc1
> hwclock --show --rtc /dev/rtc1
2018-11-29 17:08:00.780793-08:00

Signed-off-by: Duncan Laurie <dlau...@google.com>
Signed-off-by: Nick Crews <ncr...@google.com>
---

 drivers/platform/chrome/Makefile           |   3 +-
 drivers/platform/chrome/wilco_ec.h         |  29 ++++
 drivers/platform/chrome/wilco_ec_mailbox.c |  15 ++
 drivers/platform/chrome/wilco_ec_rtc.c     | 163 +++++++++++++++++++++
 4 files changed, 209 insertions(+), 1 deletion(-)
 create mode 100644 drivers/platform/chrome/wilco_ec_rtc.c

diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
index e8603bc5b095..5ca484c2d0d7 100644
--- a/drivers/platform/chrome/Makefile
+++ b/drivers/platform/chrome/Makefile
@@ -13,5 +13,6 @@ cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC)        += 
cros_ec_lpc_mec.o
 obj-$(CONFIG_CROS_EC_LPC)              += cros_ec_lpcs.o
 obj-$(CONFIG_CROS_EC_PROTO)            += cros_ec_proto.o
 
-wilco_ec-objs                          := wilco_ec_mailbox.o wilco_ec_sysfs.o
+wilco_ec-objs                          := wilco_ec_mailbox.o wilco_ec_rtc.o \
+                                          wilco_ec_sysfs.o
 obj-$(CONFIG_WILCO_EC)                 += wilco_ec.o
diff --git a/drivers/platform/chrome/wilco_ec.h 
b/drivers/platform/chrome/wilco_ec.h
index 0b3dec4e2830..eee5c514e720 100644
--- a/drivers/platform/chrome/wilco_ec.h
+++ b/drivers/platform/chrome/wilco_ec.h
@@ -19,6 +19,7 @@
 
 #include <linux/device.h>
 #include <linux/kernel.h>
+#include <linux/rtc.h>
 
 /* Normal commands have a maximum 32 bytes of data */
 #define EC_MAILBOX_DATA_SIZE           32
@@ -55,6 +56,7 @@ enum wilco_ec_msg_type {
  * @data_buffer: Buffer used for EC communication.  The same buffer
  *               is used to hold the request and the response.
  * @data_size: Size of the data buffer used for EC communication.
+ * @rtc: RTC device handler.
  */
 struct wilco_ec_device {
        struct device *dev;
@@ -64,6 +66,7 @@ struct wilco_ec_device {
        struct resource *io_packet;
        void *data_buffer;
        size_t data_size;
+       struct rtc_device *rtc;
 };
 
 /**
@@ -114,4 +117,30 @@ int wilco_ec_sysfs_init(struct wilco_ec_device *ec);
  */
 void wilco_ec_sysfs_remove(struct wilco_ec_device *ec);
 
+/**
+ * wilco_ec_rtc_read() - Fill RTC time structure with values from the EC.
+ * @dev: EC device.
+ * @tm: Returns RTC time from EC.
+ *
+ * Return: 0 for success or negative error code on failure.
+ */
+int wilco_ec_rtc_read(struct device *dev, struct rtc_time *tm);
+
+/**
+ * wilco_ec_rtc_write() - Write EC time/date from RTC time structure.
+ * @dev: EC device.
+ * @tm: RTC time to write to EC.
+ *
+ * Return: 0 for success or negative error code on failure.
+ */
+int wilco_ec_rtc_write(struct device *dev, struct rtc_time *tm);
+
+/**
+ * wilco_ec_rtc_sync() - Write EC time/date from system time.
+ * @dev: EC device.
+ *
+ * Return: 0 for success or negative error code on failure.
+ */
+int wilco_ec_rtc_sync(struct device *dev);
+
 #endif /* WILCO_EC_H */
diff --git a/drivers/platform/chrome/wilco_ec_mailbox.c 
b/drivers/platform/chrome/wilco_ec_mailbox.c
index 1cb34b7280fd..2f093a281a30 100644
--- a/drivers/platform/chrome/wilco_ec_mailbox.c
+++ b/drivers/platform/chrome/wilco_ec_mailbox.c
@@ -33,6 +33,7 @@
 #include <linux/mfd/cros_ec_lpc_mec.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/rtc.h>
 #include "wilco_ec.h"
 
 /* Version of mailbox interface */
@@ -325,6 +326,11 @@ static struct resource *wilco_get_resource(struct 
platform_device *pdev,
        return res;
 }
 
+static const struct rtc_class_ops wilco_ec_rtc_ops = {
+       .read_time = wilco_ec_rtc_read,
+       .set_time = wilco_ec_rtc_write,
+};
+
 static int wilco_ec_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -355,6 +361,15 @@ static int wilco_ec_probe(struct platform_device *pdev)
        cros_ec_lpc_mec_init(ec->io_packet->start,
                             ec->io_packet->start + EC_MAILBOX_DATA_SIZE);
 
+       /* Install RTC driver */
+       ec->rtc = devm_rtc_device_register(ec->dev, "wilco_ec",
+                                          &wilco_ec_rtc_ops, THIS_MODULE);
+       if (IS_ERR(ec->rtc)) {
+               dev_err(dev, "Failed to install RTC driver\n");
+               cros_ec_lpc_mec_destroy();
+               return PTR_ERR(ec->rtc);
+       }
+
        /* Create sysfs attributes for userspace interaction */
        if (wilco_ec_sysfs_init(ec) < 0) {
                dev_err(dev, "Failed to create sysfs attributes\n");
diff --git a/drivers/platform/chrome/wilco_ec_rtc.c 
b/drivers/platform/chrome/wilco_ec_rtc.c
new file mode 100644
index 000000000000..3e36403d0cb8
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec_rtc.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * wilco_ec_rtc - RTC interface for Wilco Embedded Controller
+ *
+ * Copyright 2018 Google LLC
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/bcd.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/timekeeping.h>
+#include "wilco_ec.h"
+
+#define EC_COMMAND_CMOS                        0x7c
+#define  EC_CMOS_TOD_WRITE             0x02
+#define  EC_CMOS_TOD_READ              0x08
+
+/**
+ * struct ec_rtc_read - Format of RTC returned by EC.
+ * @second: Second value (0..59)
+ * @minute: Minute value (0..59)
+ * @hour: Hour value (0..23)
+ * @day: Day value (1..31)
+ * @month: Month value (1..12)
+ * @year: Year value (full year % 100)
+ * @century: Century value (full year / 100)
+ *
+ * All values are presented in binary (not BCD).
+ */
+struct ec_rtc_read {
+       u8 second;
+       u8 minute;
+       u8 hour;
+       u8 day;
+       u8 month;
+       u8 year;
+       u8 century;
+} __packed;
+
+/**
+ * struct ec_rtc_write - Format of RTC sent to the EC.
+ * @param: EC_CMOS_TOD_WRITE
+ * @century: Century value (full year / 100)
+ * @year: Year value (full year % 100)
+ * @month: Month value (1..12)
+ * @day: Day value (1..31)
+ * @hour: Hour value (0..23)
+ * @minute: Minute value (0..59)
+ * @second: Second value (0..59)
+ * @weekday: Day of the week (0=Saturday)
+ *
+ * All values are presented in BCD.
+ */
+struct ec_rtc_write {
+       u8 param;
+       u8 century;
+       u8 year;
+       u8 month;
+       u8 day;
+       u8 hour;
+       u8 minute;
+       u8 second;
+       u8 weekday;
+} __packed;
+
+int wilco_ec_rtc_read(struct device *dev, struct rtc_time *tm)
+{
+       struct wilco_ec_device *ec = dev_get_drvdata(dev);
+       u8 param = EC_CMOS_TOD_READ;
+       struct ec_rtc_read rtc;
+       struct wilco_ec_message msg = {
+               .type = WILCO_EC_MSG_LEGACY,
+               .flags = WILCO_EC_FLAG_RAW_RESPONSE,
+               .command = EC_COMMAND_CMOS,
+               .request_data = &param,
+               .request_size = sizeof(param),
+               .response_data = &rtc,
+               .response_size = sizeof(rtc),
+       };
+       struct rtc_time calc_tm;
+       unsigned long time;
+       int ret;
+
+       ret = wilco_ec_mailbox(ec, &msg);
+       if (ret < 0) {
+               dev_err(dev, "Failed to read EC RTC\n");
+               return ret;
+       }
+
+       tm->tm_sec      = rtc.second;
+       tm->tm_min      = rtc.minute;
+       tm->tm_hour     = rtc.hour;
+       tm->tm_mday     = rtc.day;
+       /*
+        * The RTC stores the month value as 1-12 but the kernel expects 0-11,
+        * so ensure invalid/zero month value from RTC is not converted to -1.
+        */
+       tm->tm_mon      = rtc.month ? rtc.month - 1 : 0;
+       tm->tm_year     = rtc.year + (rtc.century * 100) - 1900;
+       tm->tm_yday     = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+       /* Compute day of week */
+       rtc_tm_to_time(tm, &time);
+       rtc_time_to_tm(time, &calc_tm);
+       tm->tm_wday = calc_tm.tm_wday;
+
+       return 0;
+}
+
+int wilco_ec_rtc_write(struct device *dev, struct rtc_time *tm)
+{
+       struct wilco_ec_device *ec = dev_get_drvdata(dev);
+       struct ec_rtc_write rtc;
+       struct wilco_ec_message msg = {
+               .type = WILCO_EC_MSG_LEGACY,
+               .flags = WILCO_EC_FLAG_RAW_RESPONSE,
+               .command = EC_COMMAND_CMOS,
+               .request_data = &rtc,
+               .request_size = sizeof(rtc),
+       };
+       int year = tm->tm_year + 1900;
+       /* Convert from 0=Sunday to 0=Saturday for the EC */
+       int wday = tm->tm_wday == 6 ? 0 : tm->tm_wday + 1;
+       int ret;
+
+       rtc.param       = EC_CMOS_TOD_WRITE;
+       rtc.century     = bin2bcd(year / 100);
+       rtc.year        = bin2bcd(year % 100);
+       rtc.month       = bin2bcd(tm->tm_mon + 1);
+       rtc.day         = bin2bcd(tm->tm_mday);
+       rtc.hour        = bin2bcd(tm->tm_hour);
+       rtc.minute      = bin2bcd(tm->tm_min);
+       rtc.second      = bin2bcd(tm->tm_sec);
+       rtc.weekday     = bin2bcd(wday);
+
+       ret = wilco_ec_mailbox(ec, &msg);
+       if (ret < 0) {
+               dev_err(dev, "Failed to write EC RTC\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+int wilco_ec_rtc_sync(struct device *dev)
+{
+       struct rtc_time tm;
+
+       rtc_time64_to_tm(ktime_get_real_seconds(), &tm);
+
+       return wilco_ec_rtc_write(dev, &tm);
+}
-- 
2.20.0.405.gbc1bbc6f85-goog

Reply via email to