Merge branch 'acpi-cmos-rtc'

Merge updates related to the CMOS RTC driver and x86/ACPI CMOS RTC
support for 7.1-rc1:

 - Add ACPI support to the platform device interface in the CMOS RTC
   driver, make the ACPI core device enumeration code create a platform
   device for the CMOS RTC, and drop CMOS RTC PNP device support (Rafael
   Wysocki)

 - Consolidate the x86-specific CMOS RTC handling with the ACPI TAD
   driver and clean up the CMOS RTC ACPI address space handler (Rafael
   Wysocki)

 - Enable ACPI alarm in the CMOS RTC driver if advertised in ACPI FADT
   and allow that driver to work without a dedicated IRQ if the ACPI
   alarm is used (Rafael Wysocki)

* acpi-cmos-rtc:
  rtc: cmos: Do not require IRQ if ACPI alarm is used
  rtc: cmos: Enable ACPI alarm if advertised in ACPI FADT
  ACPI: TAD/x86: cmos_rtc: Consolidate address space handler setup
  rtc: cmos: Drop PNP device support
  x86: rtc: Drop PNP device check
  ACPI: PNP: Drop CMOS RTC PNP device support
  ACPI: x86/rtc-cmos: Use platform device for driver binding
  ACPI: x86: cmos_rtc: Create a CMOS RTC platform device
  ACPI: x86: cmos_rtc: Improve coordination with ACPI TAD driver
  ACPI: x86: cmos_rtc: Clean up address space handler driver
This commit is contained in:
Rafael J. Wysocki 2026-04-09 21:40:22 +02:00
commit d07060217b
7 changed files with 100 additions and 220 deletions

View File

