Skip to content

Commit 311ee9c

Browse files
zhang-ruialexandrebelloni
authored andcommitted
rtc: cmos: allow using ACPI for RTC alarm instead of HPET
It's found that the HPET timer prevents the platform from entering Low Power S0 on some new Intel platforms. This means that 1. users can still use RTC wake Alarm for suspend-to-idle, but the system never enters Low Power S0, which is a waste of power. or 2. if users want to put the system into Low Power S0, they can not use RTC as the wakeup source. To fix this, we need to stop using the HPET timer for wake alarm. But disabling CONFIG_HPET_EMULATE_RTC is not an option because HPET emulates PIT at the same time, and this is needed on some of these platforms. Thus, introduce a new mode (use_acpi_alarm) to the rtc_cmos driver, so that, even with CONFIG_HPET_EMULATE_RTC enabled, it's still possible to use ACPI SCI for RTC Alarm, including UIE/AIE/wkalrm, instead of HPET. Only necessary changes are made for the new "use_acpi_alarm" mode, including 1. drop all the calls to HPET emulation code, including the HPET irq handler for rtc interrupt. 2. enabling/disabling ACPI RTC Fixed event upon RTC UIE/AIE request. 3. acknowledge the RTC Alarm in ACPI RTC Fixed event handler. There is no functional change made in this patch if the new mode is not enabled. Note: this "use_acpi_alarm" mode is made based on the assumption that ACPI RTC Fixed event is reliable both at runtime and during system wakeup. And this has been verified on a couple of platforms I have, including a MS Surface Pro 4 (SKL), a Lenovo Yoga 900 (SKL), and a HP 9360 (KBL). Signed-off-by: Zhang Rui <[email protected]> Signed-off-by: Alexandre Belloni <[email protected]>
1 parent 60cc43f commit 311ee9c

File tree

1 file changed

+80
-31
lines changed

1 file changed

+80
-31
lines changed

drivers/rtc/rtc-cmos.c

+80-31
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@
4848
/* this is for "generic access to PC-style RTC" using CMOS_READ/CMOS_WRITE */
4949
#include <linux/mc146818rtc.h>
5050

51+
/*
52+
* Use ACPI SCI to replace HPET interrupt for RTC Alarm event
53+
*
54+
* If cleared, ACPI SCI is only used to wake up the system from suspend
55+
*
56+
* If set, ACPI SCI is used to handle UIE/AIE and system wakeup
57+
*/
58+
59+
static bool use_acpi_alarm;
60+
module_param(use_acpi_alarm, bool, 0444);
61+
5162
struct cmos_rtc {
5263
struct rtc_device *rtc;
5364
struct device *dev;
@@ -153,6 +164,12 @@ static inline int hpet_unregister_irq_handler(irq_handler_t handler)
153164

154165
#endif
155166

167+
/* Don't use HPET for RTC Alarm event if ACPI Fixed event is used */
168+
static int use_hpet_alarm(void)
169+
{
170+
return is_hpet_enabled() && !use_acpi_alarm;
171+
}
172+
156173
/*----------------------------------------------------------------*/
157174

158175
#ifdef RTC_PORT
@@ -298,7 +315,7 @@ static void cmos_checkintr(struct cmos_rtc *cmos, unsigned char rtc_control)
298315
*/
299316
rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
300317

301-
if (is_hpet_enabled())
318+
if (use_hpet_alarm())
302319
return;
303320

304321
rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;
@@ -318,7 +335,13 @@ static void cmos_irq_enable(struct cmos_rtc *cmos, unsigned char mask)
318335

319336
rtc_control |= mask;
320337
CMOS_WRITE(rtc_control, RTC_CONTROL);
321-
hpet_set_rtc_irq_bit(mask);
338+
if (use_hpet_alarm())
339+
hpet_set_rtc_irq_bit(mask);
340+
341+
if ((mask & RTC_AIE) && use_acpi_alarm) {
342+
if (cmos->wake_on)
343+
cmos->wake_on(cmos->dev);
344+
}
322345

323346
cmos_checkintr(cmos, rtc_control);
324347
}
@@ -330,7 +353,13 @@ static void cmos_irq_disable(struct cmos_rtc *cmos, unsigned char mask)
330353
rtc_control = CMOS_READ(RTC_CONTROL);
331354
rtc_control &= ~mask;
332355
CMOS_WRITE(rtc_control, RTC_CONTROL);
333-
hpet_mask_rtc_irq_bit(mask);
356+
if (use_hpet_alarm())
357+
hpet_mask_rtc_irq_bit(mask);
358+
359+
if ((mask & RTC_AIE) && use_acpi_alarm) {
360+
if (cmos->wake_off)
361+
cmos->wake_off(cmos->dev);
362+
}
334363

