From: Rafael J. Wysocki Date: Tue, 31 Mar 2026 19:38:52 +0000 (+0200) Subject: ACPI: TAD: Add alarm support to the RTC class device interface X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7572dcabe38d904dd501e652b504a9ad364ba1cc;p=thirdparty%2Fkernel%2Fstable.git 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 Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/2076980.usQuhbGJ8B@rafael.j.wysocki --- 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 #include +#include #include #include #include @@ -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; }