[RFC PATCH 05/14] rtc: Add RTC driver of ACPI Time and Alarm Device

From: Lee, Chun-Yi
Date: Thu Dec 19 2013 - 02:54:10 EST


This patch add the RTC driver of ACPI TAD to provide userspace access
ACPI time through RTC interface.

Signed-off-by: Lee, Chun-Yi <jlee@xxxxxxxx>
---
drivers/rtc/Kconfig | 10 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-acpitad.c | 294 +++++++++++++++++++++++++++++++++++++++++++++
drivers/rtc/rtc-dev.c | 4 +
drivers/rtc/rtc-sysfs.c | 8 ++
include/linux/rtc.h | 5 +
include/uapi/linux/rtc.h | 5 +
7 files changed, 327 insertions(+), 0 deletions(-)
create mode 100644 drivers/rtc/rtc-acpitad.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 0077302..349dbc4 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -878,6 +878,16 @@ config RTC_DRV_NUC900
If you say yes here you get support for the RTC subsystem of the
NUC910/NUC920 used in embedded systems.

+config RTC_ACPI_TAD
+ tristate "RTC ACPI Time and Alarm Device driver"
+ help
+ This driver exposes ACPI 5.0 Time and Alarm Device as RTC device.
+ Say Y (or M) if you have a computer with ACPI 5.0 firmware that
+ implemented Time and Alarm Device.
+
+ To compile this driver as a module, choose M here:
+ the module will be called rtc_acpitad.
+
comment "on-CPU RTC drivers"

