Message ID | 6419044.RXe4JATo1K@aspire.rjw.lan (mailing list archive) |
---|---|
State | Accepted, archived |
Delegated to: | Rafael Wysocki |
Headers | show |
On Fri, Mar 16, 2018 at 01:51:01PM +0100, Rafael J. Wysocki wrote: > From: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > > Introduce a driver for the ACPI Time and Alarm Device (TAD) based on > Section 9.18 of ACPI 6.2. Does UEFI bios for CNL support it already? Perhaps we can try this out and share some feedback. Some minor styling related comments below. > > This driver only supports the system wakeup capabilities of the TAD > which are mandatory. Support for the RTC capabilities of the TAD I guess two whitespaces are put intentionally. IMO, even one looks as good or better. > will be added to it in the future. > > This driver is entirely sysfs-based. It provides attributes (under Ditto > the TAD platform device) to allow user space to manage the AC and DC > wakeup timers of the TAD: set and read their values, set and check > their expire timer wake policies, check and clear their status and > check the capabilities of the TAD reported by AML. The DC timer Ditto, and elsewhere too below. > attributes are only present if the TAD supports a separate DC alarm > timer. > > The wakeup events handling and power management of the TAD is > expected to be taken care of by the ACPI PM domain attached to its > platform device. > > Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > --- > Documentation/ABI/testing/sysfs-devices-platform-ACPI-TAD | 113 +++ > drivers/acpi/Kconfig | 14 > drivers/acpi/Makefile | 1 > drivers/acpi/acpi_tad.c | 473 ++++++++++++++ > 4 files changed, 601 insertions(+) Please update MAINTAINERS too. > > Index: linux-pm/drivers/acpi/Kconfig > =================================================================== > --- linux-pm.orig/drivers/acpi/Kconfig > +++ linux-pm/drivers/acpi/Kconfig > @@ -217,6 +217,20 @@ config ACPI_FAN > To compile this driver as a module, choose M here: > the module will be called fan. > > +config ACPI_TAD > + tristate "ACPI Time and Alarm (TAD) Device Support" > + depends on SYSFS && PM_SLEEP > + default n This may be removed. > + help > + The ACPI Time and Alarm (TAD) device is an alternative to the Real > + Time Clock (RTC). Its wake timers allow the system to transition from > + the S3 (or optionally S4/S5) state to S0 state after a time period > + elapses. In comparison with the RTC Alarm, the TAD provides a larger > + scale of flexibility in the wake timers. The time capabilities of the > + TAD maintain the time of day information across platform power > + transitions, and keep track of time even when the platform is turned > + off. > + > config ACPI_DOCK > bool "Dock" > help > Index: linux-pm/drivers/acpi/Makefile > =================================================================== > --- linux-pm.orig/drivers/acpi/Makefile > +++ linux-pm/drivers/acpi/Makefile > @@ -70,6 +70,7 @@ obj-$(CONFIG_ACPI_AC) += ac.o > obj-$(CONFIG_ACPI_BUTTON) += button.o > obj-$(CONFIG_ACPI_FAN) += fan.o > obj-$(CONFIG_ACPI_VIDEO) += video.o > +obj-$(CONFIG_ACPI_TAD) += acpi_tad.o > obj-$(CONFIG_ACPI_PCI_SLOT) += pci_slot.o > obj-$(CONFIG_ACPI_PROCESSOR) += processor.o > obj-$(CONFIG_ACPI) += container.o > Index: linux-pm/drivers/acpi/acpi_tad.c > =================================================================== > --- /dev/null > +++ linux-pm/drivers/acpi/acpi_tad.c > @@ -0,0 +1,473 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * ACPI Time and Alarm (TAD) Device Driver > + * > + * Copyright (C) 2018 Intel Corporation > + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > + * > + * This driver is based on Section 9.18 of the ACPI 6.2 specification revision. > + * > + * It only supports the system wakeup capabilities of the TAD. > + * > + * Provided are sysfs attributes, available under the TAD platform device, > + * allowing user space to manage the AC and DC wakeup timers of the TAD: > + * set and read their values, set and check their expire timer wake policies, > + * check and clear their status and check the capabilities of the TAD reported > + * by AML. The DC timer attributes are only present if the TAD supports a > + * separate DC alarm timer. > + * > + * The wakeup events handling and power management of the TAD is expected to > + * be taken care of by the ACPI PM domain attached to its platform device. > + */ > + > +#include <linux/acpi.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/suspend.h> > + > +MODULE_LICENSE("GPL v2"); > +MODULE_AUTHOR("Rafael J. Wysocki"); > + > +/* ACPI TAD capability flags (ACPI 6.2, Section 9.18.2) */ > +#define ACPI_TAD_AC_WAKE BIT(0) > +#define ACPI_TAD_DC_WAKE BIT(1) > +#define ACPI_TAD_RT BIT(2) > +#define ACPI_TAD_RT_IN_MS BIT(3) > +#define ACPI_TAD_S4_S5__GWS BIT(4) > +#define ACPI_TAD_AC_S4_WAKE BIT(5) > +#define ACPI_TAD_AC_S5_WAKE BIT(6) > +#define ACPI_TAD_DC_S4_WAKE BIT(7) > +#define ACPI_TAD_DC_S5_WAKE BIT(8) > + > +/* ACPI TAD alarm timer selection */ > +#define ACPI_TAD_AC_TIMER (u32)0 > +#define ACPI_TAD_DC_TIMER (u32)1 > + > +/* Special value for disabled timer or expired timer wake policy. */ > +#define ACPI_TAD_WAKE_DISABLED (~(u32)0) > + > +struct acpi_tad_driver_data { > + u32 capabilities; > +}; > + > +static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, > + u32 value) > +{ > + acpi_handle handle = ACPI_HANDLE(dev); > + union acpi_object args[] = { > + { .type = ACPI_TYPE_INTEGER, }, > + { .type = ACPI_TYPE_INTEGER, }, > + }; > + struct acpi_object_list arg_list = { > + .pointer = args, > + .count = ARRAY_SIZE(args), > + }; > + unsigned long long retval; > + acpi_status status; though its a matter of personal preference but these can be moved up and a newline after struct closing brace. > + > + args[0].integer.value = timer_id; > + args[1].integer.value = value; > + > + pm_runtime_get_sync(dev); > + > + status = acpi_evaluate_integer(handle, method, &arg_list, &retval); > + > + pm_runtime_put_sync(dev); > + > + if (ACPI_FAILURE(status) || retval) > + return -EIO; > + > + return 0; > +} > + > +static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method, Going abovbe 80. > + u32 timer_id, const char *specval) > +{ > + u32 value; > + > + if (sysfs_streq(buf, specval)) { > + value = ACPI_TAD_WAKE_DISABLED; > + } else { > + int ret = kstrtou32(buf, 0, &value); > + > + if (ret) > + return ret; > + > + if (value == ACPI_TAD_WAKE_DISABLED) > + return -EINVAL; > + } > + > + return acpi_tad_wake_set(dev, method, timer_id, value); > +} > + > +static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, > + u32 timer_id, const char *specval) > +{ > + acpi_handle handle = ACPI_HANDLE(dev); > + union acpi_object args[] = { > + { .type = ACPI_TYPE_INTEGER, }, > + }; > + struct acpi_object_list arg_list = { > + .pointer = args, > + .count = ARRAY_SIZE(args), > + }; > + unsigned long long retval; > + acpi_status status; > + > + args[0].integer.value = timer_id; > + > + pm_runtime_get_sync(dev); > + > + status = acpi_evaluate_integer(handle, method, &arg_list, &retval); > + > + pm_runtime_put_sync(dev); > + > + if (ACPI_FAILURE(status)) > + return -EIO; > + > + if ((u32)retval == ACPI_TAD_WAKE_DISABLED) > + return sprintf(buf, "%s\n", specval); > + > + return sprintf(buf, "%u\n", (u32)retval); > +} > + > +static const char *alarm_specval = "disabled"; > + > +static int acpi_tad_alarm_write(struct device *dev, const char *buf, > + u32 timer_id) > +{ > + return acpi_tad_wake_write(dev, buf, "_STV", timer_id, alarm_specval); > +} > + > +static ssize_t acpi_tad_alarm_read(struct device *dev, char *buf, u32 timer_id) > +{ > + return acpi_tad_wake_read(dev, buf, "_TIV", timer_id, alarm_specval); > +} > + > +static const char *policy_specval = "never"; > + > +static int acpi_tad_policy_write(struct device *dev, const char *buf, > + u32 timer_id) > +{ > + return acpi_tad_wake_write(dev, buf, "_STP", timer_id, policy_specval); > +} > + > +static ssize_t acpi_tad_policy_read(struct device *dev, char *buf, u32 timer_id) > +{ > + return acpi_tad_wake_read(dev, buf, "_TIP", timer_id, policy_specval); > +} > + > +static int acpi_tad_clear_status(struct device *dev, u32 timer_id) > +{ > + acpi_handle handle = ACPI_HANDLE(dev); > + union acpi_object args[] = { > + { .type = ACPI_TYPE_INTEGER, }, > + }; > + struct acpi_object_list arg_list = { > + .pointer = args, > + .count = ARRAY_SIZE(args), > + }; > + unsigned long long retval; > + acpi_status status; > + > + args[0].integer.value = timer_id; > + > + pm_runtime_get_sync(dev); > + > + status = acpi_evaluate_integer(handle, "_CWS", &arg_list, &retval); > + > + pm_runtime_put_sync(dev); > + > + if (ACPI_FAILURE(status) || retval) > + return -EIO; > + > + return 0; > +} > + > +static int acpi_tad_status_write(struct device *dev, const char *buf, u32 timer_id) > +{ > + int ret, value; > + > + ret = kstrtoint(buf, 0, &value); > + if (ret) > + return ret; > + > + if (value) > + return -EINVAL; > + > + return acpi_tad_clear_status(dev, timer_id); > +} > + > +static ssize_t acpi_tad_status_read(struct device *dev, char *buf, u32 timer_id) > +{ > + acpi_handle handle = ACPI_HANDLE(dev); > + union acpi_object args[] = { > + { .type = ACPI_TYPE_INTEGER, }, > + }; > + struct acpi_object_list arg_list = { > + .pointer = args, > + .count = ARRAY_SIZE(args), > + }; > + unsigned long long retval; > + acpi_status status; > + > + args[0].integer.value = timer_id; > + > + pm_runtime_get_sync(dev); > + > + status = acpi_evaluate_integer(handle, "_GWS", &arg_list, &retval); > + > + pm_runtime_put_sync(dev); > + > + if (ACPI_FAILURE(status)) > + return -EIO; > + > + return sprintf(buf, "0x%02X\n", (u32)retval); > +} > + > +static ssize_t caps_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct acpi_tad_driver_data *dd = dev_get_drvdata(dev); > + > + return sprintf(buf, "0x%02X\n", dd->capabilities); > +} > + > +static DEVICE_ATTR_RO(caps); > + > +static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int ret = acpi_tad_alarm_write(dev, buf, ACPI_TAD_AC_TIMER); > + > + return ret ? ret : count; > +} > + > +static ssize_t ac_alarm_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + return acpi_tad_alarm_read(dev, buf, ACPI_TAD_AC_TIMER); > +} > + > +static DEVICE_ATTR(ac_alarm, S_IRUSR | S_IWUSR, ac_alarm_show, ac_alarm_store); > + > +static ssize_t ac_policy_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int ret = acpi_tad_policy_write(dev, buf, ACPI_TAD_AC_TIMER); > + > + return ret ? ret : count; > +} > + > +static ssize_t ac_policy_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + return acpi_tad_policy_read(dev, buf, ACPI_TAD_AC_TIMER); > +} > + > +static DEVICE_ATTR(ac_policy, S_IRUSR | S_IWUSR, ac_policy_show, ac_policy_store); > + > +static ssize_t ac_status_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int ret = acpi_tad_status_write(dev, buf, ACPI_TAD_AC_TIMER); > + > + return ret ? ret : count; > +} > + > +static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + return acpi_tad_status_read(dev, buf, ACPI_TAD_AC_TIMER); > +} > + > +static DEVICE_ATTR(ac_status, S_IRUSR | S_IWUSR, ac_status_show, ac_status_store); > + > +static struct attribute *acpi_tad_attrs[] = { > + &dev_attr_caps.attr, > + &dev_attr_ac_alarm.attr, > + &dev_attr_ac_policy.attr, > + &dev_attr_ac_status.attr, > + NULL, > +}; > +static const struct attribute_group acpi_tad_attr_group = { > + .attrs = acpi_tad_attrs, > +}; > + > +static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int ret = acpi_tad_alarm_write(dev, buf, ACPI_TAD_DC_TIMER); > + > + return ret ? ret : count; > +} > + > +static ssize_t dc_alarm_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + return acpi_tad_alarm_read(dev, buf, ACPI_TAD_DC_TIMER); > +} > + > +static DEVICE_ATTR(dc_alarm, S_IRUSR | S_IWUSR, dc_alarm_show, dc_alarm_store); > + > +static ssize_t dc_policy_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int ret = acpi_tad_policy_write(dev, buf, ACPI_TAD_DC_TIMER); > + > + return ret ? ret : count; > +} > + > +static ssize_t dc_policy_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + return acpi_tad_policy_read(dev, buf, ACPI_TAD_DC_TIMER); > +} > + > +static DEVICE_ATTR(dc_policy, S_IRUSR | S_IWUSR, dc_policy_show, dc_policy_store); > + > +static ssize_t dc_status_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int ret = acpi_tad_status_write(dev, buf, ACPI_TAD_DC_TIMER); > + > + return ret ? ret : count; > +} > + > +static ssize_t dc_status_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + return acpi_tad_status_read(dev, buf, ACPI_TAD_DC_TIMER); > +} > + > +static DEVICE_ATTR(dc_status, S_IRUSR | S_IWUSR, dc_status_show, dc_status_store); > + > +static struct attribute *acpi_tad_dc_attrs[] = { > + &dev_attr_dc_alarm.attr, > + &dev_attr_dc_policy.attr, > + &dev_attr_dc_status.attr, > + NULL, > +}; Newline. > +static const struct attribute_group acpi_tad_dc_attr_group = { > + .attrs = acpi_tad_dc_attrs, > +}; > + > +static int acpi_tad_disable_timer(struct device *dev, u32 timer_id) > +{ > + return acpi_tad_wake_set(dev, "_STV", timer_id, ACPI_TAD_WAKE_DISABLED); > +} > + > +static int acpi_tad_remove(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct acpi_tad_driver_data *dd = dev_get_drvdata(dev); > + > + device_init_wakeup(dev, false); > + > + pm_runtime_get_sync(dev); > + > + if (dd->capabilities & ACPI_TAD_DC_WAKE) > + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); > + > + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); > + > + acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); > + acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); > + if (dd->capabilities & ACPI_TAD_DC_WAKE) { > + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); > + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); > + } > + > + pm_runtime_put_sync(dev); > + pm_runtime_disable(dev); > + return 0; > +} > + > +static int acpi_tad_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + acpi_handle handle = ACPI_HANDLE(dev); > + struct acpi_tad_driver_data *dd; > + acpi_status status; > + unsigned long long caps; > + int ret; > + > + /* > + * Initialization failure messages are mostly about firmware issues, so > + * print them at the "info" level. > + */ > + status = acpi_evaluate_integer(handle, "_GCP", NULL, &caps); > + if (ACPI_FAILURE(status)) { > + dev_info(dev, "Unable to get capabilities\n"); > + return -ENODEV; > + } > + > + if (!(caps & ACPI_TAD_AC_WAKE)) { > + dev_info(dev, "Unsupported capabilities\n"); > + return -ENODEV; > + } > + > + if (!acpi_has_method(handle, "_PRW")) { > + dev_info(dev, "Missing _PRW\n"); > + return -ENODEV; > + } > + > + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); > + if (!dd) > + return -ENOMEM; > + > + dd->capabilities = caps; > + dev_set_drvdata(dev, dd); > + > + /* > + * Assume that the ACPI PM domain has been attached to the device and > + * simply enable system wakeup and runtime PM and put the device into > + * runtime suspend. Everything else should be taken care of by the ACPI > + * PM domain callbacks. > + */ > + device_init_wakeup(dev, true); > + dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | > + DPM_FLAG_LEAVE_SUSPENDED); > + /* > + * The platform bus type layer tells the ACPI PM domain powers up the > + * device, so set the runtime PM status of it to "active". > + */ > + pm_runtime_set_active(dev); > + pm_runtime_enable(dev); > + pm_runtime_suspend(dev); > + > + ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); > + if (ret) > + goto fail; > + > + if (caps & ACPI_TAD_DC_WAKE) { > + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); > + if (ret) > + goto fail; > + } > + > + return 0; > + > +fail: > + acpi_tad_remove(pdev); > + return ret; > +} > + > +static const struct acpi_device_id acpi_tad_ids[] = { > + {"ACPI000E", 0}, > + {} > +}; > + > +static struct platform_driver acpi_tad_driver = { > + .driver = { > + .name = "acpi-tad", > + .acpi_match_table = acpi_tad_ids, > + }, > + .probe = acpi_tad_probe, > + .remove = acpi_tad_remove, > +}; > +MODULE_DEVICE_TABLE(acpi, acpi_tad_ids); > + > +module_platform_driver(acpi_tad_driver); > Index: linux-pm/Documentation/ABI/testing/sysfs-devices-platform-ACPI-TAD > =================================================================== > --- /dev/null > +++ linux-pm/Documentation/ABI/testing/sysfs-devices-platform-ACPI-TAD > @@ -0,0 +1,113 @@ > + ACPI Time and Alarm (TAD) device attributes. > + > +What: /sys/bus/platform/devices/ACPI000E:00/caps > +Date: March 2018 > +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > +Description: > + (RO) Hexadecimal bitmask of the TAD attributes are reported by > + the platform firmware (see ACPI 6.2, section 9.18.2): > + > + BIT(0): AC wakeup implemented if set > + BIT(1): DC wakeup implemented if set > + BIT(2): Get/set real time features implemented if set > + BIT(3): Real time accuracy in milliseconds if set > + BIT(4): Correct status reported for wakeups from S4/S5 if set > + BIT(5): The AC timer wakes up from S4 if set > + BIT(6): The AC timer wakes up from S5 if set > + BIT(7): The DC timer wakes up from S4 if set > + BIT(8): The DC timer wakes up from S5 if set > + > + The other bits are reserved. > + > +What: /sys/bus/platform/devices/ACPI000E:00/ac_alarm > +Date: March 2018 > +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > +Description: > + (RW) The AC alarm timer value. > + > + Reads return the current AC alarm timer value in seconds or > + "disabled", if the AC alarm is not set to wake up the system. > + > + Write a new AC alarm timer value in seconds or "disabled" to it > + to set the AC alarm timer or to disable it, respectively. > + > + If the AC alarm timer is set through this attribute and it > + expires, it will immediately wake up the system from the S3 > + sleep state (and from S4/S5 too if supported) until its status > + is explicitly cleared via the ac_status attribute. > + > +What: /sys/bus/platform/devices/ACPI000E:00/ac_policy > +Date: March 2018 > +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > +Description: > + (RW) The AC alarm expired timer wake policy (see ACPI 6.2, > + Section 9.18 for details). > + > + Reads return the current expired timer wake delay for the AC > + alarm timer or "never", if the policy is to discard AC timer > + wakeups if the system is on DC power. > + > + Write a new expired timer wake delay for the AC alarm timer in > + seconds or "never" to it to set the expired timer wake delay for > + the AC alarm timer or to set its expired wake policy to discard > + wakeups if the system is on DC power, respectively. > + > +What: /sys/bus/platform/devices/ACPI000E:00/ac_status > +Date: March 2018 > +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > +Description: > + (RW) The AC alarm status. > + > + Reads return a hexadecimal bitmask representing the AC alarm > + timer status with the following meaning of bits (see ACPI 6.2, > + Section 9.18.5): > + > + Bit(0): The timer has expired if set. > + Bit(1): The timer has woken up the system from a sleep state > + (S3 or S4/S5 if supported) if set. > + > + The other bits are reserved. > + > + Reads also cause the AC alarm timer status to be reset. > + > + Another way to reset the the status of the AC alarm timer is to > + write (the number) 0 to this file. > + > + If the status return value indicates that the timer has expired, > + it will immediately wake up the system from the S3 sleep state > + (and from S4/S5 too if supported) until its status is explicitly > + cleared through this attribute. > + > +What: /sys/bus/platform/devices/ACPI000E:00/dc_alarm > +Date: March 2018 > +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > +Description: > + (RW,optional) The DC alarm timer value. > + > + This attribute is only present if the TAD supports a separate > + DC timer. > + > + It is analogous to the ac_alarm attribute. > + > +What: /sys/bus/platform/devices/ACPI000E:00/dc_policy > +Date: March 2018 > +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > +Description: > + (RW,optional) The DC alarm expired timer wake policy. > + > + This attribute is only present if the TAD supports a separate > + DC timer. > + > + It is analogous to the ac_policy attribute. > + > +What: /sys/bus/platform/devices/ACPI000E:00/dc_status > +Date: March 2018 > +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > + > +Description: > + (RW,optional) The DC alarm status. > + > + This attribute is only present if the TAD supports a separate > + DC timer. > + > + It is analogous to the ac_status attribute. >
On Fri, Mar 16, 2018 at 01:51:01PM +0100, Rafael J. Wysocki wrote: > From: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > > Introduce a driver for the ACPI Time and Alarm Device (TAD) based on > Section 9.18 of ACPI 6.2. > > This driver only supports the system wakeup capabilities of the TAD > which are mandatory. Support for the RTC capabilities of the TAD > will be added to it in the future. > > This driver is entirely sysfs-based. It provides attributes (under > the TAD platform device) to allow user space to manage the AC and DC > wakeup timers of the TAD: set and read their values, set and check > their expire timer wake policies, check and clear their status and > check the capabilities of the TAD reported by AML. The DC timer > attributes are only present if the TAD supports a separate DC alarm > timer. > > The wakeup events handling and power management of the TAD is > expected to be taken care of by the ACPI PM domain attached to its > platform device. > > Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com> -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Index: linux-pm/drivers/acpi/Kconfig =================================================================== --- linux-pm.orig/drivers/acpi/Kconfig +++ linux-pm/drivers/acpi/Kconfig @@ -217,6 +217,20 @@ config ACPI_FAN To compile this driver as a module, choose M here: the module will be called fan. +config ACPI_TAD + tristate "ACPI Time and Alarm (TAD) Device Support" + depends on SYSFS && PM_SLEEP + default n + help + The ACPI Time and Alarm (TAD) device is an alternative to the Real + Time Clock (RTC). Its wake timers allow the system to transition from + the S3 (or optionally S4/S5) state to S0 state after a time period + elapses. In comparison with the RTC Alarm, the TAD provides a larger + scale of flexibility in the wake timers. The time capabilities of the + TAD maintain the time of day information across platform power + transitions, and keep track of time even when the platform is turned + off. + config ACPI_DOCK bool "Dock" help Index: linux-pm/drivers/acpi/Makefile =================================================================== --- linux-pm.orig/drivers/acpi/Makefile +++ linux-pm/drivers/acpi/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_ACPI_AC) += ac.o obj-$(CONFIG_ACPI_BUTTON) += button.o obj-$(CONFIG_ACPI_FAN) += fan.o obj-$(CONFIG_ACPI_VIDEO) += video.o +obj-$(CONFIG_ACPI_TAD) += acpi_tad.o obj-$(CONFIG_ACPI_PCI_SLOT) += pci_slot.o obj-$(CONFIG_ACPI_PROCESSOR) += processor.o obj-$(CONFIG_ACPI) += container.o Index: linux-pm/drivers/acpi/acpi_tad.c =================================================================== --- /dev/null +++ linux-pm/drivers/acpi/acpi_tad.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ACPI Time and Alarm (TAD) Device Driver + * + * Copyright (C) 2018 Intel Corporation + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> + * + * This driver is based on Section 9.18 of the ACPI 6.2 specification revision. + * + * It only supports the system wakeup capabilities of the TAD. + * + * Provided are sysfs attributes, available under the TAD platform device, + * allowing user space to manage the AC and DC wakeup timers of the TAD: + * set and read their values, set and check their expire timer wake policies, + * check and clear their status and check the capabilities of the TAD reported + * by AML. The DC timer attributes are only present if the TAD supports a + * separate DC alarm timer. + * + * The wakeup events handling and power management of the TAD is expected to + * be taken care of by the ACPI PM domain attached to its platform device. + */ + +#include <linux/acpi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/suspend.h> + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rafael J. Wysocki"); + +/* ACPI TAD capability flags (ACPI 6.2, Section 9.18.2) */ +#define ACPI_TAD_AC_WAKE BIT(0) +#define ACPI_TAD_DC_WAKE BIT(1) +#define ACPI_TAD_RT BIT(2) +#define ACPI_TAD_RT_IN_MS BIT(3) +#define ACPI_TAD_S4_S5__GWS BIT(4) +#define ACPI_TAD_AC_S4_WAKE BIT(5) +#define ACPI_TAD_AC_S5_WAKE BIT(6) +#define ACPI_TAD_DC_S4_WAKE BIT(7) +#define ACPI_TAD_DC_S5_WAKE BIT(8) + +/* ACPI TAD alarm timer selection */ +#define ACPI_TAD_AC_TIMER (u32)0 +#define ACPI_TAD_DC_TIMER (u32)1 + +/* Special value for disabled timer or expired timer wake policy. */ +#define ACPI_TAD_WAKE_DISABLED (~(u32)0) + +struct acpi_tad_driver_data { + u32 capabilities; +}; + +static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, + u32 value) +{ + acpi_handle handle = ACPI_HANDLE(dev); + union acpi_object args[] = { + { .type = ACPI_TYPE_INTEGER, }, + { .type = ACPI_TYPE_INTEGER, }, + }; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + unsigned long long retval; + acpi_status status; + + args[0].integer.value = timer_id; + args[1].integer.value = value; + + pm_runtime_get_sync(dev); + + status = acpi_evaluate_integer(handle, method, &arg_list, &retval); + + pm_runtime_put_sync(dev); + + if (ACPI_FAILURE(status) || retval) + return -EIO; + + return 0; +} + +static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method, + u32 timer_id, const char *specval) +{ + u32 value; + + if (sysfs_streq(buf, specval)) { + value = ACPI_TAD_WAKE_DISABLED; + } else { + int ret = kstrtou32(buf, 0, &value); + + if (ret) + return ret; + + if (value == ACPI_TAD_WAKE_DISABLED) + return -EINVAL; + } + + return acpi_tad_wake_set(dev, method, timer_id, value); +} + +static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, + u32 timer_id, const char *specval) +{ + acpi_handle handle = ACPI_HANDLE(dev); + union acpi_object args[] = { + { .type = ACPI_TYPE_INTEGER, }, + }; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + unsigned long long retval; + acpi_status status; + + args[0].integer.value = timer_id; + + pm_runtime_get_sync(dev); + + status = acpi_evaluate_integer(handle, method, &arg_list, &retval); + + pm_runtime_put_sync(dev); + + if (ACPI_FAILURE(status)) + return -EIO; + + if ((u32)retval == ACPI_TAD_WAKE_DISABLED) + return sprintf(buf, "%s\n", specval); + + return sprintf(buf, "%u\n", (u32)retval); +} + +static const char *alarm_specval = "disabled"; + +static int acpi_tad_alarm_write(struct device *dev, const char *buf, + u32 timer_id) +{ + return acpi_tad_wake_write(dev, buf, "_STV", timer_id, alarm_specval); +} + +static ssize_t acpi_tad_alarm_read(struct device *dev, char *buf, u32 timer_id) +{ + return acpi_tad_wake_read(dev, buf, "_TIV", timer_id, alarm_specval); +} + +static const char *policy_specval = "never"; + +static int acpi_tad_policy_write(struct device *dev, const char *buf, + u32 timer_id) +{ + return acpi_tad_wake_write(dev, buf, "_STP", timer_id, policy_specval); +} + +static ssize_t acpi_tad_policy_read(struct device *dev, char *buf, u32 timer_id) +{ + return acpi_tad_wake_read(dev, buf, "_TIP", timer_id, policy_specval); +} + +static int acpi_tad_clear_status(struct device *dev, u32 timer_id) +{ + acpi_handle handle = ACPI_HANDLE(dev); + union acpi_object args[] = { + { .type = ACPI_TYPE_INTEGER, }, + }; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + unsigned long long retval; + acpi_status status; + + args[0].integer.value = timer_id; + + pm_runtime_get_sync(dev); + + status = acpi_evaluate_integer(handle, "_CWS", &arg_list, &retval); + + pm_runtime_put_sync(dev); + + if (ACPI_FAILURE(status) || retval) + return -EIO; + + return 0; +} + +static int acpi_tad_status_write(struct device *dev, const char *buf, u32 timer_id) +{ + int ret, value; + + ret = kstrtoint(buf, 0, &value); + if (ret) + return ret; + + if (value) + return -EINVAL; + + return acpi_tad_clear_status(dev, timer_id); +} + +static ssize_t acpi_tad_status_read(struct device *dev, char *buf, u32 timer_id) +{ + acpi_handle handle = ACPI_HANDLE(dev); + union acpi_object args[] = { + { .type = ACPI_TYPE_INTEGER, }, + }; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + unsigned long long retval; + acpi_status status; + + args[0].integer.value = timer_id; + + pm_runtime_get_sync(dev); + + status = acpi_evaluate_integer(handle, "_GWS", &arg_list, &retval); + + pm_runtime_put_sync(dev); + + if (ACPI_FAILURE(status)) + return -EIO; + + return sprintf(buf, "0x%02X\n", (u32)retval); +} + +static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_tad_driver_data *dd = dev_get_drvdata(dev); + + return sprintf(buf, "0x%02X\n", dd->capabilities); +} + +static DEVICE_ATTR_RO(caps); + +static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = acpi_tad_alarm_write(dev, buf, ACPI_TAD_AC_TIMER); + + return ret ? ret : count; +} + +static ssize_t ac_alarm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return acpi_tad_alarm_read(dev, buf, ACPI_TAD_AC_TIMER); +} + +static DEVICE_ATTR(ac_alarm, S_IRUSR | S_IWUSR, ac_alarm_show, ac_alarm_store); + +static ssize_t ac_policy_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = acpi_tad_policy_write(dev, buf, ACPI_TAD_AC_TIMER); + + return ret ? ret : count; +} + +static ssize_t ac_policy_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return acpi_tad_policy_read(dev, buf, ACPI_TAD_AC_TIMER); +} + +static DEVICE_ATTR(ac_policy, S_IRUSR | S_IWUSR, ac_policy_show, ac_policy_store); + +static ssize_t ac_status_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = acpi_tad_status_write(dev, buf, ACPI_TAD_AC_TIMER); + + return ret ? ret : count; +} + +static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return acpi_tad_status_read(dev, buf, ACPI_TAD_AC_TIMER); +} + +static DEVICE_ATTR(ac_status, S_IRUSR | S_IWUSR, ac_status_show, ac_status_store); + +static struct attribute *acpi_tad_attrs[] = { + &dev_attr_caps.attr, + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, +}; +static const struct attribute_group acpi_tad_attr_group = { + .attrs = acpi_tad_attrs, +}; + +static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = acpi_tad_alarm_write(dev, buf, ACPI_TAD_DC_TIMER); + + return ret ? ret : count; +} + +static ssize_t dc_alarm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return acpi_tad_alarm_read(dev, buf, ACPI_TAD_DC_TIMER); +} + +static DEVICE_ATTR(dc_alarm, S_IRUSR | S_IWUSR, dc_alarm_show, dc_alarm_store); + +static ssize_t dc_policy_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = acpi_tad_policy_write(dev, buf, ACPI_TAD_DC_TIMER); + + return ret ? ret : count; +} + +static ssize_t dc_policy_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return acpi_tad_policy_read(dev, buf, ACPI_TAD_DC_TIMER); +} + +static DEVICE_ATTR(dc_policy, S_IRUSR | S_IWUSR, dc_policy_show, dc_policy_store); + +static ssize_t dc_status_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = acpi_tad_status_write(dev, buf, ACPI_TAD_DC_TIMER); + + return ret ? ret : count; +} + +static ssize_t dc_status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return acpi_tad_status_read(dev, buf, ACPI_TAD_DC_TIMER); +} + +static DEVICE_ATTR(dc_status, S_IRUSR | S_IWUSR, dc_status_show, dc_status_store); + +static struct attribute *acpi_tad_dc_attrs[] = { + &dev_attr_dc_alarm.attr, + &dev_attr_dc_policy.attr, + &dev_attr_dc_status.attr, + NULL, +}; +static const struct attribute_group acpi_tad_dc_attr_group = { + .attrs = acpi_tad_dc_attrs, +}; + +static int acpi_tad_disable_timer(struct device *dev, u32 timer_id) +{ + return acpi_tad_wake_set(dev, "_STV", timer_id, ACPI_TAD_WAKE_DISABLED); +} + +static int acpi_tad_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acpi_tad_driver_data *dd = dev_get_drvdata(dev); + + device_init_wakeup(dev, false); + + pm_runtime_get_sync(dev); + + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + + acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); + } + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return 0; +} + +static int acpi_tad_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + acpi_handle handle = ACPI_HANDLE(dev); + struct acpi_tad_driver_data *dd; + acpi_status status; + unsigned long long caps; + int ret; + + /* + * Initialization failure messages are mostly about firmware issues, so + * print them at the "info" level. + */ + status = acpi_evaluate_integer(handle, "_GCP", NULL, &caps); + if (ACPI_FAILURE(status)) { + dev_info(dev, "Unable to get capabilities\n"); + return -ENODEV; + } + + if (!(caps & ACPI_TAD_AC_WAKE)) { + dev_info(dev, "Unsupported capabilities\n"); + return -ENODEV; + } + + if (!acpi_has_method(handle, "_PRW")) { + dev_info(dev, "Missing _PRW\n"); + return -ENODEV; + } + + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; + + dd->capabilities = caps; + dev_set_drvdata(dev, dd); + + /* + * Assume that the ACPI PM domain has been attached to the device and + * simply enable system wakeup and runtime PM and put the device into + * runtime suspend. Everything else should be taken care of by the ACPI + * PM domain callbacks. + */ + device_init_wakeup(dev, true); + dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | + DPM_FLAG_LEAVE_SUSPENDED); + /* + * The platform bus type layer tells the ACPI PM domain powers up the + * device, so set the runtime PM status of it to "active". + */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_suspend(dev); + + ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); + if (ret) + goto fail; + + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) + goto fail; + } + + return 0; + +fail: + acpi_tad_remove(pdev); + return ret; +} + +static const struct acpi_device_id acpi_tad_ids[] = { + {"ACPI000E", 0}, + {} +}; + +static struct platform_driver acpi_tad_driver = { + .driver = { + .name = "acpi-tad", + .acpi_match_table = acpi_tad_ids, + }, + .probe = acpi_tad_probe, + .remove = acpi_tad_remove, +}; +MODULE_DEVICE_TABLE(acpi, acpi_tad_ids); + +module_platform_driver(acpi_tad_driver); Index: linux-pm/Documentation/ABI/testing/sysfs-devices-platform-ACPI-TAD =================================================================== --- /dev/null +++ linux-pm/Documentation/ABI/testing/sysfs-devices-platform-ACPI-TAD @@ -0,0 +1,113 @@ + ACPI Time and Alarm (TAD) device attributes. + +What: /sys/bus/platform/devices/ACPI000E:00/caps +Date: March 2018 +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> +Description: + (RO) Hexadecimal bitmask of the TAD attributes are reported by + the platform firmware (see ACPI 6.2, section 9.18.2): + + BIT(0): AC wakeup implemented if set + BIT(1): DC wakeup implemented if set + BIT(2): Get/set real time features implemented if set + BIT(3): Real time accuracy in milliseconds if set + BIT(4): Correct status reported for wakeups from S4/S5 if set + BIT(5): The AC timer wakes up from S4 if set + BIT(6): The AC timer wakes up from S5 if set + BIT(7): The DC timer wakes up from S4 if set + BIT(8): The DC timer wakes up from S5 if set + + The other bits are reserved. + +What: /sys/bus/platform/devices/ACPI000E:00/ac_alarm +Date: March 2018 +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> +Description: + (RW) The AC alarm timer value. + + Reads return the current AC alarm timer value in seconds or + "disabled", if the AC alarm is not set to wake up the system. + + Write a new AC alarm timer value in seconds or "disabled" to it + to set the AC alarm timer or to disable it, respectively. + + If the AC alarm timer is set through this attribute and it + expires, it will immediately wake up the system from the S3 + sleep state (and from S4/S5 too if supported) until its status + is explicitly cleared via the ac_status attribute. + +What: /sys/bus/platform/devices/ACPI000E:00/ac_policy +Date: March 2018 +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> +Description: + (RW) The AC alarm expired timer wake policy (see ACPI 6.2, + Section 9.18 for details). + + Reads return the current expired timer wake delay for the AC + alarm timer or "never", if the policy is to discard AC timer + wakeups if the system is on DC power. + + Write a new expired timer wake delay for the AC alarm timer in + seconds or "never" to it to set the expired timer wake delay for + the AC alarm timer or to set its expired wake policy to discard + wakeups if the system is on DC power, respectively. + +What: /sys/bus/platform/devices/ACPI000E:00/ac_status +Date: March 2018 +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> +Description: + (RW) The AC alarm status. + + Reads return a hexadecimal bitmask representing the AC alarm + timer status with the following meaning of bits (see ACPI 6.2, + Section 9.18.5): + + Bit(0): The timer has expired if set. + Bit(1): The timer has woken up the system from a sleep state + (S3 or S4/S5 if supported) if set. + + The other bits are reserved. + + Reads also cause the AC alarm timer status to be reset. + + Another way to reset the the status of the AC alarm timer is to + write (the number) 0 to this file. + + If the status return value indicates that the timer has expired, + it will immediately wake up the system from the S3 sleep state + (and from S4/S5 too if supported) until its status is explicitly + cleared through this attribute. + +What: /sys/bus/platform/devices/ACPI000E:00/dc_alarm +Date: March 2018 +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> +Description: + (RW,optional) The DC alarm timer value. + + This attribute is only present if the TAD supports a separate + DC timer. + + It is analogous to the ac_alarm attribute. + +What: /sys/bus/platform/devices/ACPI000E:00/dc_policy +Date: March 2018 +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> +Description: + (RW,optional) The DC alarm expired timer wake policy. + + This attribute is only present if the TAD supports a separate + DC timer. + + It is analogous to the ac_policy attribute. + +What: /sys/bus/platform/devices/ACPI000E:00/dc_status +Date: March 2018 +Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com> + +Description: + (RW,optional) The DC alarm status. + + This attribute is only present if the TAD supports a separate + DC timer. + + It is analogous to the ac_status attribute.