diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/rtc/rtc-cmos.c | 193 |
1 files changed, 131 insertions, 62 deletions
diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 94b89a2..e998465 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -36,28 +36,9 @@ #include <linux/platform_device.h> #include <linux/mod_devicetable.h> -#ifdef CONFIG_HPET_EMULATE_RTC -#include <asm/hpet.h> -#endif - /* this is for "generic access to PC-style RTC" using CMOS_READ/CMOS_WRITE */ #include <asm-generic/rtc.h> -#ifndef CONFIG_HPET_EMULATE_RTC -#define is_hpet_enabled() 0 -#define hpet_set_alarm_time(hrs, min, sec) do { } while (0) -#define hpet_set_periodic_freq(arg) 0 -#define hpet_mask_rtc_irq_bit(arg) do { } while (0) -#define hpet_set_rtc_irq_bit(arg) do { } while (0) -#define hpet_rtc_timer_init() do { } while (0) -#define hpet_register_irq_handler(h) 0 -#define hpet_unregister_irq_handler(h) do { } while (0) -static irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id) -{ - return 0; -} -#endif - struct cmos_rtc { struct rtc_device *rtc; struct device *dev; @@ -96,6 +77,72 @@ static inline int is_intr(u8 rtc_intr) /*----------------------------------------------------------------*/ +/* Much modern x86 hardware has HPETs (10+ MHz timers) which, because + * many BIOS programmers don't set up "sane mode" IRQ routing, are mostly + * used in a broken "legacy replacement" mode. The breakage includes + * HPET #1 hijacking the IRQ for this RTC, and being unavailable for + * other (better) use. + * + * When that broken mode is in use, platform glue provides a partial + * emulation of hardware RTC IRQ facilities using HPET #1. We don't + * want to use HPET for anything except those IRQs though... + */ +#ifdef CONFIG_HPET_EMULATE_RTC +#include <asm/hpet.h> +#else + +static inline int is_hpet_enabled(void) +{ + return 0; +} + +static inline int hpet_mask_rtc_irq_bit(unsigned long mask) +{ + return 0; +} + +static inline int hpet_set_rtc_irq_bit(unsigned long mask) +{ + return 0; +} + +static inline int +hpet_set_alarm_time(unsigned char hrs, unsigned char min, unsigned char sec) +{ + return 0; +} + +static inline int hpet_set_periodic_freq(unsigned long freq) +{ + return 0; +} + +static inline int hpet_rtc_dropped_irq(void) +{ + return 0; +} + +static inline int hpet_rtc_timer_init(void) +{ + return 0; +} + +extern irq_handler_t hpet_rtc_interrupt; + +static inline int hpet_register_irq_handler(irq_handler_t handler) +{ + return 0; +} + +static inline int hpet_unregister_irq_handler(irq_handler_t handler) +{ + return 0; +} + +#endif + +/*----------------------------------------------------------------*/ + static int cmos_read_time(struct device *dev, struct rtc_time *t) { /* REVISIT: if the clock has a "century" register, use @@ -216,13 +263,14 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t) sec = t->time.tm_sec; sec = (sec < 60) ? BIN2BCD(sec) : 0xff; - hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min, t->time.tm_sec); spin_lock_irq(&rtc_lock); /* next rtc irq must not be from previous alarm setting */ rtc_control = CMOS_READ(RTC_CONTROL); rtc_control &= ~RTC_AIE; CMOS_WRITE(rtc_control, RTC_CONTROL); + hpet_mask_rtc_irq_bit(RTC_AIE); + rtc_intr = CMOS_READ(RTC_INTR_FLAGS); rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; if (is_intr(rtc_intr)) @@ -240,9 +288,16 @@ 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 (t->enabled) { rtc_control |= RTC_AIE; CMOS_WRITE(rtc_control, RTC_CONTROL); + hpet_set_rtc_irq_bit(RTC_AIE); + rtc_intr = CMOS_READ(RTC_INTR_FLAGS); rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; if (is_intr(rtc_intr)) @@ -270,8 +325,8 @@ static int cmos_irq_set_freq(struct device *dev, int freq) f = 16 - f; spin_lock_irqsave(&rtc_lock, flags); - if (!hpet_set_periodic_freq(freq)) - CMOS_WRITE(RTC_REF_CLCK_32KHZ | f, RTC_FREQ_SELECT); + hpet_set_periodic_freq(freq); + CMOS_WRITE(RTC_REF_CLCK_32KHZ | f, RTC_FREQ_SELECT); spin_unlock_irqrestore(&rtc_lock, flags); return 0; @@ -289,11 +344,13 @@ static int cmos_irq_set_state(struct device *dev, int enabled) spin_lock_irqsave(&rtc_lock, flags); rtc_control = CMOS_READ(RTC_CONTROL); - if (enabled) + if (enabled) { rtc_control |= RTC_PIE; - else + hpet_set_rtc_irq_bit(RTC_PIE); + } else { rtc_control &= ~RTC_PIE; - + hpet_mask_rtc_irq_bit(RTC_PIE); + } CMOS_WRITE(rtc_control, RTC_CONTROL); rtc_intr = CMOS_READ(RTC_INTR_FLAGS); @@ -319,11 +376,10 @@ cmos_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) case RTC_AIE_ON: case RTC_UIE_OFF: case RTC_UIE_ON: - case RTC_PIE_OFF: - case RTC_PIE_ON: if (!is_valid_irq(cmos->irq)) return -EINVAL; break; + /* PIE ON/OFF is handled by cmos_irq_set_state() */ default: return -ENOIOCTLCMD; } @@ -347,17 +403,8 @@ cmos_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) rtc_control |= RTC_UIE; hpet_set_rtc_irq_bit(RTC_UIE); break; - case RTC_PIE_OFF: /* periodic off */ - rtc_control &= ~RTC_PIE; - hpet_mask_rtc_irq_bit(RTC_PIE); - break; - case RTC_PIE_ON: /* periodic on */ - rtc_control |= RTC_PIE; - hpet_set_rtc_irq_bit(RTC_PIE); - break; } - if (!is_hpet_enabled()) - CMOS_WRITE(rtc_control, RTC_CONTROL); + CMOS_WRITE(rtc_control, RTC_CONTROL); rtc_intr = CMOS_READ(RTC_INTR_FLAGS); rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; @@ -505,18 +552,19 @@ static irqreturn_t cmos_interrupt(int irq, void *p) u8 rtc_control; spin_lock(&rtc_lock); - /* - * In this case it is HPET RTC interrupt handler - * calling us, with the interrupt information - * passed as arg1, instead of irq. + + /* When the HPET interrupt handler calls us, the interrupt + * status is passed as arg1 instead of the irq number. But + * always clear irq status, even when HPET is in the way. + * + * Note that HPET and RTC are almost certainly out of phase, + * giving different IRQ status ... */ + irqstat = CMOS_READ(RTC_INTR_FLAGS); + rtc_control = CMOS_READ(RTC_CONTROL); if (is_hpet_enabled()) irqstat = (unsigned long)irq & 0xF0; - else { - irqstat = CMOS_READ(RTC_INTR_FLAGS); - rtc_control = CMOS_READ(RTC_CONTROL); - irqstat &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; - } + irqstat &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; /* All Linux RTC alarms should be treated as if they were oneshot. * Similar code may be needed in system wakeup paths, in case the @@ -526,6 +574,8 @@ static irqreturn_t cmos_interrupt(int irq, void *p) rtc_control = CMOS_READ(RTC_CONTROL); rtc_control &= ~RTC_AIE; CMOS_WRITE(rtc_control, RTC_CONTROL); + hpet_mask_rtc_irq_bit(RTC_AIE); + CMOS_READ(RTC_INTR_FLAGS); } spin_unlock(&rtc_lock); @@ -632,8 +682,8 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) * do something about other clock frequencies. */ cmos_rtc.rtc->irq_freq = 1024; - if (!hpet_set_periodic_freq(cmos_rtc.rtc->irq_freq)) - CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT); + hpet_set_periodic_freq(cmos_rtc.rtc->irq_freq); + CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT); /* disable irqs. * @@ -643,6 +693,8 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) rtc_control = CMOS_READ(RTC_CONTROL); rtc_control &= ~(RTC_PIE | RTC_AIE | RTC_UIE); CMOS_WRITE(rtc_control, RTC_CONTROL); + hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE); + CMOS_READ(RTC_INTR_FLAGS); spin_unlock_irq(&rtc_lock); @@ -690,7 +742,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) goto cleanup2; } - pr_info("%s: alarms up to one %s%s\n", + pr_info("%s: alarms up to one %s%s%s\n", cmos_rtc.rtc->dev.bus_id, is_valid_irq(rtc_irq) ? (cmos_rtc.mon_alrm @@ -698,8 +750,8 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) : (cmos_rtc.day_alrm ? "month" : "day")) : "no", - cmos_rtc.century ? ", y3k" : "" - ); + cmos_rtc.century ? ", y3k" : "", + is_hpet_enabled() ? ", hpet irqs" : ""); return 0; @@ -720,8 +772,10 @@ static void cmos_do_shutdown(void) spin_lock_irq(&rtc_lock); rtc_control = CMOS_READ(RTC_CONTROL); - rtc_control &= ~(RTC_PIE|RTC_AIE|RTC_UIE); + rtc_control &= ~RTC_IRQMASK; CMOS_WRITE(rtc_control, RTC_CONTROL); + hpet_mask_rtc_irq_bit(RTC_IRQMASK); + CMOS_READ(RTC_INTR_FLAGS); spin_unlock_irq(&rtc_lock); } @@ -764,12 +818,16 @@ static int cmos_suspend(struct device *dev, pm_message_t mesg) cmos->suspend_ctrl = tmp = CMOS_READ(RTC_CONTROL); if (tmp & (RTC_PIE|RTC_AIE|RTC_UIE)) { unsigned char irqstat; + unsigned char mask; if (do_wake) - tmp &= ~(RTC_PIE|RTC_UIE); + mask = RTC_IRQMASK & ~RTC_AIE; else - tmp &= ~(RTC_PIE|RTC_AIE|RTC_UIE); + mask = RTC_IRQMASK; + tmp &= ~mask; CMOS_WRITE(tmp, RTC_CONTROL); + hpet_mask_rtc_irq_bit(mask); + irqstat = CMOS_READ(RTC_INTR_FLAGS); irqstat &= (tmp & RTC_IRQMASK) | RTC_IRQF; if (is_intr(irqstat)) @@ -799,7 +857,8 @@ static int cmos_resume(struct device *dev) unsigned char tmp = cmos->suspend_ctrl; /* re-enable any irqs previously active */ - if (tmp & (RTC_PIE|RTC_AIE|RTC_UIE)) { + if (tmp & RTC_IRQMASK) { + unsigned char mask; if (cmos->enabled_wake) { if (cmos->wake_off) @@ -810,18 +869,28 @@ static int cmos_resume(struct device *dev) } spin_lock_irq(&rtc_lock); - CMOS_WRITE(tmp, RTC_CONTROL); - tmp = CMOS_READ(RTC_INTR_FLAGS); - tmp &= (cmos->suspend_ctrl & RTC_IRQMASK) | RTC_IRQF; - if (is_intr(tmp)) - rtc_update_irq(cmos->rtc, 1, tmp); + do { + CMOS_WRITE(tmp, RTC_CONTROL); + hpet_set_rtc_irq_bit(tmp & RTC_IRQMASK); + + mask = CMOS_READ(RTC_INTR_FLAGS); + mask &= (tmp & RTC_IRQMASK) | RTC_IRQF; + if (!is_intr(mask)) + break; + + /* force one-shot behavior if HPET blocked + * the wake alarm's irq + */ + rtc_update_irq(cmos->rtc, 1, mask); + tmp &= ~RTC_AIE; + hpet_mask_rtc_irq_bit(RTC_AIE); + } while (mask & RTC_AIE); spin_unlock_irq(&rtc_lock); } pr_debug("%s: resume, ctrl %02x\n", cmos_rtc.rtc->dev.bus_id, - cmos->suspend_ctrl); - + tmp); return 0; } |