@ -2,10 +2,10 @@
/*
* RTC related functions
*/
#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <linux/mc146818rtc.h>
#include <linux/export.h>
#include <linux/pnp.h>
#include <asm/vsyscall.h>
#include <asm/x86_init.h>
@ -133,25 +133,14 @@ static struct platform_device rtc_device = {
static __init int add_rtc_cmos(void)
{
#ifdef CONFIG_PNP
static const char * const ids[] __initconst =
{ "PNP0b00", "PNP0b01", "PNP0b02", };
struct pnp_dev *dev;
int i;
if (cmos_rtc_platform_device_present)
return 0;
pnp_for_each_dev(dev) {
for (i = 0; i < ARRAY_SIZE(ids); i++) {
if (compare_pnp_id(dev->id, ids[i]) != 0)
return 0;
}
}
#endif
if (!x86_platform.legacy.rtc)
return -ENODEV;
platform_device_register(&rtc_device);
dev_info(&rtc_device.dev,
"registered platform RTC device (no PNP device found)\n");
dev_info(&rtc_device.dev, "registered fallback platform RTC device\n");
return 0;
}

View File

@ -125,10 +125,6 @@ static const struct acpi_device_id acpi_pnp_device_ids[] = {
{"PNP0401"}, /* ECP Printer Port */
/* apple-gmux */
{"APP000B"},
/* rtc_cmos */
{"PNP0b00"},
{"PNP0b01"},
{"PNP0b02"},
/* c6xdigio */
{"PNP0400"}, /* Standard LPT Printer Port */
{"PNP0401"}, /* ECP Printer Port */
@ -355,25 +351,9 @@ static struct acpi_scan_handler acpi_pnp_handler = {
.attach = acpi_pnp_attach,
};
/*
* For CMOS RTC devices, the PNP ACPI scan handler does not work, because
* there is a CMOS RTC ACPI scan handler installed already, so we need to
* check those devices and enumerate them to the PNP bus directly.
*/
static int is_cmos_rtc_device(struct acpi_device *adev)
{
static const struct acpi_device_id ids[] = {
{ "PNP0B00" },
{ "PNP0B01" },
{ "PNP0B02" },
{""},
};
return !acpi_match_device_ids(adev, ids);
}
bool acpi_is_pnp_device(struct acpi_device *adev)
{
return adev->handler == &acpi_pnp_handler || is_cmos_rtc_device(adev);
return adev->handler == &acpi_pnp_handler;
}
EXPORT_SYMBOL_GPL(acpi_is_pnp_device);

View File

@ -563,7 +563,6 @@ static int acpi_tad_disable_timer(struct device *dev, u32 timer_id)
static void acpi_tad_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
acpi_handle handle = ACPI_HANDLE(dev);
struct acpi_tad_driver_data *dd = dev_get_drvdata(dev);
device_init_wakeup(dev, false);
@ -587,7 +586,6 @@ static void acpi_tad_remove(struct platform_device *pdev)
pm_runtime_suspend(dev);
pm_runtime_disable(dev);
acpi_remove_cmos_rtc_space_handler(handle);
}
static int acpi_tad_probe(struct platform_device *pdev)
@ -599,11 +597,6 @@ static int acpi_tad_probe(struct platform_device *pdev)
unsigned long long caps;
int ret;
ret = acpi_install_cmos_rtc_space_handler(handle);
if (ret < 0) {
dev_info(dev, "Unable to install space handler\n");
return -ENODEV;
}
/*
* Initialization failure messages are mostly about firmware issues, so
* print them at the "info" level.
@ -611,27 +604,22 @@ static int acpi_tad_probe(struct platform_device *pdev)
status = acpi_evaluate_integer(handle, "_GCP", NULL, &caps);
if (ACPI_FAILURE(status)) {
dev_info(dev, "Unable to get capabilities\n");
ret = -ENODEV;
goto remove_handler;
return -ENODEV;
}
if (!(caps & ACPI_TAD_AC_WAKE)) {
dev_info(dev, "Unsupported capabilities\n");
ret = -ENODEV;
goto remove_handler;
return -ENODEV;
}
if (!acpi_has_method(handle, "_PRW")) {
dev_info(dev, "Missing _PRW\n");
ret = -ENODEV;
goto remove_handler;
return -ENODEV;
}
dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL);
if (!dd) {
ret = -ENOMEM;
goto remove_handler;
}
if (!dd)
return -ENOMEM;
dd->capabilities = caps;
dev_set_drvdata(dev, dd);
@ -673,11 +661,6 @@ static int acpi_tad_probe(struct platform_device *pdev)
fail:
acpi_tad_remove(pdev);
/* Don't fallthrough because cmos rtc space handler is removed in acpi_tad_remove() */
return ret;
remove_handler:
acpi_remove_cmos_rtc_space_handler(handle);
return ret;
}

View File

