platform/chrome: wilco_ec: Add RTC driver
authorNick Crews <ncrews@chromium.org>
Sat, 9 Feb 2019 00:37:19 +0000 (17:37 -0700)
committerEnric Balletbo i Serra <enric.balletbo@collabora.com>
Thu, 21 Feb 2019 20:36:53 +0000 (21:36 +0100)
This Embedded Controller has an internal RTC that is exposed
as a standard RTC class driver with read/write functionality.

The driver is added to the drivers/rtc/ so that the maintainer of that
directory will be able to comment on this change, as that maintainer is
the expert on this system. In addition, the driver code is called
indirectly after a corresponding device is registered from core.c,
as opposed to core.c registering the driver callbacks directly.

To test:
> 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

> 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 <dlaurie@google.com>
Signed-off-by: Nick Crews <ncrews@chromium.org>
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
[Fix the sparse warning: symbol 'wilco_ec_rtc_read/write' was not declared]
Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
drivers/platform/chrome/wilco_ec/core.c
drivers/rtc/Kconfig
drivers/rtc/Makefile
drivers/rtc/rtc-wilco-ec.c [new file with mode: 0644]
include/linux/platform_data/wilco-ec.h

index af5fd288b63ba66afdb75e010ad3e2576622aca3..05e1e2be1c910e0d560b653c1d772a2bb639c279 100644 (file)
@@ -42,6 +42,7 @@ static int wilco_ec_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
        struct wilco_ec_device *ec;
+       int ret;
 
        ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
        if (!ec)
@@ -78,13 +79,30 @@ static int wilco_ec_probe(struct platform_device *pdev)
                                                         PLATFORM_DEVID_AUTO,
                                                         NULL, 0);
 
+       /* Register a child device that will be found by the RTC driver. */
+       ec->rtc_pdev = platform_device_register_data(dev, "rtc-wilco-ec",
+                                                    PLATFORM_DEVID_AUTO,
+                                                    NULL, 0);
+       if (IS_ERR(ec->rtc_pdev)) {
+               dev_err(dev, "Failed to create RTC platform device\n");
+               ret = PTR_ERR(ec->rtc_pdev);
+               goto unregister_debugfs;
+       }
+
        return 0;
+
+unregister_debugfs:
+       if (ec->debugfs_pdev)
+               platform_device_unregister(ec->debugfs_pdev);
+       cros_ec_lpc_mec_destroy();
+       return ret;
 }
 
 static int wilco_ec_remove(struct platform_device *pdev)
 {
        struct wilco_ec_device *ec = platform_get_drvdata(pdev);
 
+       platform_device_unregister(ec->rtc_pdev);
        if (ec->debugfs_pdev)
                platform_device_unregister(ec->debugfs_pdev);
 
index 225b0b8516f3e4206a29031c47df9381607d6a2a..d5063c7915150cd9834eaf2ca078843d1ca4d25e 100644 (file)
@@ -1814,4 +1814,15 @@ config RTC_DRV_GOLDFISH
          Goldfish is a code name for the virtual platform developed by Google
          for Android emulation.
 
+config RTC_DRV_WILCO_EC
+       tristate "Wilco EC RTC"
+       depends on WILCO_EC
+       default m
+       help
+         If you say yes here, you get read/write support for the Real Time
+         Clock on the Wilco Embedded Controller (Wilco is a kind of Chromebook)
+
+         This can also be built as a module. If so, the module will
+         be named "rtc_wilco_ec".
+
 endif # RTC_CLASS
index df022d820bee1abae550cec9aacab668e46f5f76..6255ea78da25b225fcc9c214eae985a9b0272e7e 100644 (file)
@@ -172,6 +172,7 @@ obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
 obj-$(CONFIG_RTC_DRV_VR41XX)   += rtc-vr41xx.o
 obj-$(CONFIG_RTC_DRV_VRTC)     += rtc-mrst.o
 obj-$(CONFIG_RTC_DRV_VT8500)   += rtc-vt8500.o
+obj-$(CONFIG_RTC_DRV_WILCO_EC) += rtc-wilco-ec.o
 obj-$(CONFIG_RTC_DRV_WM831X)   += rtc-wm831x.o
 obj-$(CONFIG_RTC_DRV_WM8350)   += rtc-wm8350.o
 obj-$(CONFIG_RTC_DRV_X1205)    += rtc-x1205.o
diff --git a/drivers/rtc/rtc-wilco-ec.c b/drivers/rtc/rtc-wilco-ec.c
new file mode 100644 (file)
index 0000000..e62bda0
--- /dev/null
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RTC interface for Wilco Embedded Controller with R/W abilities
+ *
+ * Copyright 2018 Google LLC
+ *
+ * The corresponding platform device is typically registered in
+ * drivers/platform/chrome/wilco_ec/core.c
+ */
+
+#include <linux/bcd.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/rtc.h>
+#include <linux/timekeeping.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;
+
+static int wilco_ec_rtc_read(struct device *dev, struct rtc_time *tm)
+{
+       struct wilco_ec_device *ec = dev_get_drvdata(dev->parent);
+       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),
+       };
+       int ret;
+
+       ret = wilco_ec_mailbox(ec, &msg);
+       if (ret < 0)
+               return ret;
+
+       tm->tm_sec      = rtc.second;
+       tm->tm_min      = rtc.minute;
+       tm->tm_hour     = rtc.hour;
+       tm->tm_mday     = rtc.day;
+       tm->tm_mon      = rtc.month - 1;
+       tm->tm_year     = rtc.year + (rtc.century * 100) - 1900;
+       tm->tm_yday     = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+       /* Don't compute day of week, we don't need it. */
+       tm->tm_wday = -1;
+
+       return 0;
+}
+
+static int wilco_ec_rtc_write(struct device *dev, struct rtc_time *tm)
+{
+       struct wilco_ec_device *ec = dev_get_drvdata(dev->parent);
+       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
+        * We DO need to set weekday because the EC controls battery charging
+        * schedules that depend on the day of the week.
+        */
+       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)
+               return ret;
+
+       return 0;
+}
+
+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_rtc_probe(struct platform_device *pdev)
+{
+       struct rtc_device *rtc;
+
+       rtc = devm_rtc_allocate_device(&pdev->dev);
+       if (IS_ERR(rtc))
+               return PTR_ERR(rtc);
+
+       rtc->ops = &wilco_ec_rtc_ops;
+       /* EC only supports this century */
+       rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
+       rtc->range_max = RTC_TIMESTAMP_END_2099;
+       rtc->owner = THIS_MODULE;
+
+       return rtc_register_device(rtc);
+}
+
+static struct platform_driver wilco_ec_rtc_driver = {
+       .driver = {
+               .name = "rtc-wilco-ec",
+       },
+       .probe = wilco_ec_rtc_probe,
+};
+
+module_platform_driver(wilco_ec_rtc_driver);
+
+MODULE_ALIAS("platform:rtc-wilco-ec");
+MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Wilco EC RTC driver");
index 5344975afa1a985b6d455dbaf926a28fed2adc4b..446473a46b88d3d623bf235a7f0508610b2d085c 100644 (file)
@@ -35,6 +35,7 @@
  *               is used to hold the request and the response.
  * @data_size: Size of the data buffer used for EC communication.
  * @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
+ * @rtc_pdev: The child platform_device used by the RTC sub-driver.
  */
 struct wilco_ec_device {
        struct device *dev;
@@ -45,6 +46,7 @@ struct wilco_ec_device {
        void *data_buffer;
        size_t data_size;
        struct platform_device *debugfs_pdev;
+       struct platform_device *rtc_pdev;
 };
 
 /**