config RTC_DRV_DAVINCI
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 27b4bd8..bca5ab3 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-88pm860x.o
obj-$(CONFIG_RTC_DRV_88PM80X) += rtc-88pm80x.o
obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o
obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o
+obj-$(CONFIG_RTC_ACPI_TAD) += rtc-acpitad.o
obj-$(CONFIG_RTC_DRV_AS3722) += rtc-as3722.o
obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o
obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
diff --git a/drivers/rtc/rtc-acpitad.c b/drivers/rtc/rtc-acpitad.c
new file mode 100644
index 0000000..065a033
--- /dev/null
+++ b/drivers/rtc/rtc-acpitad.c
@@ -0,0 +1,294 @@
+/* A RTC driver for ACPI 5.0 Time and Alarm Device
+ *
+ * Copyright (C) 2013 SUSE Linux Products GmbH. All rights reserved.
+ * Written by Lee, Chun-Yi (jlee@xxxxxxxx)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+
+/*
+ * returns day of the year [0-365]
+ */
+static inline int
+compute_yday(struct acpi_time *acpit)
+{
+ /* acpi_time.month is in the [1-12] so, we need -1 */
+ return rtc_year_days(acpit->day, acpit->month - 1, acpit->year);
+}
+
+/*
+ * returns day of the week [0-6] 0=Sunday
+ */
+static int
+compute_wday(struct acpi_time *acpit)
+{
+ int y;
+ int ndays = 0;
+
+ if (acpit->year < 1900) {
+ pr_err("ACPI year < 1900, invalid date\n");
+ return -1;
+ }
+
+ for (y = 1900; y < acpit->year; y++)
+ ndays += 365 + (is_leap_year(y) ? 1 : 0);
+
+ ndays += compute_yday(acpit);
+
+ /*
+ * 1=1/1/1900 was a Monday
+ */
+ return (ndays + 1) % 7;
+}
+
+static void
+convert_to_acpi_time(struct rtc_time *tm, struct acpi_time *acpit)
+{
+ acpit->year = tm->tm_year + 1900;
+ acpit->month = tm->tm_mon + 1;
+ acpit->day = tm->tm_mday;
+ acpit->hour = tm->tm_hour;
+ acpit->minute = tm->tm_min;
+ acpit->second = tm->tm_sec;
+ acpit->milliseconds = 0;
+ acpit->daylight = tm->tm_isdst ? ACPI_ISDST : 0;
+}
+
+static void
+convert_from_acpi_time(struct acpi_time *acpit, struct rtc_time *tm)
+{
+ memset(tm, 0, sizeof(*tm));
+ tm->tm_sec = acpit->second;
+ tm->tm_min = acpit->minute;
+ tm->tm_hour = acpit->hour;
+ tm->tm_mday = acpit->day;
+ tm->tm_mon = acpit->month - 1;
+ tm->tm_year = acpit->year - 1900;
+
+ /* day of the week [0-6], Sunday=0 */
+ tm->tm_wday = compute_wday(acpit);
+
+ /* day in the year [1-365]*/
+ tm->tm_yday = compute_yday(acpit);
+
+ switch (acpit->daylight & ACPI_ISDST) {
+ case ACPI_ISDST:
+ tm->tm_isdst = 1;
+ break;
+ case ACPI_TIME_AFFECTED_BY_DAYLIGHT:
+ tm->tm_isdst = 0;
+ break;
+ default:
+ tm->tm_isdst = -1;
+ }
+}
+
+static int acpitad_read_gmtoff(struct device *dev, long int *arg)
+{
+ struct acpi_time *acpit;
+ s16 timezone;
+ int ret;
+
+ acpit = kzalloc(sizeof(struct acpi_time), GFP_KERNEL);
+ if (!acpit)
+ return -ENOMEM;
+
+ ret = acpi_read_time(acpit);
+ if (ret)
+ goto error_read;
+
+ /* transfer minutes to seconds east of UTC for userspace */
+ timezone = (s16)le16_to_cpu(acpit->timezone);
+ *arg = ACPI_UNSPECIFIED_TIMEZONE * 60;
+ if (abs(timezone) != ACPI_UNSPECIFIED_TIMEZONE &&
+ abs(timezone) <= 1440)
+ *arg = timezone * 60 * -1;
+
+error_read:
+ kfree(acpit);
+
+ return ret;
+}
+
+
+static int acpitad_set_gmtoff(struct device *dev, long int arg)
+{
+ struct acpi_time *acpit;
+ s16 timezone;
+ int ret;
+
+ /* transfer seconds east of UTC to minutes for ACPI */
+ timezone = arg / 60 * -1;
+ if (abs(timezone) > 1440 &&
+ abs(timezone) != ACPI_UNSPECIFIED_TIMEZONE)
+ return -EINVAL;
+
+ /* can not use -2047 */
+ if (timezone == ACPI_UNSPECIFIED_TIMEZONE * -1)
+ timezone = ACPI_UNSPECIFIED_TIMEZONE;
+
+ acpit = kzalloc(sizeof(struct acpi_time), GFP_KERNEL);
+ if (!acpit)
+ return -ENOMEM;
+
+ ret = acpi_read_time(acpit);
+ if (ret)
+ goto error_read;
+
+ acpit->timezone = (s16)cpu_to_le16(timezone);
+ ret = acpi_set_time(acpit);
+
+error_read:
+ kfree(acpit);
+
+ return ret;
+}
+
+static int acpitad_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ long int gmtoff;
+ int err;
+
+ switch (cmd) {
+ case RTC_RD_GMTOFF:
+ err = acpitad_read_gmtoff(dev, &gmtoff);
+ if (err)
+ return err;
+ return put_user(gmtoff, (unsigned long __user *)arg);
+ case RTC_SET_GMTOFF:
+ return acpitad_set_gmtoff(dev, arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static int acpitad_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct acpi_time *acpit;
+ int ret;
+
+ acpit = kzalloc(sizeof(struct acpi_time), GFP_KERNEL);
+ if (!acpit)
+ return -ENOMEM;
+
+ ret = acpi_read_time(acpit);
+ if (ret)
+ return ret;
+
+ convert_from_acpi_time(acpit, tm);
+
+ return rtc_valid_tm(tm);
+}
+
+static int acpitad_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct acpi_time *acpit;
+ int ret;
+
+ acpit = kzalloc(sizeof(struct acpi_time), GFP_KERNEL);
+ if (!acpit)
+ return -ENOMEM;
+
+ /* read current timzone to avoid overwrite it by set time */
+ ret = acpi_read_time(acpit);
+ if (ret)
+ goto error_read;
+
+ convert_to_acpi_time(tm, acpit);
+
+ ret = acpi_set_time(acpit);
+
+error_read:
+ kfree(acpit);
+ return ret;
+}
+
+static struct rtc_class_ops acpi_rtc_ops = {
+ .ioctl = acpitad_rtc_ioctl,
+ .read_time = acpitad_read_time,
+ .set_time = acpitad_set_time,
+};
+
+static int acpitad_rtc_probe(struct platform_device *dev)
+{
+ unsigned long cap;
+ struct rtc_device *rtc;
+ int ret;
+
+ ret = acpi_tad_get_capability(&cap);
+ if (ret)
+ return ret;
+
+ if (!(cap & TAD_CAP_GETSETTIME)) {
+ acpi_rtc_ops.read_time = NULL;
+ acpi_rtc_ops.set_time = NULL;
+ pr_warn("No get/set time support\n");
+ }
+
+ /* ACPI Alarm at least need AC wake capability */
+ if (!(cap & TAD_CAP_ACWAKE)) {
+ acpi_rtc_ops.read_alarm = NULL;
+ acpi_rtc_ops.set_alarm = NULL;
+ pr_warn("No AC wake support\n");
+ }
+
+ /* register rtc device */
+ rtc = rtc_device_register("rtc-acpitad", &dev->dev, &acpi_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(rtc))
+ return PTR_ERR(rtc);
+
+ rtc->uie_unsupported = 1;
+ rtc->caps = (RTC_TZ_CAP | RTC_DST_CAP);
+ platform_set_drvdata(dev, rtc);
+
+ return 0;
+}
+
+static int acpitad_rtc_remove(struct platform_device *dev)
+{
+ struct rtc_device *rtc = platform_get_drvdata(dev);
+
+ rtc_device_unregister(rtc);
+
+ return 0;
+}
+
+static struct platform_driver acpitad_rtc_driver = {
+ .driver = {
+ .name = "rtc-acpitad",
+ .owner = THIS_MODULE,
+ },
+ .probe = acpitad_rtc_probe,
+ .remove = acpitad_rtc_remove,
+};
+
+static int __init acpitad_rtc_init(void)
+{
+ return platform_driver_register(&acpitad_rtc_driver);
+}
+
+static void __exit acpitad_rtc_exit(void)
+{
+ platform_driver_unregister(&acpitad_rtc_driver);
+}
+
+module_init(acpitad_rtc_init);
+module_exit(acpitad_rtc_exit);
+
+MODULE_AUTHOR("Lee, Chun-Yi <jlee@xxxxxxxx>");
+MODULE_DESCRIPTION("RTC ACPI Time and Alarm Device driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rtc-acpitad");
diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c
index d049393..aab70e7 100644
--- a/drivers/rtc/rtc-dev.c
+++ b/drivers/rtc/rtc-dev.c
@@ -398,6 +398,10 @@ static long rtc_dev_ioctl(struct file *file,
err = -EFAULT;
return err;

+ case RTC_CAPS_READ:
+ err = put_user(rtc->caps, (unsigned int __user *)uarg);
+ break;
+
default:
/* Finally try the driver's ioctl interface */
if (ops->ioctl) {
diff --git a/drivers/rtc/rtc-sysfs.c b/drivers/rtc/rtc-sysfs.c
index babd43b..bdffb8f 100644
--- a/drivers/rtc/rtc-sysfs.c
+++ b/drivers/rtc/rtc-sysfs.c
@@ -122,6 +122,13 @@ hctosys_show(struct device *dev, struct device_attribute *attr, char *buf)
}
static DEVICE_ATTR_RO(hctosys);

+static ssize_t
+caps_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", to_rtc_device(dev)->caps);
+}
+static DEVICE_ATTR_RO(caps);
+
static struct attribute *rtc_attrs[] = {
&dev_attr_name.attr,
&dev_attr_date.attr,
@@ -129,6 +136,7 @@ static struct attribute *rtc_attrs[] = {
&dev_attr_since_epoch.attr,
&dev_attr_max_user_freq.attr,
&dev_attr_hctosys.attr,
+ &dev_attr_caps.attr,
NULL,
};
ATTRIBUTE_GROUPS(rtc);
diff --git a/include/linux/rtc.h b/include/linux/rtc.h
index c2c2897..e6380ec 100644
--- a/include/linux/rtc.h
+++ b/include/linux/rtc.h
@@ -116,6 +116,11 @@ struct rtc_device
/* Some hardware can't support UIE mode */
int uie_unsupported;

+ /* Time Zone and Daylight capabilities */
+#define RTC_TZ_CAP (1 << 0)
+#define RTC_DST_CAP (1 << 1)
+ int caps;
+
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;
struct timer_list uie_timer;
diff --git a/include/uapi/linux/rtc.h b/include/uapi/linux/rtc.h
index f8c82e6..5533914 100644
--- a/include/uapi/linux/rtc.h
+++ b/include/uapi/linux/rtc.h
@@ -94,6 +94,11 @@ struct rtc_pll_info {
#define RTC_VL_READ _IOR('p', 0x13, int) /* Voltage low detector */
#define RTC_VL_CLR _IO('p', 0x14) /* Clear voltage low information */

+#define RTC_RD_GMTOFF _IOR('p', 0x15, long int) /* Read time zone return seconds east of UTC */
+#define RTC_SET_GMTOFF _IOW('p', 0x16, long int) /* Set time zone input seconds east of UTC */
+
+#define RTC_CAPS_READ _IOR('p', 0x17, unsigned int) /* Get capabilities, e.g. TZ, DST */
+
/* interrupt flags */
#define RTC_IRQF 0x80 /* Any of the following is active */
#define RTC_PF 0x40 /* Periodic interrupt */
--
1.6.4.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/