@ -18,78 +18,86 @@
#include "../internal.h"
static const struct acpi_device_id acpi_cmos_rtc_ids[] = {
{ "PNP0B00" },
{ "PNP0B01" },
{ "PNP0B02" },
{}
{ "ACPI000E", 1 }, /* ACPI Time and Alarm Device (TAD) */
ACPI_CMOS_RTC_IDS
};
static acpi_status
acpi_cmos_rtc_space_handler(u32 function, acpi_physical_address address,
u32 bits, u64 *value64,
void *handler_context, void *region_context)
bool cmos_rtc_platform_device_present;
static acpi_status acpi_cmos_rtc_space_handler(u32 function,
acpi_physical_address address,
u32 bits, u64 *value64,
void *handler_context,
void *region_context)
{
int i;
unsigned int i, bytes = DIV_ROUND_UP(bits, 8);
u8 *value = (u8 *)value64;
if (address > 0xff || !value64)
return AE_BAD_PARAMETER;
if (function != ACPI_WRITE && function != ACPI_READ)
return AE_BAD_PARAMETER;
guard(spinlock_irq)(&rtc_lock);
spin_lock_irq(&rtc_lock);
for (i = 0; i < DIV_ROUND_UP(bits, 8); ++i, ++address, ++value)
if (function == ACPI_READ)
*value = CMOS_READ(address);
else
if (function == ACPI_WRITE) {
for (i = 0; i < bytes; i++, address++, value++)
CMOS_WRITE(*value, address);
spin_unlock_irq(&rtc_lock);
return AE_OK;
}
return AE_OK;
if (function == ACPI_READ) {
for (i = 0; i < bytes; i++, address++, value++)
*value = CMOS_READ(address);
return AE_OK;
}
return AE_BAD_PARAMETER;
}
int acpi_install_cmos_rtc_space_handler(acpi_handle handle)
static int acpi_install_cmos_rtc_space_handler(acpi_handle handle)
{
static bool cmos_rtc_space_handler_present __read_mostly;
acpi_status status;
if (cmos_rtc_space_handler_present)
return 0;
status = acpi_install_address_space_handler(handle,
ACPI_ADR_SPACE_CMOS,
&acpi_cmos_rtc_space_handler,
NULL, NULL);
ACPI_ADR_SPACE_CMOS,
acpi_cmos_rtc_space_handler,
NULL, NULL);
if (ACPI_FAILURE(status)) {
pr_err("Error installing CMOS-RTC region handler\n");
pr_err("Failed to install CMOS-RTC address space handler\n");
return -ENODEV;
}
cmos_rtc_space_handler_present = true;
return 1;
}
EXPORT_SYMBOL_GPL(acpi_install_cmos_rtc_space_handler);
void acpi_remove_cmos_rtc_space_handler(acpi_handle handle)
static int acpi_cmos_rtc_attach(struct acpi_device *adev,
const struct acpi_device_id *id)
{
if (ACPI_FAILURE(acpi_remove_address_space_handler(handle,
ACPI_ADR_SPACE_CMOS, &acpi_cmos_rtc_space_handler)))
pr_err("Error removing CMOS-RTC region handler\n");
}
EXPORT_SYMBOL_GPL(acpi_remove_cmos_rtc_space_handler);
int ret;
static int acpi_cmos_rtc_attach_handler(struct acpi_device *adev, const struct acpi_device_id *id)
{
return acpi_install_cmos_rtc_space_handler(adev->handle);
}
ret = acpi_install_cmos_rtc_space_handler(adev->handle);
if (ret < 0)
return ret;
static void acpi_cmos_rtc_detach_handler(struct acpi_device *adev)
{
acpi_remove_cmos_rtc_space_handler(adev->handle);
if (IS_ERR_OR_NULL(acpi_create_platform_device(adev, NULL))) {
pr_err("Failed to create a platform device for %s\n", (char *)id->id);
return 0;
} else if (!id->driver_data) {
cmos_rtc_platform_device_present = true;
}
return 1;
}
static struct acpi_scan_handler cmos_rtc_handler = {
.ids = acpi_cmos_rtc_ids,
.attach = acpi_cmos_rtc_attach_handler,
.detach = acpi_cmos_rtc_detach_handler,
.attach = acpi_cmos_rtc_attach,
};
void __init acpi_cmos_rtc_init(void)

View File

