fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / arch / i386 / kernel / time_hpet.c
index 0ec677d..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 */
-static unsigned long hpet_virt_address;        /* hpet kernel virtual address */
+static void __iomem * hpet_virt_address;       /* hpet kernel virtual address */
 
 #define FSEC_TO_USEC (1000000000UL)
 
@@ -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;
 
@@ -60,13 +60,55 @@ void __init wait_hpet_tick(void)
 }
 #endif
 
+static int hpet_timer_stop_set_go(unsigned long tick)
+{
+       unsigned int cfg;
+
+       /*
+        * Stop the timers and reset the main counter.
+        */
+       cfg = hpet_readl(HPET_CFG);
+       cfg &= ~HPET_CFG_ENABLE;
+       hpet_writel(cfg, HPET_CFG);
+       hpet_writel(0, HPET_COUNTER);
+       hpet_writel(0, HPET_COUNTER + 4);
+
+       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);
+       if (hpet_use_timer)
+               cfg |= HPET_CFG_LEGACY;
+       cfg |= HPET_CFG_ENABLE;
+       hpet_writel(cfg, HPET_CFG);
+
+       return 0;
+}
+
 /*
  * Check whether HPET was found by ACPI boot parse. If yes setup HPET
  * counter 0 for kernel base timer.
  */
 int __init hpet_enable(void)
 {
-       unsigned int cfg, id;
+       unsigned int id;
        unsigned long tick_fsec_low, tick_fsec_high; /* tick in femto sec */
        unsigned long hpet_tick_rem;
 
@@ -76,25 +118,34 @@ int __init hpet_enable(void)
        if (!hpet_address) {
                return -1;
        }
-       hpet_virt_address = (unsigned long) ioremap_nocache(hpet_address,
-                                                           HPET_MMAP_SIZE);
+       hpet_virt_address = ioremap_nocache(hpet_address, HPET_MMAP_SIZE);
        /*
         * Read the period, compute tick and quotient.
         */
        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
@@ -109,31 +160,13 @@ int __init hpet_enable(void)
        if (hpet_tick_rem > (hpet_period >> 1))
                hpet_tick++; /* rounding the result */
 
-       /*
-        * Stop the timers and reset the main counter.
-        */
-       cfg = hpet_readl(HPET_CFG);
-       cfg &= ~HPET_CFG_ENABLE;
-       hpet_writel(cfg, HPET_CFG);
-       hpet_writel(0, HPET_COUNTER);
-       hpet_writel(0, HPET_COUNTER + 4);
+       hpet_use_timer = id & HPET_ID_LEGSUP;
 
-       /*
-        * 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);
-       hpet_writel(hpet_tick, HPET_T0_CMP);
-
-       /*
-        * Go!
-        */
-       cfg = hpet_readl(HPET_CFG);
-       cfg |= HPET_CFG_ENABLE | HPET_CFG_LEGACY;
-       hpet_writel(cfg, HPET_CFG);
+       if (hpet_timer_stop_set_go(hpet_tick)) {
+               iounmap(hpet_virt_address);
+               hpet_virt_address = NULL;
+               return -1;
+       }
 
        use_hpet = 1;
 
@@ -152,6 +185,7 @@ int __init hpet_enable(void)
                 * Register with driver.
                 * Timer0 and Timer1 is used by platform.
                 */
+               hd.hd_phys_address = hpet_address;
                hd.hd_address = hpet_virt_address;
                hd.hd_nirqs = ntimer;
                hd.hd_flags = HPET_DATA_PLATFORM;
@@ -162,11 +196,11 @@ int __init hpet_enable(void)
                hd.hd_irq[0] = HPET_LEGACY_8254;
                hd.hd_irq[1] = HPET_LEGACY_RTC;
                if (ntimer > 2) {
-                       struct hpet             *hpet;
-                       struct hpet_timer       *timer;
+                       struct hpet __iomem     *hpet;
+                       struct hpet_timer __iomem *timer;
                        int                     i;
 
-                       hpet = (struct hpet *) hpet_virt_address;
+                       hpet = hpet_virt_address;
 
                        for (i = 2, timer = &hpet->hpet_timers[2]; i < ntimer;
                                timer++, i++)
@@ -181,11 +215,17 @@ 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;
 }
 
+int hpet_reenable(void)
+{
+       return hpet_timer_stop_set_go(hpet_tick);
+}
+
 int is_hpet_enabled(void)
 {
        return use_hpet;
@@ -227,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
 
@@ -243,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,
@@ -271,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;
@@ -296,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);
+       }
 }
 
 /*
@@ -385,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;
@@ -424,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;
 }