335364
cmos_checkintr(cmos, rtc_control);
336365
}
@@ -448,10 +477,14 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
448477
CMOS_WRITE(mon, cmos->mon_alrm);
449478
}
450479

451-
/* FIXME the HPET alarm glue currently ignores day_alrm
452-
* and mon_alrm ...
453-
*/
454-
hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min, t->time.tm_sec);
480+
if (use_hpet_alarm()) {
481+
/*
482+
* FIXME the HPET alarm glue currently ignores day_alrm
483+
* and mon_alrm ...
484+
*/
485+
hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min,
486+
t->time.tm_sec);
487+
}
455488

456489
if (t->enabled)
457490
cmos_irq_enable(cmos, RTC_AIE);
@@ -508,7 +541,7 @@ static int cmos_procfs(struct device *dev, struct seq_file *seq)
508541
"batt_status\t: %s\n",
509542
(rtc_control & RTC_PIE) ? "yes" : "no",
510543
(rtc_control & RTC_UIE) ? "yes" : "no",
511-
is_hpet_enabled() ? "yes" : "no",
544+
use_hpet_alarm() ? "yes" : "no",
512545
// (rtc_control & RTC_SQWE) ? "yes" : "no",
513546
(rtc_control & RTC_DM_BINARY) ? "no" : "yes",
514547
(rtc_control & RTC_DST_EN) ? "yes" : "no",
@@ -614,7 +647,7 @@ static irqreturn_t cmos_interrupt(int irq, void *p)
614647
*/
615648
irqstat = CMOS_READ(RTC_INTR_FLAGS);
616649
rtc_control = CMOS_READ(RTC_CONTROL);
617-
if (is_hpet_enabled())
650+
if (use_hpet_alarm())
618651
irqstat = (unsigned long)irq & 0xF0;
619652

620653
/* If we were suspended, RTC_CONTROL may not be accurate since the
@@ -633,7 +666,8 @@ static irqreturn_t cmos_interrupt(int irq, void *p)
633666
cmos_rtc.suspend_ctrl &= ~RTC_AIE;
634667
rtc_control &= ~RTC_AIE;
635668
CMOS_WRITE(rtc_control, RTC_CONTROL);
636-
hpet_mask_rtc_irq_bit(RTC_AIE);
669+
if (use_hpet_alarm())
670+
hpet_mask_rtc_irq_bit(RTC_AIE);
637671
CMOS_READ(RTC_INTR_FLAGS);
638672
}
639673
spin_unlock(&rtc_lock);
@@ -762,7 +796,8 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
762796
* need to do something about other clock frequencies.
763797
*/
764798
cmos_rtc.rtc->irq_freq = 1024;
765-
hpet_set_periodic_freq(cmos_rtc.rtc->irq_freq);
799+
if (use_hpet_alarm())
800+
hpet_set_periodic_freq(cmos_rtc.rtc->irq_freq);
766801
CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT);
767802
}
768803

@@ -780,12 +815,13 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
780815
goto cleanup1;
781816
}
782817

783-
hpet_rtc_timer_init();
818+
if (use_hpet_alarm())
819+
hpet_rtc_timer_init();
784820

