summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/rtc/rtc-cmos.c64
1 files changed, 60 insertions, 4 deletions
diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
index a82556a..5ac9a5d 100644
--- a/drivers/rtc/rtc-cmos.c
+++ b/drivers/rtc/rtc-cmos.c
@@ -51,6 +51,7 @@ struct cmos_rtc {
struct device *dev;
int irq;
struct resource *iomem;
+ time64_t alarm_expires;
void (*wake_on)(struct device *);
void (*wake_off)(struct device *);
@@ -377,6 +378,8 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
spin_unlock_irq(&rtc_lock);
+ cmos->alarm_expires = rtc_tm_to_time64(&t->time);
+
return 0;
}
@@ -860,6 +863,51 @@ static void __exit cmos_do_remove(struct device *dev)
cmos->dev = NULL;
}
+static int cmos_aie_poweroff(struct device *dev)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ struct rtc_time now;
+ time64_t t_now;
+ int retval = 0;
+ unsigned char rtc_control;
+
+ if (!cmos->alarm_expires)
+ return -EINVAL;
+
+ spin_lock_irq(&rtc_lock);
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ spin_unlock_irq(&rtc_lock);
+
+ /* We only care about the situation where AIE is disabled. */
+ if (rtc_control & RTC_AIE)
+ return -EBUSY;
+
+ cmos_read_time(dev, &now);
+ t_now = rtc_tm_to_time64(&now);
+
+ /*
+ * When enabling "RTC wake-up" in BIOS setup, the machine reboots
+ * automatically right after shutdown on some buggy boxes.
+ * This automatic rebooting issue won't happen when the alarm
+ * time is larger than now+1 seconds.
+ *
+ * If the alarm time is equal to now+1 seconds, the issue can be
+ * prevented by cancelling the alarm.
+ */
+ if (cmos->alarm_expires == t_now + 1) {
+ struct rtc_wkalrm alarm;
+
+ /* Cancel the AIE timer by configuring the past time. */
+ rtc_time64_to_tm(t_now - 1, &alarm.time);
+ alarm.enabled = 0;
+ retval = cmos_set_alarm(dev, &alarm);
+ } else if (cmos->alarm_expires > t_now + 1) {
+ retval = -EBUSY;
+ }
+
+ return retval;
+}
+
#ifdef CONFIG_PM
static int cmos_suspend(struct device *dev)
@@ -1094,8 +1142,12 @@ 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 && !cmos_poweroff(dev))
- return;
+ if (system_state == SYSTEM_POWER_OFF) {
+ int retval = cmos_poweroff(dev);
+
+ if (cmos_aie_poweroff(dev) < 0 && !retval)
+ return;
+ }
cmos_do_shutdown(cmos->irq);
}
@@ -1200,8 +1252,12 @@ static void cmos_platform_shutdown(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct cmos_rtc *cmos = dev_get_drvdata(dev);
- if (system_state == SYSTEM_POWER_OFF && !cmos_poweroff(dev))
- return;
+ if (system_state == SYSTEM_POWER_OFF) {
+ int retval = cmos_poweroff(dev);
+
+ if (cmos_aie_poweroff(dev) < 0 && !retval)
+ return;
+ }
cmos_do_shutdown(cmos->irq);
}