fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / arch / i386 / kernel / time_hpet.c
index 86b79c6..1e4702d 100644 (file)
 #include <asm/apic.h>
 
 #include <linux/timex.h>
-#include <linux/config.h>
 
 #include <asm/hpet.h>
 #include <linux/hpet.h>
 
-unsigned long hpet_period;     /* fsecs / HPET clock */
-unsigned long hpet_tick;       /* hpet clks count per tick */
-unsigned long hpet_address;    /* hpet memory map physical address */
+static unsigned long hpet_period;      /* fsecs / HPET clock */
+unsigned long hpet_tick;               /* hpet clks count per tick */
+unsigned long hpet_address;            /* hpet memory map physical address */
+int hpet_use_timer;
 
 static int use_hpet;           /* can be used for runtime check of hpet */
 static int boot_hpet_disable;  /* boottime override for HPET timer */
@@ -38,7 +38,7 @@ int hpet_readl(unsigned long a)
        return readl(hpet_virt_address + a);
 }
 
-void hpet_writel(unsigned long d, unsigned long a)
+static void hpet_writel(unsigned long d, unsigned long a)
 {
        writel(d, hpet_virt_address + a);
 }
@@ -49,7 +49,7 @@ void hpet_writel(unsigned long d, unsigned long a)
  * comparator value and continue. Next tick can be caught by checking
  * for a change in the comparator value. Used in apic.c.
  */
-void __init wait_hpet_tick(void)
+static void __devinit wait_hpet_tick(void)
 {
        unsigned int start_cmp_val, end_cmp_val;
 
@@ -73,26 +73,30 @@ static int hpet_timer_stop_set_go(unsigned long tick)
        hpet_writel(0, HPET_COUNTER);
        hpet_writel(0, HPET_COUNTER + 4);
 
-       /*
-        * Set up timer 0, as periodic with first interrupt to happen at
-        * hpet_tick, and period also hpet_tick.
-        */
-       cfg = hpet_readl(HPET_T0_CFG);
-       cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC |
-              HPET_TN_SETVAL | HPET_TN_32BIT;
-       hpet_writel(cfg, HPET_T0_CFG);
-       /*
-        * Some systems seems to need two writes to HPET_T0_CMP,
-        * to get interrupts working
-        */
-       hpet_writel(tick, HPET_T0_CMP);
-       hpet_writel(tick, HPET_T0_CMP);
+       if (hpet_use_timer) {
+               /*
+                * Set up timer 0, as periodic with first interrupt to happen at
+                * hpet_tick, and period also hpet_tick.
+                */
+               cfg = hpet_readl(HPET_T0_CFG);
+               cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC |
+                      HPET_TN_SETVAL | HPET_TN_32BIT;
+               hpet_writel(cfg, HPET_T0_CFG);
 
+               /*
+                * The first write after writing TN_SETVAL to the config register sets
+                * the counter value, the second write sets the threshold.
+                */
+               hpet_writel(tick, HPET_T0_CMP);
+               hpet_writel(tick, HPET_T0_CMP);
+       }
        /*
         * Go!
         */
        cfg = hpet_readl(HPET_CFG);
-       cfg |= HPET_CFG_ENABLE | HPET_CFG_LEGACY;
+       if (hpet_use_timer)
+               cfg |= HPET_CFG_LEGACY;
+       cfg |= HPET_CFG_ENABLE;
        hpet_writel(cfg, HPET_CFG);
 
        return 0;
@@ -121,17 +125,27 @@ int __init hpet_enable(void)
        id = hpet_readl(HPET_ID);
 
        /*
-        * We are checking for value '1' or more in number field.
-        * So, we are OK with HPET_EMULATE_RTC part too, where we need
-        * to have atleast 2 timers.
+        * We are checking for value '1' or more in number field if
+        * CONFIG_HPET_EMULATE_RTC is set because we will need an
+        * additional timer for RTC emulation.
+        * However, we can do with one timer otherwise using the
+        * the single HPET timer for system time.
         */
-       if (!(id & HPET_ID_NUMBER) ||
-           !(id & HPET_ID_LEGSUP))
+#ifdef CONFIG_HPET_EMULATE_RTC
+       if (!(id & HPET_ID_NUMBER)) {
+               iounmap(hpet_virt_address);
+               hpet_virt_address = NULL;
                return -1;
+       }
+#endif
+
 
        hpet_period = hpet_readl(HPET_PERIOD);
-       if ((hpet_period < HPET_MIN_PERIOD) || (hpet_period > HPET_MAX_PERIOD))
+       if ((hpet_period < HPET_MIN_PERIOD) || (hpet_period > HPET_MAX_PERIOD)) {
+               iounmap(hpet_virt_address);
+               hpet_virt_address = NULL;
                return -1;
+       }
 
        /*
         * 64 bit math
@@ -146,8 +160,13 @@ int __init hpet_enable(void)
        if (hpet_tick_rem > (hpet_period >> 1))
                hpet_tick++; /* rounding the result */
 
-       if (hpet_timer_stop_set_go(hpet_tick))
+       hpet_use_timer = id & HPET_ID_LEGSUP;
+
+       if (hpet_timer_stop_set_go(hpet_tick)) {
+               iounmap(hpet_virt_address);
+               hpet_virt_address = NULL;
                return -1;
+       }
 
        use_hpet = 1;
 
@@ -196,7 +215,8 @@ int __init hpet_enable(void)
 #endif
 
 #ifdef CONFIG_X86_LOCAL_APIC
-       wait_timer_tick = wait_hpet_tick;
+       if (hpet_use_timer)
+               wait_timer_tick = wait_hpet_tick;
 #endif
        return 0;
 }
