Merge branch 'for-linus' of git://one.firstfloor.org/home/andi/git/linux-2.6
[pandora-kernel.git] / arch / x86_64 / kernel / time.c
index d66c7f7..1c255ee 100644 (file)
@@ -276,6 +276,7 @@ static void set_rtc_mmss(unsigned long nowtime)
  *             Note: This function is required to return accurate
  *             time even in the absence of multiple timer ticks.
  */
+static inline unsigned long long cycles_2_ns(unsigned long long cyc);
 unsigned long long monotonic_clock(void)
 {
        unsigned long seq;
@@ -300,8 +301,7 @@ unsigned long long monotonic_clock(void)
                        base = monotonic_base;
                } while (read_seqretry(&xtime_lock, seq));
                this_offset = get_cycles_sync();
-               /* FIXME: 1000 or 1000000? */
-               offset = (this_offset - last_offset)*1000 / cpu_khz;
+               offset = cycles_2_ns(this_offset - last_offset);
        }
        return base + offset;
 }
@@ -405,8 +405,7 @@ void main_timer_handler(struct pt_regs *regs)
                        offset %= USEC_PER_TICK;
                }
 
-               /* FIXME: 1000 or 1000000? */
-               monotonic_base += (tsc - vxtime.last_tsc) * 1000000 / cpu_khz;
+               monotonic_base += cycles_2_ns(tsc - vxtime.last_tsc);
 
                vxtime.last_tsc = tsc - vxtime.quot * delay / vxtime.tsc_quot;
 
@@ -929,10 +928,8 @@ void __init time_init(void)
        vxtime.quot = (USEC_PER_SEC << US_SCALE) / vxtime_hz;
        vxtime.tsc_quot = (USEC_PER_MSEC << US_SCALE) / cpu_khz;
        vxtime.last_tsc = get_cycles_sync();
-       setup_irq(0, &irq0);
-
        set_cyc2ns_scale(cpu_khz);
-
+       setup_irq(0, &irq0);
        hotcpu_notifier(time_cpu_notifier, 0);
        time_cpu_notifier(NULL, CPU_ONLINE, (void *)(long)smp_processor_id());
 
@@ -1042,8 +1039,16 @@ static int timer_resume(struct sys_device *dev)
        unsigned long flags;
        unsigned long sec;
        unsigned long ctime = get_cmos_time();
-       unsigned long sleep_length = (ctime - sleep_start) * HZ;
+       long sleep_length = (ctime - sleep_start) * HZ;
 
+       if (sleep_length < 0) {
+               printk(KERN_WARNING "Time skew detected in timer resume!\n");
+               /* The time after the resume must not be earlier than the time
+                * before the suspend or some nasty things will happen
+                */
+               sleep_length = 0;
+               ctime = sleep_start;
+       }
        if (vxtime.hpet_address)
                hpet_reenable();
        else
@@ -1159,23 +1164,25 @@ int hpet_rtc_timer_init(void)
                hpet_rtc_int_freq = DEFAULT_RTC_INT_FREQ;
 
        local_irq_save(flags);
+
        cnt = hpet_readl(HPET_COUNTER);
        cnt += ((hpet_tick*HZ)/hpet_rtc_int_freq);
        hpet_writel(cnt, HPET_T1_CMP);
        hpet_t1_cmp = cnt;
-       local_irq_restore(flags);
 
        cfg = hpet_readl(HPET_T1_CFG);
        cfg &= ~HPET_TN_PERIODIC;
        cfg |= HPET_TN_ENABLE | HPET_TN_32BIT;
        hpet_writel(cfg, HPET_T1_CFG);
 
+       local_irq_restore(flags);
+
        return 1;
 }
 
 static void hpet_rtc_timer_reinit(void)
 {
-       unsigned int cfg, cnt;
+       unsigned int cfg, cnt, ticks_per_int, lost_ints;
 
        if (unlikely(!(PIE_on | AIE_on | UIE_on))) {
                cfg = hpet_readl(HPET_T1_CFG);
@@ -1190,10 +1197,33 @@ static void hpet_rtc_timer_reinit(void)
                hpet_rtc_int_freq = DEFAULT_RTC_INT_FREQ;
 
        /* It is more accurate to use the comparator value than current count.*/
-       cnt = hpet_t1_cmp;
-       cnt += hpet_tick*HZ/hpet_rtc_int_freq;
-       hpet_writel(cnt, HPET_T1_CMP);
-       hpet_t1_cmp = cnt;
+       ticks_per_int = hpet_tick * HZ / hpet_rtc_int_freq;
+       hpet_t1_cmp += ticks_per_int;
+       hpet_writel(hpet_t1_cmp, HPET_T1_CMP);
+
+       /*
+        * If the interrupt handler was delayed too long, the write above tries
+        * to schedule the next interrupt in the past and the hardware would
+        * not interrupt until the counter had wrapped around.
+        * So we have to check that the comparator wasn't set to a past time.
+        */
+       cnt = hpet_readl(HPET_COUNTER);
+       if (unlikely((int)(cnt - hpet_t1_cmp) > 0)) {
+               lost_ints = (cnt - hpet_t1_cmp) / ticks_per_int + 1;
+               /* Make sure that, even with the time needed to execute
+                * this code, the next scheduled interrupt has been moved
+                * back to the future: */
+               lost_ints++;
+
+               hpet_t1_cmp += lost_ints * ticks_per_int;
+               hpet_writel(hpet_t1_cmp, HPET_T1_CMP);
+
+               if (PIE_on)
+                       PIE_count += lost_ints;
+
+               printk(KERN_WARNING "rtc: lost some interrupts at %ldHz.\n",
+                      hpet_rtc_int_freq);
+       }
 }
 
 /*