@ -27,6 +27,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
@ -215,6 +216,11 @@ static inline void cmos_write_bank2(unsigned char val, unsigned char addr)
/*----------------------------------------------------------------*/
static bool cmos_no_alarm(struct cmos_rtc *cmos)
{
return !is_valid_irq(cmos->irq) && !cmos_use_acpi_alarm();
}
static int cmos_read_time(struct device *dev, struct rtc_time *t)
{
int ret;
@ -286,7 +292,7 @@ static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)
};
/* This not only a rtc_op, but also called directly */
if (!is_valid_irq(cmos->irq))
if (cmos_no_alarm(cmos))
return -ETIMEDOUT;
/* Basic alarms only support hour, minute, and seconds fields.
@ -519,7 +525,7 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
int ret;
/* This not only a rtc_op, but also called directly */
if (!is_valid_irq(cmos->irq))
if (cmos_no_alarm(cmos))
return -EIO;
ret = cmos_validate_alarm(dev, t);
@ -816,6 +822,9 @@ static void rtc_wake_off(struct device *dev)
#ifdef CONFIG_X86
static void use_acpi_alarm_quirks(void)
{
if (acpi_gbl_FADT.flags & ACPI_FADT_FIXED_RTC)
return;
switch (boot_cpu_data.x86_vendor) {
case X86_VENDOR_INTEL:
if (dmi_get_bios_year() < 2015)
@ -829,8 +838,6 @@ static void use_acpi_alarm_quirks(void)
default:
return;
}
if (!is_hpet_enabled())
return;
use_acpi_alarm = true;
}
@ -1094,7 +1101,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
dev_dbg(dev, "IRQ %d is already in use\n", rtc_irq);
goto cleanup1;
}
} else {
} else if (!cmos_use_acpi_alarm()) {
clear_bit(RTC_FEATURE_ALARM, cmos_rtc.rtc->features);
}
@ -1119,7 +1126,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
acpi_rtc_event_setup(dev);
dev_info(dev, "%s%s, %d bytes nvram%s\n",
!is_valid_irq(rtc_irq) ? "no alarms" :
cmos_no_alarm(&cmos_rtc) ? "no alarms" :
cmos_rtc.mon_alrm ? "alarms up to one year" :
cmos_rtc.day_alrm ? "alarms up to one month" :
"alarms up to one day",
@ -1145,7 +1152,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
static void cmos_do_shutdown(int rtc_irq)
{
spin_lock_irq(&rtc_lock);
if (is_valid_irq(rtc_irq))
if (!cmos_no_alarm(&cmos_rtc))
cmos_irq_disable(&cmos_rtc, RTC_IRQMASK);
spin_unlock_irq(&rtc_lock);
}
@ -1369,85 +1376,6 @@ static int __maybe_unused cmos_resume(struct device *dev)
static SIMPLE_DEV_PM_OPS(cmos_pm_ops, cmos_suspend, cmos_resume);
/*----------------------------------------------------------------*/
/* On non-x86 systems, a "CMOS" RTC lives most naturally on platform_bus.
* ACPI systems always list these as PNPACPI devices, and pre-ACPI PCs
* probably list them in similar PNPBIOS tables; so PNP is more common.
*
* We don't use legacy "poke at the hardware" probing. Ancient PCs that
* predate even PNPBIOS should set up platform_bus devices.
*/
#ifdef CONFIG_PNP
#include <linux/pnp.h>
static int cmos_pnp_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
{
int irq;
if (pnp_port_start(pnp, 0) == 0x70 && !pnp_irq_valid(pnp, 0)) {
irq = 0;
#ifdef CONFIG_X86
/* Some machines contain a PNP entry for the RTC, but
* don't define the IRQ. It should always be safe to
* hardcode it on systems with a legacy PIC.
*/
if (nr_legacy_irqs())
irq = RTC_IRQ;
#endif
} else {
irq = pnp_irq(pnp, 0);
}
return cmos_do_probe(&pnp->dev, pnp_get_resource(pnp, IORESOURCE_IO, 0), irq);
}
static void cmos_pnp_remove(struct pnp_dev *pnp)
{
cmos_do_remove(&pnp->dev);
}
static void cmos_pnp_shutdown(struct pnp_dev *pnp)
{
struct device *dev = &pnp->dev;
struct cmos_rtc *cmos = dev_get_drvdata(dev);
if (system_state == SYSTEM_POWER_OFF) {
int retval = cmos_poweroff(dev);
if (cmos_aie_poweroff(dev) < 0 && !retval)
return;
}
cmos_do_shutdown(cmos->irq);
}
static const struct pnp_device_id rtc_ids[] = {
{ .id = "PNP0b00", },
{ .id = "PNP0b01", },
{ .id = "PNP0b02", },
{ },
};
MODULE_DEVICE_TABLE(pnp, rtc_ids);
static struct pnp_driver cmos_pnp_driver = {
.name = driver_name,
.id_table = rtc_ids,
.probe = cmos_pnp_probe,
.remove = cmos_pnp_remove,
.shutdown = cmos_pnp_shutdown,
/* flag ensures resume() gets called, and stops syslog spam */
.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
.driver = {
.pm = &cmos_pm_ops,
},
};
#endif /* CONFIG_PNP */
#ifdef CONFIG_OF
static const struct of_device_id of_cmos_match[] = {
{
@ -1476,6 +1404,14 @@ static __init void cmos_of_init(struct platform_device *pdev)
#else
static inline void cmos_of_init(struct platform_device *pdev) {}
#endif
#ifdef CONFIG_ACPI
static const struct acpi_device_id acpi_cmos_rtc_ids[] = {
ACPI_CMOS_RTC_IDS
};
MODULE_DEVICE_TABLE(acpi, acpi_cmos_rtc_ids);
#endif
/*----------------------------------------------------------------*/
/* Platform setup should have set up an RTC device, when PNP is
@ -1530,48 +1466,31 @@ static struct platform_driver cmos_platform_driver = {
.name = driver_name,
.pm = &cmos_pm_ops,
.of_match_table = of_match_ptr(of_cmos_match),
.acpi_match_table = ACPI_PTR(acpi_cmos_rtc_ids),
}
};
#ifdef CONFIG_PNP
static bool pnp_driver_registered;
#endif
static bool platform_driver_registered;
static int __init cmos_init(void)
{
int retval = 0;
int retval;
#ifdef CONFIG_PNP
retval = pnp_register_driver(&cmos_pnp_driver);
if (retval == 0)
pnp_driver_registered = true;
#endif
if (!cmos_rtc.dev) {
retval = platform_driver_probe(&cmos_platform_driver,
cmos_platform_probe);
if (retval == 0)
platform_driver_registered = true;
}
if (retval == 0)
if (cmos_rtc.dev)
return 0;
#ifdef CONFIG_PNP
if (pnp_driver_registered)
pnp_unregister_driver(&cmos_pnp_driver);
#endif
return retval;
retval = platform_driver_probe(&cmos_platform_driver, cmos_platform_probe);
if (retval)
return retval;
platform_driver_registered = true;
return 0;
}
module_init(cmos_init);
static void __exit cmos_exit(void)
{
#ifdef CONFIG_PNP
if (pnp_driver_registered)
pnp_unregister_driver(&cmos_pnp_driver);
#endif
if (platform_driver_registered)
platform_driver_unregister(&cmos_platform_driver);
}

View File

@ -760,8 +760,6 @@ int acpi_disable_wakeup_device_power(struct acpi_device *dev);
#ifdef CONFIG_X86
bool acpi_device_override_status(struct acpi_device *adev, unsigned long long *status);
bool acpi_quirk_skip_acpi_ac_and_battery(void);
int acpi_install_cmos_rtc_space_handler(acpi_handle handle);
void acpi_remove_cmos_rtc_space_handler(acpi_handle handle);
int acpi_quirk_skip_serdev_enumeration(struct device *controller_parent, bool *skip);
#else
static inline bool acpi_device_override_status(struct acpi_device *adev,
@ -773,13 +771,6 @@ static inline bool acpi_quirk_skip_acpi_ac_and_battery(void)
{
return false;
}
static inline int acpi_install_cmos_rtc_space_handler(acpi_handle handle)
{
return 1;
}
static inline void acpi_remove_cmos_rtc_space_handler(acpi_handle handle)
{
}
static inline int
acpi_quirk_skip_serdev_enumeration(struct device *controller_parent, bool *skip)
{

View File

@ -802,6 +802,14 @@ const char *acpi_get_subsystem_id(acpi_handle handle);
int acpi_mrrm_max_mem_region(void);
#endif
#define ACPI_CMOS_RTC_IDS \
{ "PNP0B00", }, \
{ "PNP0B01", }, \
{ "PNP0B02", }, \
{ "", }
extern bool cmos_rtc_platform_device_present;
#else /* !CONFIG_ACPI */
#define acpi_disabled 1
@ -1127,6 +1135,8 @@ static inline int acpi_mrrm_max_mem_region(void)
return 1;
}
#define cmos_rtc_platform_device_present false
#endif /* !CONFIG_ACPI */
#ifdef CONFIG_ACPI_HMAT