@@ -247,8 +267,6 @@ __setup("hpet=", hpet_setup);
 #include <linux/mc146818rtc.h>
 #include <linux/rtc.h>
 
-extern irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs);
-
 #define DEFAULT_RTC_INT_FREQ   64
 #define RTC_NUM_INTS           1
 
@@ -263,6 +281,7 @@ static unsigned long PIE_freq = DEFAULT_RTC_INT_FREQ;
 static unsigned long PIE_count;
 
 static unsigned long hpet_rtc_int_freq; /* RTC interrupt frequency */
+static unsigned int hpet_t1_cmp; /* cached comparator register */
 
 /*
  * Timer 1 for RTC, we do not use periodic interrupt feature,
@@ -291,24 +310,32 @@ 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);
-       local_irq_restore(flags);
+       hpet_t1_cmp = cnt;
 
        cfg = hpet_readl(HPET_T1_CFG);
-       cfg |= HPET_TN_ENABLE | HPET_TN_SETVAL | HPET_TN_32BIT;
+       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 (!(PIE_on | AIE_on | UIE_on))
+       if (unlikely(!(PIE_on | AIE_on | UIE_on))) {
+               cfg = hpet_readl(HPET_T1_CFG);
+               cfg &= ~HPET_TN_ENABLE;
+               hpet_writel(cfg, HPET_T1_CFG);
                return;
+       }
 
        if (PIE_on && (PIE_freq > DEFAULT_RTC_INT_FREQ))
                hpet_rtc_int_freq = PIE_freq;
@@ -316,15 +343,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_readl(HPET_T1_CMP);
-       cnt += hpet_tick*HZ/hpet_rtc_int_freq;
-       hpet_writel(cnt, HPET_T1_CMP);
+       ticks_per_int = hpet_tick * HZ / hpet_rtc_int_freq;
+       hpet_t1_cmp += ticks_per_int;
+       hpet_writel(hpet_t1_cmp, HPET_T1_CMP);
 
-       cfg = hpet_readl(HPET_T1_CFG);
-       cfg |= HPET_TN_ENABLE | HPET_TN_SETVAL | HPET_TN_32BIT;
-       hpet_writel(cfg, HPET_T1_CFG);
+       /*
+        * 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);
 
-       return;
+               if (PIE_on)
+                       PIE_count += lost_ints;
+
+               printk(KERN_WARNING "rtc: lost some interrupts at %ldHz.\n",
+                      hpet_rtc_int_freq);
+       }
 }
 
 /*
@@ -405,7 +450,7 @@ int hpet_rtc_dropped_irq(void)
        return 1;
 }
 
-irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id)
 {
        struct rtc_time curr_time;
        unsigned long rtc_int_flag = 0;
@@ -444,7 +489,7 @@ irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
        }
        if (call_rtc_interrupt) {
                rtc_int_flag |= (RTC_IRQF | (RTC_NUM_INTS << 8));
-               rtc_interrupt(rtc_int_flag, dev_id, regs);
+               rtc_interrupt(rtc_int_flag, dev_id);
        }
        return IRQ_HANDLED;
 }