summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2026-03-31 22:38:52 +0300
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2026-04-04 21:37:25 +0300
commit7572dcabe38d904dd501e652b504a9ad364ba1cc (patch)
tree5951a438256c9bf10064beadb4ff587209d36c68
parent2ffc8bf29e4d7dff0e6c94f245d5a757be6c013d (diff)
downloadlinux-7572dcabe38d904dd501e652b504a9ad364ba1cc.tar.xz
ACPI: TAD: Add alarm support to the RTC class device interface
Add alarm support, based on Section 9.17 of ACPI 6.6 [1], to the RTC class device interface of the driver. The ACPI time and alarm device (TAD) can support two separate alarm timers, one for waking up the system when it is on AC power, and one for waking it up when it is on DC power. In principle, each of them can be set to a different value representing the number of seconds till the given alarm timer expires. However, the RTC class device can only set one alarm, so it will set both the alarm timers of the ACPI TAD (if the DC one is supported) to the same value. That is somewhat cumbersome because there is no way in the ACPI TAD firmware interface to set both timers in one go, so they need to be set sequentially, but that's how it goes. On the alarm read side, the driver assumes that both timers have been set to the same value, so it is sufficient to access one of them (the AC one specifically). Link: https://uefi.org/specs/ACPI/6.6/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device [1] Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Link: https://patch.msgid.link/2076980.usQuhbGJ8B@rafael.j.wysocki
-rw-r--r--drivers/acpi/acpi_tad.c112
1 files changed, 109 insertions, 3 deletions
diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c
index 8bb71396f99c..b406d7a98996 100644
--- a/drivers/acpi/acpi_tad.c
+++ b/drivers/acpi/acpi_tad.c
@@ -25,6 +25,7 @@
#include <linux/acpi.h>
#include <linux/kernel.h>
+#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
@@ -658,12 +659,113 @@ static int acpi_tad_rtc_read_time(struct device *dev, struct rtc_time *tm)
return 0;
}
+static int acpi_tad_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct acpi_tad_driver_data *dd = dev_get_drvdata(dev);
+ s64 value = ACPI_TAD_WAKE_DISABLED;
+ struct rtc_time tm_now;
+ struct acpi_tad_rt rt;
+ int ret;
+
+ PM_RUNTIME_ACQUIRE(dev, pm);
+ if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+ return -ENXIO;
+
+ if (t->enabled) {
+ /*
+ * The value to pass to _STV is expected to be the number of
+ * seconds between the time when the timer is programmed and the
+ * time when it expires represented as a 32-bit integer.
+ */
+ ret = __acpi_tad_get_real_time(dev, &rt);
+ if (ret)
+ return ret;
+
+ acpi_tad_rt_to_tm(&rt, &tm_now);
+
+ value = ktime_divns(ktime_sub(rtc_tm_to_ktime(t->time),
+ rtc_tm_to_ktime(tm_now)), NSEC_PER_SEC);
+ if (value <= 0 || value > U32_MAX)
+ return -EINVAL;
+ }
+
+ ret = __acpi_tad_wake_set(dev, "_STV", ACPI_TAD_AC_TIMER, value);
+ if (ret && t->enabled)
+ return ret;
+
+ /*
+ * If a separate DC alarm timer is supported, set it to the same value
+ * as the AC alarm timer.
+ */
+ if (dd->capabilities & ACPI_TAD_DC_WAKE) {
+ ret = __acpi_tad_wake_set(dev, "_STV", ACPI_TAD_DC_TIMER, value);
+ if (ret && t->enabled) {
+ __acpi_tad_wake_set(dev, "_STV", ACPI_TAD_AC_TIMER,
+ ACPI_TAD_WAKE_DISABLED);
+ return ret;
+ }
+ }
+
+ /* Assume success if the alarm is being disabled. */
+ return 0;
+}
+
+static int acpi_tad_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ unsigned long long retval;
+ struct rtc_time tm_now;
+ struct acpi_tad_rt rt;
+ int ret;
+
+ PM_RUNTIME_ACQUIRE(dev, pm);
+ if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+ return -ENXIO;
+
+ ret = __acpi_tad_get_real_time(dev, &rt);
+ if (ret)
+ return ret;
+
+ acpi_tad_rt_to_tm(&rt, &tm_now);
+
+ /*
+ * Assume that the alarm was set by acpi_tad_rtc_set_alarm(), so the AC
+ * and DC alarm timer settings are the same and it is sufficient to read
+ * the former.
+ *
+ * The value returned by _TIV should be the number of seconds till the
+ * expiration of the timer, represented as a 32-bit integer, or the
+ * special ACPI_TAD_WAKE_DISABLED value meaning that the timer has
+ * been disabled.
+ */
+ ret = __acpi_tad_wake_read(dev, "_TIV", ACPI_TAD_AC_TIMER, &retval);
+ if (ret)
+ return ret;
+
+ if (retval > U32_MAX)
+ return -ENODATA;
+
+ t->pending = 0;
+
+ if (retval != ACPI_TAD_WAKE_DISABLED) {
+ t->enabled = 1;
+ t->time = rtc_ktime_to_tm(ktime_add_ns(rtc_tm_to_ktime(tm_now),
+ (u64)retval * NSEC_PER_SEC));
+ } else {
+ t->enabled = 0;
+ t->time = tm_now;
+ }
+
+ return 0;
+}
+
static const struct rtc_class_ops acpi_tad_rtc_ops = {
.read_time = acpi_tad_rtc_read_time,
.set_time = acpi_tad_rtc_set_time,
+ .set_alarm = acpi_tad_rtc_set_alarm,
+ .read_alarm = acpi_tad_rtc_read_alarm,
};
-static void acpi_tad_register_rtc(struct device *dev)
+static void acpi_tad_register_rtc(struct device *dev, unsigned long long caps)
{
struct rtc_device *rtc;
@@ -676,10 +778,14 @@ static void acpi_tad_register_rtc(struct device *dev)
rtc->ops = &acpi_tad_rtc_ops;
+ if (!(caps & ACPI_TAD_AC_WAKE))
+ clear_bit(RTC_FEATURE_ALARM, rtc->features);
+
devm_rtc_register_device(rtc);
}
#else /* !CONFIG_RTC_CLASS */
-static inline void acpi_tad_register_rtc(struct device *dev) {}
+static inline void acpi_tad_register_rtc(struct device *dev,
+ unsigned long long caps) {}
#endif /* !CONFIG_RTC_CLASS */
/* Platform driver interface */
@@ -765,7 +871,7 @@ static int acpi_tad_probe(struct platform_device *pdev)
pm_runtime_suspend(dev);
if (caps & ACPI_TAD_RT)
- acpi_tad_register_rtc(dev);
+ acpi_tad_register_rtc(dev, caps);
return 0;
}