diff options
Diffstat (limited to 'drivers/rtc/rtc-cmos.c')
-rw-r--r-- | drivers/rtc/rtc-cmos.c | 155 |
1 files changed, 124 insertions, 31 deletions
diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 1b3738a11702..cd3a2411bc2f 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -43,11 +43,24 @@ #include <linux/of_platform.h> #ifdef CONFIG_X86 #include <asm/i8259.h> +#include <asm/processor.h> +#include <linux/dmi.h> #endif /* this is for "generic access to PC-style RTC" using CMOS_READ/CMOS_WRITE */ #include <linux/mc146818rtc.h> +/* + * Use ACPI SCI to replace HPET interrupt for RTC Alarm event + * + * If cleared, ACPI SCI is only used to wake up the system from suspend + * + * If set, ACPI SCI is used to handle UIE/AIE and system wakeup + */ + +static bool use_acpi_alarm; +module_param(use_acpi_alarm, bool, 0444); + struct cmos_rtc { struct rtc_device *rtc; struct device *dev; @@ -153,6 +166,12 @@ static inline int hpet_unregister_irq_handler(irq_handler_t handler) #endif +/* Don't use HPET for RTC Alarm event if ACPI Fixed event is used */ +static int use_hpet_alarm(void) +{ + return is_hpet_enabled() && !use_acpi_alarm; +} + /*----------------------------------------------------------------*/ #ifdef RTC_PORT @@ -298,7 +317,7 @@ static void cmos_checkintr(struct cmos_rtc *cmos, unsigned char rtc_control) */ rtc_intr = CMOS_READ(RTC_INTR_FLAGS); - if (is_hpet_enabled()) + if (use_hpet_alarm()) return; rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; @@ -318,7 +337,13 @@ static void cmos_irq_enable(struct cmos_rtc *cmos, unsigned char mask) rtc_control |= mask; CMOS_WRITE(rtc_control, RTC_CONTROL); - hpet_set_rtc_irq_bit(mask); + if (use_hpet_alarm()) + hpet_set_rtc_irq_bit(mask); + + if ((mask & RTC_AIE) && use_acpi_alarm) { + if (cmos->wake_on) + cmos->wake_on(cmos->dev); + } cmos_checkintr(cmos, rtc_control); } @@ -330,7 +355,13 @@ static void cmos_irq_disable(struct cmos_rtc *cmos, unsigned char mask) rtc_control = CMOS_READ(RTC_CONTROL); rtc_control &= ~mask; CMOS_WRITE(rtc_control, RTC_CONTROL); - hpet_mask_rtc_irq_bit(mask); + if (use_hpet_alarm()) + hpet_mask_rtc_irq_bit(mask); + + if ((mask & RTC_AIE) && use_acpi_alarm) { + if (cmos->wake_off) + cmos->wake_off(cmos->dev); + } cmos_checkintr(cmos, rtc_control); } @@ -448,10 +479,14 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t) CMOS_WRITE(mon, cmos->mon_alrm); } - /* FIXME the HPET alarm glue currently ignores day_alrm - * and mon_alrm ... - */ - hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min, t->time.tm_sec); + if (use_hpet_alarm()) { + /* + * FIXME the HPET alarm glue currently ignores day_alrm + * and mon_alrm ... + */ + hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min, + t->time.tm_sec); + } if (t->enabled) cmos_irq_enable(cmos, RTC_AIE); @@ -508,7 +543,7 @@ static int cmos_procfs(struct device *dev, struct seq_file *seq) "batt_status\t: %s\n", (rtc_control & RTC_PIE) ? "yes" : "no", (rtc_control & RTC_UIE) ? "yes" : "no", - is_hpet_enabled() ? "yes" : "no", + use_hpet_alarm() ? "yes" : "no", // (rtc_control & RTC_SQWE) ? "yes" : "no", (rtc_control & RTC_DM_BINARY) ? "no" : "yes", (rtc_control & RTC_DST_EN) ? "yes" : "no", @@ -614,7 +649,7 @@ static irqreturn_t cmos_interrupt(int irq, void *p) */ irqstat = CMOS_READ(RTC_INTR_FLAGS); rtc_control = CMOS_READ(RTC_CONTROL); - if (is_hpet_enabled()) + if (use_hpet_alarm()) irqstat = (unsigned long)irq & 0xF0; /* If we were suspended, RTC_CONTROL may not be accurate since the @@ -633,7 +668,8 @@ static irqreturn_t cmos_interrupt(int irq, void *p) cmos_rtc.suspend_ctrl &= ~RTC_AIE; rtc_control &= ~RTC_AIE; CMOS_WRITE(rtc_control, RTC_CONTROL); - hpet_mask_rtc_irq_bit(RTC_AIE); + if (use_hpet_alarm()) + hpet_mask_rtc_irq_bit(RTC_AIE); CMOS_READ(RTC_INTR_FLAGS); } spin_unlock(&rtc_lock); @@ -762,7 +798,8 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) * need to do something about other clock frequencies. */ cmos_rtc.rtc->irq_freq = 1024; - hpet_set_periodic_freq(cmos_rtc.rtc->irq_freq); + if (use_hpet_alarm()) + hpet_set_periodic_freq(cmos_rtc.rtc->irq_freq); CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT); } @@ -780,12 +817,13 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) goto cleanup1; } - hpet_rtc_timer_init(); + if (use_hpet_alarm()) + hpet_rtc_timer_init(); if (is_valid_irq(rtc_irq)) { irq_handler_t rtc_cmos_int_handler; - if (is_hpet_enabled()) { + if (use_hpet_alarm()) { rtc_cmos_int_handler = hpet_rtc_interrupt; retval = hpet_register_irq_handler(cmos_interrupt); if (retval) { @@ -824,7 +862,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) "alarms up to one day", cmos_rtc.century ? ", y3k" : "", nvmem_cfg.size, - is_hpet_enabled() ? ", hpet irqs" : ""); + use_hpet_alarm() ? ", hpet irqs" : ""); return 0; @@ -858,7 +896,8 @@ static void cmos_do_remove(struct device *dev) if (is_valid_irq(cmos->irq)) { free_irq(cmos->irq, cmos->rtc); - hpet_unregister_irq_handler(cmos_interrupt); + if (use_hpet_alarm()) + hpet_unregister_irq_handler(cmos_interrupt); } cmos->rtc = NULL; @@ -935,13 +974,13 @@ static int cmos_suspend(struct device *dev) mask = RTC_IRQMASK; tmp &= ~mask; CMOS_WRITE(tmp, RTC_CONTROL); - hpet_mask_rtc_irq_bit(mask); - + if (use_hpet_alarm()) + hpet_mask_rtc_irq_bit(mask); cmos_checkintr(cmos, tmp); } spin_unlock_irq(&rtc_lock); - if (tmp & RTC_AIE) { + if ((tmp & RTC_AIE) && !use_acpi_alarm) { cmos->enabled_wake = 1; if (cmos->wake_on) cmos->wake_on(dev); @@ -976,8 +1015,26 @@ static void cmos_check_wkalrm(struct device *dev) { struct cmos_rtc *cmos = dev_get_drvdata(dev); struct rtc_wkalrm current_alarm; + time64_t t_now; time64_t t_current_expires; time64_t t_saved_expires; + struct rtc_time now; + + /* Check if we have RTC Alarm armed */ + if (!(cmos->suspend_ctrl & RTC_AIE)) + return; + + cmos_read_time(dev, &now); + t_now = rtc_tm_to_time64(&now); + + /* + * ACPI RTC wake event is cleared after resume from STR, + * ACK the rtc irq here + */ + if (t_now >= cmos->alarm_expires && use_acpi_alarm) { + cmos_interrupt(0, (void *)cmos->rtc); + return; + } cmos_read_alarm(dev, ¤t_alarm); t_current_expires = rtc_tm_to_time64(¤t_alarm.time); @@ -996,7 +1053,7 @@ static int __maybe_unused cmos_resume(struct device *dev) struct cmos_rtc *cmos = dev_get_drvdata(dev); unsigned char tmp; - if (cmos->enabled_wake) { + if (cmos->enabled_wake && !use_acpi_alarm) { if (cmos->wake_off) cmos->wake_off(dev); else @@ -1014,16 +1071,17 @@ static int __maybe_unused cmos_resume(struct device *dev) if (tmp & RTC_IRQMASK) { unsigned char mask; - if (device_may_wakeup(dev)) + if (device_may_wakeup(dev) && use_hpet_alarm()) hpet_rtc_timer_init(); do { CMOS_WRITE(tmp, RTC_CONTROL); - hpet_set_rtc_irq_bit(tmp & RTC_IRQMASK); + if (use_hpet_alarm()) + hpet_set_rtc_irq_bit(tmp & RTC_IRQMASK); mask = CMOS_READ(RTC_INTR_FLAGS); mask &= (tmp & RTC_IRQMASK) | RTC_IRQF; - if (!is_hpet_enabled() || !is_intr(mask)) + if (!use_hpet_alarm() || !is_intr(mask)) break; /* force one-shot behavior if HPET blocked @@ -1068,16 +1126,27 @@ static u32 rtc_handler(void *context) unsigned char rtc_intr; unsigned long flags; - spin_lock_irqsave(&rtc_lock, flags); - if (cmos_rtc.suspend_ctrl) - rtc_control = CMOS_READ(RTC_CONTROL); - if (rtc_control & RTC_AIE) { - cmos_rtc.suspend_ctrl &= ~RTC_AIE; - CMOS_WRITE(rtc_control, RTC_CONTROL); - rtc_intr = CMOS_READ(RTC_INTR_FLAGS); - rtc_update_irq(cmos->rtc, 1, rtc_intr); + + /* + * Always update rtc irq when ACPI is used as RTC Alarm. + * Or else, ACPI SCI is enabled during suspend/resume only, + * update rtc irq in that case. + */ + if (use_acpi_alarm) + cmos_interrupt(0, (void *)cmos->rtc); + else { + /* Fix me: can we use cmos_interrupt() here as well? */ + spin_lock_irqsave(&rtc_lock, flags); + if (cmos_rtc.suspend_ctrl) + rtc_control = CMOS_READ(RTC_CONTROL); + if (rtc_control & RTC_AIE) { + cmos_rtc.suspend_ctrl &= ~RTC_AIE; + CMOS_WRITE(rtc_control, RTC_CONTROL); + rtc_intr = CMOS_READ(RTC_INTR_FLAGS); + rtc_update_irq(cmos->rtc, 1, rtc_intr); + } + spin_unlock_irqrestore(&rtc_lock, flags); } - spin_unlock_irqrestore(&rtc_lock, flags); pm_wakeup_hard_event(dev); acpi_clear_event(ACPI_EVENT_RTC); @@ -1107,6 +1176,28 @@ static void rtc_wake_off(struct device *dev) acpi_disable_event(ACPI_EVENT_RTC, 0); } +#ifdef CONFIG_X86 +/* Enable use_acpi_alarm mode for Intel platforms no earlier than 2015 */ +static void use_acpi_alarm_quirks(void) +{ + int year; + + if (boot_cpu_data.x86_vendor != X86_VENDOR_INTEL) + return; + + if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)) + return; + + if (!is_hpet_enabled()) + return; + + if (dmi_get_date(DMI_BIOS_DATE, &year, NULL, NULL) && year >= 2015) + use_acpi_alarm = true; +} +#else +static inline void use_acpi_alarm_quirks(void) { } +#endif + /* Every ACPI platform has a mc146818 compatible "cmos rtc". Here we find * its device node and pass extra config data. This helps its driver use * capabilities that the now-obsolete mc146818 didn't have, and informs it @@ -1119,6 +1210,8 @@ static void cmos_wake_setup(struct device *dev) if (acpi_disabled) return; + use_acpi_alarm_quirks(); + rtc_wake_setup(dev); acpi_rtc_info.wake_on = rtc_wake_on; acpi_rtc_info.wake_off = rtc_wake_off; |