785821
if (is_valid_irq(rtc_irq)) {
786822
irq_handler_t rtc_cmos_int_handler;
787823

788-
if (is_hpet_enabled()) {
824+
if (use_hpet_alarm()) {
789825
rtc_cmos_int_handler = hpet_rtc_interrupt;
790826
retval = hpet_register_irq_handler(cmos_interrupt);
791827
if (retval) {
@@ -824,7 +860,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
824860
"alarms up to one day",
825861
cmos_rtc.century ? ", y3k" : "",
826862
nvmem_cfg.size,
827-
is_hpet_enabled() ? ", hpet irqs" : "");
863+
use_hpet_alarm() ? ", hpet irqs" : "");
828864

829865
return 0;
830866

@@ -858,7 +894,8 @@ static void cmos_do_remove(struct device *dev)
858894

859895
if (is_valid_irq(cmos->irq)) {
860896
free_irq(cmos->irq, cmos->rtc);
861-
hpet_unregister_irq_handler(cmos_interrupt);
897+
if (use_hpet_alarm())
898+
hpet_unregister_irq_handler(cmos_interrupt);
862899
}
863900

864901
cmos->rtc = NULL;
@@ -935,13 +972,13 @@ static int cmos_suspend(struct device *dev)
935972
mask = RTC_IRQMASK;
936973
tmp &= ~mask;
937974
CMOS_WRITE(tmp, RTC_CONTROL);
938-
hpet_mask_rtc_irq_bit(mask);
939-
975+
if (use_hpet_alarm())
976+
hpet_mask_rtc_irq_bit(mask);
940977
cmos_checkintr(cmos, tmp);
941978
}
942979
spin_unlock_irq(&rtc_lock);
943980

944-
if (tmp & RTC_AIE) {
981+
if ((tmp & RTC_AIE) && !use_acpi_alarm) {
945982
cmos->enabled_wake = 1;
946983
if (cmos->wake_on)
947984
cmos->wake_on(dev);
@@ -996,7 +1033,7 @@ static int __maybe_unused cmos_resume(struct device *dev)
9961033
struct cmos_rtc *cmos = dev_get_drvdata(dev);
9971034
unsigned char tmp;
9981035

999-
if (cmos->enabled_wake) {
1036+
if (cmos->enabled_wake && !use_acpi_alarm) {
10001037
if (cmos->wake_off)
10011038
cmos->wake_off(dev);
10021039
else
@@ -1014,16 +1051,17 @@ static int __maybe_unused cmos_resume(struct device *dev)
10141051
if (tmp & RTC_IRQMASK) {
10151052
unsigned char mask;
10161053

1017-
if (device_may_wakeup(dev))
1054+
if (device_may_wakeup(dev) && use_hpet_alarm())
10181055
hpet_rtc_timer_init();
10191056

10201057
do {
10211058
CMOS_WRITE(tmp, RTC_CONTROL);
1022-
hpet_set_rtc_irq_bit(tmp & RTC_IRQMASK);
1059+
if (use_hpet_alarm())
1060+
hpet_set_rtc_irq_bit(tmp & RTC_IRQMASK);
10231061

10241062
mask = CMOS_READ(RTC_INTR_FLAGS);
10251063
mask &= (tmp & RTC_IRQMASK) | RTC_IRQF;
1026-
if (!is_hpet_enabled() || !is_intr(mask))
1064+
if (!use_hpet_alarm() || !is_intr(mask))
10271065
break;
10281066

10291067
/* force one-shot behavior if HPET blocked
@@ -1068,16 +1106,27 @@ static u32 rtc_handler(void *context)
10681106
unsigned char rtc_intr;
10691107
unsigned long flags;
10701108

1071-
spin_lock_irqsave(&rtc_lock, flags);
1072-
if (cmos_rtc.suspend_ctrl)
1073-
rtc_control = CMOS_READ(RTC_CONTROL);
1074-
if (rtc_control & RTC_AIE) {
1075-
cmos_rtc.suspend_ctrl &= ~RTC_AIE;
1076-
CMOS_WRITE(rtc_control, RTC_CONTROL);
1077-
rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
1078-
rtc_update_irq(cmos->rtc, 1, rtc_intr);
1109+
1110+
/*
1111+
* Always update rtc irq when ACPI is used as RTC Alarm.
1112+
* Or else, ACPI SCI is enabled during suspend/resume only,
1113+
* update rtc irq in that case.
1114+
*/
1115+
if (use_acpi_alarm)
1116+
cmos_interrupt(0, (void *)cmos->rtc);
1117+
else {
1118+
/* Fix me: can we use cmos_interrupt() here as well? */
1119+
spin_lock_irqsave(&rtc_lock, flags);
1120+
if (cmos_rtc.suspend_ctrl)
1121+
rtc_control = CMOS_READ(RTC_CONTROL);
1122+
if (rtc_control & RTC_AIE) {
1123+
cmos_rtc.suspend_ctrl &= ~RTC_AIE;
1124+
CMOS_WRITE(rtc_control, RTC_CONTROL);
1125+
rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
1126+
rtc_update_irq(cmos->rtc, 1, rtc_intr);
1127+
}
1128+
spin_unlock_irqrestore(&rtc_lock, flags);
10791129
}
1080-
spin_unlock_irqrestore(&rtc_lock, flags);
10811130

10821131
pm_wakeup_hard_event(dev);
10831132
acpi_clear_event(ACPI_EVENT_RTC);

0 commit comments

Comments
 (0)