From 8be4a29b495ec8b1ed1e38a7db2e9160317b1ce8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:12:22 +0100 Subject: [PATCH 01/13] ACPI: TAD: Create one attribute group Instead of creating three attribute groups, one for each supported subset of capabilities, create just one and use an .is_visible() callback in it to decide which attributes to use. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2412153.ElGaqSPkdT@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 80 ++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 4f5089fc023d..78d0bee4b28a 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -249,14 +249,6 @@ static ssize_t time_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(time); -static struct attribute *acpi_tad_time_attrs[] = { - &dev_attr_time.attr, - NULL, -}; -static const struct attribute_group acpi_tad_time_attr_group = { - .attrs = acpi_tad_time_attrs, -}; - static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, u32 value) { @@ -486,17 +478,6 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(ac_status); -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) { @@ -545,14 +526,44 @@ static ssize_t dc_status_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(dc_status); -static struct attribute *acpi_tad_dc_attrs[] = { +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, &dev_attr_dc_alarm.attr, &dev_attr_dc_policy.attr, &dev_attr_dc_status.attr, + &dev_attr_time.attr, NULL, }; -static const struct attribute_group acpi_tad_dc_attr_group = { - .attrs = acpi_tad_dc_attrs, + +static umode_t acpi_tad_attr_is_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct acpi_tad_driver_data *dd = dev_get_drvdata(kobj_to_dev(kobj)); + + if (a == &dev_attr_caps.attr) + return a->mode; + + if (a == &dev_attr_ac_alarm.attr || a == &dev_attr_ac_policy.attr || + a == &dev_attr_ac_status.attr) + return a->mode; + + if ((dd->capabilities & ACPI_TAD_DC_WAKE) && + (a == &dev_attr_dc_alarm.attr || a == &dev_attr_dc_policy.attr || + a == &dev_attr_dc_status.attr)) + return a->mode; + + if ((dd->capabilities & ACPI_TAD_RT) && a == &dev_attr_time.attr) + return a->mode; + + return 0; +} + +static const struct attribute_group acpi_tad_attr_group = { + .attrs = acpi_tad_attrs, + .is_visible = acpi_tad_attr_is_visible, }; static int acpi_tad_disable_timer(struct device *dev, u32 timer_id) @@ -567,12 +578,6 @@ static void acpi_tad_remove(struct platform_device *pdev) device_init_wakeup(dev, false); - if (dd->capabilities & ACPI_TAD_RT) - sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group); - - 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); scoped_guard(pm_runtime_noresume, dev) { @@ -633,6 +638,7 @@ static int acpi_tad_probe(struct platform_device *pdev) device_init_wakeup(dev, true); dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | DPM_FLAG_MAY_SKIP_RESUME); + /* * The platform bus type layer tells the ACPI PM domain powers up the * device, so set the runtime PM status of it to "active". @@ -643,24 +649,8 @@ static int acpi_tad_probe(struct platform_device *pdev) ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); if (ret) - goto fail; + acpi_tad_remove(pdev); - if (caps & ACPI_TAD_DC_WAKE) { - ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); - if (ret) - goto fail; - } - - if (caps & ACPI_TAD_RT) { - ret = sysfs_create_group(&dev->kobj, &acpi_tad_time_attr_group); - if (ret) - goto fail; - } - - return 0; - -fail: - acpi_tad_remove(pdev); return ret; } From 6c711fde3a1ce00165e3423eb8b7fa7888f0f313 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:13:15 +0100 Subject: [PATCH 02/13] ACPI: TAD: Support RTC without wakeup The ACPI TAD can provide a functional RTC without wakeup capabilities, so stop failing probe if AC wakeup is not supported. Also, if _PRW is missing, do not fail probe, but clear the wakeup bits in capabilities. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/1959268.tdWV9SEqCh@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 78d0bee4b28a..560ed6495daf 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -546,8 +546,9 @@ static umode_t acpi_tad_attr_is_visible(struct kobject *kobj, if (a == &dev_attr_caps.attr) return a->mode; - if (a == &dev_attr_ac_alarm.attr || a == &dev_attr_ac_policy.attr || - a == &dev_attr_ac_status.attr) + if ((dd->capabilities & ACPI_TAD_AC_WAKE) && + (a == &dev_attr_ac_alarm.attr || a == &dev_attr_ac_policy.attr || + a == &dev_attr_ac_status.attr)) return a->mode; if ((dd->capabilities & ACPI_TAD_DC_WAKE) && @@ -581,8 +582,10 @@ static void acpi_tad_remove(struct platform_device *pdev) sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); scoped_guard(pm_runtime_noresume, dev) { - acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); - acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + if (dd->capabilities & ACPI_TAD_AC_WAKE) { + 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); @@ -612,14 +615,9 @@ static int acpi_tad_probe(struct platform_device *pdev) 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; + caps &= ~(ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE); } dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); @@ -635,9 +633,11 @@ static int acpi_tad_probe(struct platform_device *pdev) * 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_MAY_SKIP_RESUME); + if (ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE) { + device_init_wakeup(dev, true); + dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | + DPM_FLAG_MAY_SKIP_RESUME); + } /* * The platform bus type layer tells the ACPI PM domain powers up the From e64ab3e217cf5e16301a73d5a5230265454b15c1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:14:01 +0100 Subject: [PATCH 03/13] ACPI: TAD: Use __free() for cleanup in time_store() Use __free() for the automatic freeing of memory pointed to by local variable str in time_store() which allows the code to become somewhat easier to follow. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/13971300.uLZWGnKmhe@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 560ed6495daf..eae8ee42e23f 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -167,57 +167,57 @@ static ssize_t time_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct acpi_tad_rt rt; - char *str, *s; - int val, ret = -ENODATA; + int val, ret; + char *s; - str = kmemdup_nul(buf, count, GFP_KERNEL); + char *str __free(kfree) = kmemdup_nul(buf, count, GFP_KERNEL); if (!str) return -ENOMEM; s = acpi_tad_rt_next_field(str, &val); if (!s) - goto out_free; + return -ENODATA; rt.year = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.month = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.day = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.hour = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.minute = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.second = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.tz = val; if (kstrtoint(s, 10, &val)) - goto out_free; + return -ENODATA; rt.daylight = val; @@ -226,10 +226,10 @@ static ssize_t time_store(struct device *dev, struct device_attribute *attr, memset(rt.padding, 0, 3); ret = acpi_tad_set_real_time(dev, &rt); + if (ret) + return ret; -out_free: - kfree(str); - return ret ? ret : count; + return count; } static ssize_t time_show(struct device *dev, struct device_attribute *attr, From 3329a1416c3350449081ca5daaa94802a65b2992 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:14:44 +0100 Subject: [PATCH 04/13] ACPI: TAD: Rearrange RT data validation checking Move RT data validation checks from acpi_tad_set_real_time() to a separate function called acpi_tad_rt_is_invalid() and use it also in acpi_tad_get_real_time() to validate data coming from the platform firmware. Also make acpi_tad_set_real_time() return -EINVAL when the RT data passed to it is invalid (instead of -ERANGE which is somewhat confusing) and introduce ACPI_TAD_TZ_UNSPEC to represent the "unspecified timezone" value. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3409319.aeNJFYEL58@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index eae8ee42e23f..240e5d53c4a5 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -49,6 +49,9 @@ MODULE_AUTHOR("Rafael J. Wysocki"); /* Special value for disabled timer or expired timer wake policy. */ #define ACPI_TAD_WAKE_DISABLED (~(u32)0) +/* ACPI TAD RTC */ +#define ACPI_TAD_TZ_UNSPEC 2047 + struct acpi_tad_driver_data { u32 capabilities; }; @@ -67,6 +70,16 @@ struct acpi_tad_rt { u8 padding[3]; /* must be 0 */ } __packed; +static bool acpi_tad_rt_is_invalid(struct acpi_tad_rt *rt) +{ + return rt->year < 1900 || rt->year > 9999 || + rt->month < 1 || rt->month > 12 || + rt->hour > 23 || rt->minute > 59 || rt->second > 59 || + rt->tz < -1440 || + (rt->tz > 1440 && rt->tz != ACPI_TAD_TZ_UNSPEC) || + rt->daylight > 3; +} + static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) { acpi_handle handle = ACPI_HANDLE(dev); @@ -80,12 +93,8 @@ static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) unsigned long long retval; acpi_status status; - if (rt->year < 1900 || rt->year > 9999 || - rt->month < 1 || rt->month > 12 || - rt->hour > 23 || rt->minute > 59 || rt->second > 59 || - rt->tz < -1440 || (rt->tz > 1440 && rt->tz != 2047) || - rt->daylight > 3) - return -ERANGE; + if (acpi_tad_rt_is_invalid(rt)) + return -EINVAL; args[0].buffer.pointer = (u8 *)rt; args[0].buffer.length = sizeof(*rt); @@ -145,6 +154,9 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) if (ret) return ret; + if (acpi_tad_rt_is_invalid(rt)) + return -ENODATA; + return 0; } From fca4f233a247986db24b451682c3c6d52be4ff45 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:15:21 +0100 Subject: [PATCH 05/13] ACPI: TAD: Clear unused RT data in acpi_tad_set_real_time() Move the clearing of the fields in struct acpi_tad_rt that are not used on the real time setting side to acpi_tad_set_real_time(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/8660506.T7Z3S40VBb@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 240e5d53c4a5..b570e1bda236 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -96,6 +96,10 @@ static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) if (acpi_tad_rt_is_invalid(rt)) return -EINVAL; + rt->valid = 0; + rt->msec = 0; + memset(rt->padding, 0, 3); + args[0].buffer.pointer = (u8 *)rt; args[0].buffer.length = sizeof(*rt); @@ -233,10 +237,6 @@ static ssize_t time_store(struct device *dev, struct device_attribute *attr, rt.daylight = val; - rt.valid = 0; - rt.msec = 0; - memset(rt.padding, 0, 3); - ret = acpi_tad_set_real_time(dev, &rt); if (ret) return ret; From 8a1e7f4b176401ae015516a3cf4a71af3615f7c2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:16:01 +0100 Subject: [PATCH 06/13] ACPI: TAD: Add RTC class device interface Add an RTC class device interface allowing to read and set the real time value to the ACPI TAD driver. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/2352027.iZASKD2KPV@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 78 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index b570e1bda236..395983b270ba 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -25,6 +25,7 @@ #include #include #include +#include #include MODULE_DESCRIPTION("ACPI Time and Alarm (TAD) Device Driver"); @@ -51,6 +52,7 @@ MODULE_AUTHOR("Rafael J. Wysocki"); /* ACPI TAD RTC */ #define ACPI_TAD_TZ_UNSPEC 2047 +#define ACPI_TAD_TIME_ISDST 3 struct acpi_tad_driver_data { u32 capabilities; @@ -164,6 +166,8 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) return 0; } +/* sysfs interface */ + static char *acpi_tad_rt_next_field(char *s, int *val) { char *p; @@ -579,6 +583,71 @@ static const struct attribute_group acpi_tad_attr_group = { .is_visible = acpi_tad_attr_is_visible, }; +#ifdef CONFIG_RTC_CLASS +/* RTC class device interface */ + +static int acpi_tad_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct acpi_tad_rt rt; + + rt.year = tm->tm_year + 1900; + rt.month = tm->tm_mon + 1; + rt.day = tm->tm_mday; + rt.hour = tm->tm_hour; + rt.minute = tm->tm_min; + rt.second = tm->tm_sec; + rt.tz = ACPI_TAD_TZ_UNSPEC; + rt.daylight = ACPI_TAD_TIME_ISDST * !!tm->tm_isdst; + + return acpi_tad_set_real_time(dev, &rt); +} + +static int acpi_tad_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct acpi_tad_rt rt; + int ret; + + ret = acpi_tad_get_real_time(dev, &rt); + if (ret) + return ret; + + tm->tm_year = rt.year - 1900; + tm->tm_mon = rt.month - 1; + tm->tm_mday = rt.day; + tm->tm_hour = rt.hour; + tm->tm_min = rt.minute; + tm->tm_sec = rt.second; + tm->tm_isdst = rt.daylight == ACPI_TAD_TIME_ISDST; + + 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, +}; + +static void acpi_tad_register_rtc(struct device *dev) +{ + struct rtc_device *rtc; + + rtc = devm_rtc_allocate_device(dev); + if (IS_ERR(rtc)) + return; + + rtc->range_min = mktime64(1900, 1, 1, 0, 0, 0); + rtc->range_max = mktime64(9999, 12, 31, 23, 59, 59); + + rtc->ops = &acpi_tad_rtc_ops; + + devm_rtc_register_device(rtc); +} +#else /* !CONFIG_RTC_CLASS */ +static inline void acpi_tad_register_rtc(struct device *dev) {} +#endif /* !CONFIG_RTC_CLASS */ + +/* Platform driver interface */ + 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); @@ -660,10 +729,15 @@ static int acpi_tad_probe(struct platform_device *pdev) pm_runtime_suspend(dev); ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); - if (ret) + if (ret) { acpi_tad_remove(pdev); + return ret; + } - return ret; + if (caps & ACPI_TAD_RT) + acpi_tad_register_rtc(dev); + + return 0; } static const struct acpi_device_id acpi_tad_ids[] = { From 6db176eb41ad51e48a97431c9fbc0680aa338e5f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:16:48 +0100 Subject: [PATCH 07/13] ACPI: TAD: Update the driver description comment Update the preamble comment describing the driver to match the code after previous changes along with the copyright information. No functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/23034847.EfDdHjke4D@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 395983b270ba..f1db2807f59c 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -2,12 +2,10 @@ /* * ACPI Time and Alarm (TAD) Device Driver * - * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2018 - 2026 Intel Corporation * Author: Rafael J. Wysocki * - * 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. + * This driver is based on ACPI 6.6, Section 9.17. * * Provided are sysfs attributes, available under the TAD platform device, * allowing user space to manage the AC and DC wakeup timers of the TAD: @@ -18,6 +16,11 @@ * * 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. + * + * If the TAD supports the get/set real time features, as indicated by the + * capability mask returned by _GCP under the TAD object, additional sysfs + * attributes are created allowing the real time to be set and read and an RTC + * class device is registered under the TAD platform device. */ #include @@ -32,7 +35,7 @@ MODULE_DESCRIPTION("ACPI Time and Alarm (TAD) Device Driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Rafael J. Wysocki"); -/* ACPI TAD capability flags (ACPI 6.2, Section 9.18.2) */ +/* ACPI TAD capability flags (ACPI 6.6, Section 9.17.2) */ #define ACPI_TAD_AC_WAKE BIT(0) #define ACPI_TAD_DC_WAKE BIT(1) #define ACPI_TAD_RT BIT(2) From 93afe8ba9b0179b8e42777dad530db3ad9c9e835 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 10 Mar 2026 21:29:29 +0100 Subject: [PATCH 08/13] ACPI: TAD: Use dev_groups in struct device_driver Instead of creating and removing the device sysfs attributes directly during probe and remove of the driver, respectively, use dev_groups in struct device_driver to point to the attribute definitions and let the core take care of creating and removing them. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2836803.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index f1db2807f59c..d67c43edf855 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -586,6 +586,11 @@ static const struct attribute_group acpi_tad_attr_group = { .is_visible = acpi_tad_attr_is_visible, }; +static const struct attribute_group *acpi_tad_attr_groups[] = { + &acpi_tad_attr_group, + NULL, +}; + #ifdef CONFIG_RTC_CLASS /* RTC class device interface */ @@ -663,8 +668,6 @@ static void acpi_tad_remove(struct platform_device *pdev) device_init_wakeup(dev, false); - sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); - scoped_guard(pm_runtime_noresume, dev) { if (dd->capabilities & ACPI_TAD_AC_WAKE) { acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); @@ -687,7 +690,6 @@ static int acpi_tad_probe(struct platform_device *pdev) struct acpi_tad_driver_data *dd; acpi_status status; unsigned long long caps; - int ret; /* * Initialization failure messages are mostly about firmware issues, so @@ -731,12 +733,6 @@ static int acpi_tad_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_suspend(dev); - ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); - if (ret) { - acpi_tad_remove(pdev); - return ret; - } - if (caps & ACPI_TAD_RT) acpi_tad_register_rtc(dev); @@ -752,6 +748,7 @@ static struct platform_driver acpi_tad_driver = { .driver = { .name = "acpi-tad", .acpi_match_table = acpi_tad_ids, + .dev_groups = acpi_tad_attr_groups, }, .probe = acpi_tad_probe, .remove = acpi_tad_remove, From f706a63fd82242a21cb5248b76c21d3c302a4b54 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Mar 2026 16:26:37 +0100 Subject: [PATCH 09/13] ACPI: TAD: Use DC wakeup only if AC wakeup is supported According to Section 9.17.2 of ACPI 6.6 [1], setting ACPI_TAD_DC_WAKE in the capabilities without setting ACPI_TAD_AC_WAKE is invalid, so don't support wakeup if that's the case. Moreover, it is sufficient to check ACPI_TAD_AC_WAKE alone to determine if wakeup is supported at all, so use this observation to simplify one check. Link: https://uefi.org/specs/ACPI/6.6/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#gcp-get-capability [1] Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2833494.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index d67c43edf855..8b43d883125e 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -706,6 +706,9 @@ static int acpi_tad_probe(struct platform_device *pdev) caps &= ~(ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE); } + if (!(caps & ACPI_TAD_AC_WAKE)) + caps &= ~ACPI_TAD_DC_WAKE; + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); if (!dd) return -ENOMEM; @@ -719,7 +722,7 @@ static int acpi_tad_probe(struct platform_device *pdev) * runtime suspend. Everything else should be taken care of by the ACPI * PM domain callbacks. */ - if (ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE) { + if (ACPI_TAD_AC_WAKE) { device_init_wakeup(dev, true); dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | DPM_FLAG_MAY_SKIP_RESUME); From b14b77bbea0a82cb66f1538824783e63d4128510 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:24:44 +0200 Subject: [PATCH 10/13] ACPI: TAD: Split three functions to untangle runtime PM handling Move the core functionality of acpi_tad_get_real_time(), acpi_tad_wake_set(), and acpi_tad_wake_read() into separate functions called __acpi_tad_get_real_time(), __acpi_tad_wake_set(), and __acpi_tad_wake_read(), respectively, which can be called from code blocks following a single runtime resume of the device. This will facilitate adding alarm support to the RTC class device interface of the driver going forward. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/23076728.EfDdHjke4D@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 57 +++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 8b43d883125e..7b422904e45b 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -151,14 +151,10 @@ static int acpi_tad_evaluate_grt(struct device *dev, struct acpi_tad_rt *rt) return ret; } -static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) +static int __acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) { int ret; - PM_RUNTIME_ACQUIRE(dev, pm); - if (PM_RUNTIME_ACQUIRE_ERR(&pm)) - return -ENXIO; - ret = acpi_tad_evaluate_grt(dev, rt); if (ret) return ret; @@ -169,6 +165,15 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) return 0; } +static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) +{ + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + return __acpi_tad_get_real_time(dev, rt); +} + /* sysfs interface */ static char *acpi_tad_rt_next_field(char *s, int *val) @@ -268,8 +273,8 @@ static ssize_t time_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(time); -static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, - u32 value) +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[] = { @@ -286,10 +291,6 @@ static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, args[0].integer.value = timer_id; args[1].integer.value = value; - PM_RUNTIME_ACQUIRE(dev, pm); - if (PM_RUNTIME_ACQUIRE_ERR(&pm)) - return -ENXIO; - status = acpi_evaluate_integer(handle, method, &arg_list, &retval); if (ACPI_FAILURE(status) || retval) return -EIO; @@ -297,6 +298,16 @@ static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, return 0; } +static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, + u32 value) +{ + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + return __acpi_tad_wake_set(dev, method, timer_id, value); +} + static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method, u32 timer_id, const char *specval) { @@ -317,8 +328,8 @@ static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method 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) +static int __acpi_tad_wake_read(struct device *dev, char *method, u32 timer_id, + unsigned long long *retval) { acpi_handle handle = ACPI_HANDLE(dev); union acpi_object args[] = { @@ -328,18 +339,30 @@ static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, .pointer = args, .count = ARRAY_SIZE(args), }; - unsigned long long retval; acpi_status status; args[0].integer.value = timer_id; + status = acpi_evaluate_integer(handle, method, &arg_list, retval); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, + u32 timer_id, const char *specval) +{ + unsigned long long retval; + int ret; + PM_RUNTIME_ACQUIRE(dev, pm); if (PM_RUNTIME_ACQUIRE_ERR(&pm)) return -ENXIO; - status = acpi_evaluate_integer(handle, method, &arg_list, &retval); - if (ACPI_FAILURE(status)) - return -EIO; + ret = __acpi_tad_wake_read(dev, method, timer_id, &retval); + if (ret) + return ret; if ((u32)retval == ACPI_TAD_WAKE_DISABLED) return sprintf(buf, "%s\n", specval); From 9bcdd4ef4cd24ff360c6d0df3b8efad35d82bd94 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:25:40 +0200 Subject: [PATCH 11/13] ACPI: TAD: Relocate two functions Move two functions introduced previously, __acpi_tad_wake_set() and __acpi_tad_wake_read(), to the part of the code preceding the sysfs interface implementation, since subsequently they will be used by the RTC device interface too. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/3960639.kQq0lBPeGt@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 94 ++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 7b422904e45b..a0857a7904df 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -174,6 +174,53 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) return __acpi_tad_get_real_time(dev, rt); } +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; + + status = acpi_evaluate_integer(handle, method, &arg_list, &retval); + if (ACPI_FAILURE(status) || retval) + return -EIO; + + return 0; +} + +static int __acpi_tad_wake_read(struct device *dev, char *method, u32 timer_id, + unsigned long long *retval) +{ + 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), + }; + acpi_status status; + + args[0].integer.value = timer_id; + + status = acpi_evaluate_integer(handle, method, &arg_list, retval); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + /* sysfs interface */ static char *acpi_tad_rt_next_field(char *s, int *val) @@ -273,31 +320,6 @@ static ssize_t time_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(time); -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; - - status = acpi_evaluate_integer(handle, method, &arg_list, &retval); - if (ACPI_FAILURE(status) || retval) - return -EIO; - - return 0; -} - static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, u32 value) { @@ -328,28 +350,6 @@ static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method return acpi_tad_wake_set(dev, method, timer_id, value); } -static int __acpi_tad_wake_read(struct device *dev, char *method, u32 timer_id, - unsigned long long *retval) -{ - 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), - }; - acpi_status status; - - args[0].integer.value = timer_id; - - status = acpi_evaluate_integer(handle, method, &arg_list, retval); - if (ACPI_FAILURE(status)) - return -EIO; - - return 0; -} - static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, u32 timer_id, const char *specval) { From 2ffc8bf29e4d7dff0e6c94f245d5a757be6c013d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:26:23 +0200 Subject: [PATCH 12/13] ACPI: TAD: Split acpi_tad_rtc_read_time() Move the code converting a struct acpi_tad_rt into a struct rtc_time from acpi_tad_rtc_read_time() into a new function, acpi_tad_rt_to_tm(), to facilitate adding alarm support to the driver's RTC class device interface going forward. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/9619488.CDJkKcVGEf@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_tad.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index a0857a7904df..8bb71396f99c 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -617,6 +617,17 @@ static const struct attribute_group *acpi_tad_attr_groups[] = { #ifdef CONFIG_RTC_CLASS /* RTC class device interface */ +static void acpi_tad_rt_to_tm(struct acpi_tad_rt *rt, struct rtc_time *tm) +{ + tm->tm_year = rt->year - 1900; + tm->tm_mon = rt->month - 1; + tm->tm_mday = rt->day; + tm->tm_hour = rt->hour; + tm->tm_min = rt->minute; + tm->tm_sec = rt->second; + tm->tm_isdst = rt->daylight == ACPI_TAD_TIME_ISDST; +} + static int acpi_tad_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct acpi_tad_rt rt; @@ -642,13 +653,7 @@ static int acpi_tad_rtc_read_time(struct device *dev, struct rtc_time *tm) if (ret) return ret; - tm->tm_year = rt.year - 1900; - tm->tm_mon = rt.month - 1; - tm->tm_mday = rt.day; - tm->tm_hour = rt.hour; - tm->tm_min = rt.minute; - tm->tm_sec = rt.second; - tm->tm_isdst = rt.daylight == ACPI_TAD_TIME_ISDST; + acpi_tad_rt_to_tm(&rt, tm); return 0; } From 7572dcabe38d904dd501e652b504a9ad364ba1cc Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:38:52 +0200 Subject: [PATCH 13/13] 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 --- drivers/acpi/acpi_tad.c | 112 ++++++++++++++++++++++++++++++++++++++-- 1 file 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 #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; }