Skip to content

Commit

Permalink
rtc: cmos: allow using ACPI for RTC alarm instead of HPET
Browse files Browse the repository at this point in the history
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 <rui.zhang@intel.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
  • Loading branch information
zhang-rui authored and alexandrebelloni committed Apr 19, 2018
1 parent 60cc43f commit 311ee9c
Showing 1 changed file with 80 additions and 31 deletions.
111 changes: 80 additions & 31 deletions drivers/rtc/rtc-cmos.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
/* 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;
Expand Down Expand Up @@ -153,6 +164,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
Expand Down Expand Up @@ -298,7 +315,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;
Expand All @@ -318,7 +335,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);
}
Expand All @@ -330,7 +353,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);
}
Expand Down Expand Up @@ -448,10 +477,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);
Expand Down Expand Up @@ -508,7 +541,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",
Expand Down Expand Up @@ -614,7 +647,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
Expand All @@ -633,7 +666,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);
Expand Down Expand Up @@ -762,7 +796,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);
}

Expand All @@ -780,12 +815,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) {
Expand Down Expand Up @@ -824,7 +860,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;

Expand Down Expand Up @@ -858,7 +894,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;
Expand Down Expand Up @@ -935,13 +972,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);
Expand Down Expand Up @@ -996,7 +1033,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
Expand All @@ -1014,16 +1051,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
Expand Down Expand Up @@ -1068,16 +1106,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);
Expand Down

0 comments on commit 311ee9c

Please sign in to comment.