#include <linux/i8253.h>
 #include <linux/init.h>
 #include <linux/kernel_stat.h>
+#include <linux/math64.h>
 #include <linux/sched.h>
 #include <linux/spinlock.h>
 #include <linux/interrupt.h>
 {
        unsigned long flags;
        unsigned int count, start;
+       unsigned char secs1, secs2, ctrl;
+       int secs;
        cycle_t giccount = 0, gicstart = 0;
 
 #if defined(CONFIG_KVM_GUEST) && CONFIG_KVM_GUEST_TIMER_FREQ
        if (gic_present)
                gic_start_count();
 
-       /* Read counter exactly on falling edge of update flag. */
+       /*
+        * Read counters exactly on rising edge of update flag.
+        * This helps get an accurate reading under virtualisation.
+        */
        while (CMOS_READ(RTC_REG_A) & RTC_UIP);
        while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));
-
        start = read_c0_count();
        if (gic_present)
                gicstart = gic_read_count();
 
-       /* Read counter exactly on falling edge of update flag. */
+       /* Wait for falling edge before reading RTC. */
        while (CMOS_READ(RTC_REG_A) & RTC_UIP);
-       while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));
+       secs1 = CMOS_READ(RTC_SECONDS);
 
+       /* Read counters again exactly on rising edge of update flag. */
+       while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));
        count = read_c0_count();
        if (gic_present)
                giccount = gic_read_count();
 
+       /* Wait for falling edge before reading RTC again. */
+       while (CMOS_READ(RTC_REG_A) & RTC_UIP);
+       secs2 = CMOS_READ(RTC_SECONDS);
+
+       ctrl = CMOS_READ(RTC_CONTROL);
+
        local_irq_restore(flags);
 
+       if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+               secs1 = bcd2bin(secs1);
+               secs2 = bcd2bin(secs2);
+       }
+       secs = secs2 - secs1;
+       if (secs < 1)
+               secs += 60;
+
        count -= start;
+       count /= secs;
        mips_hpt_frequency = count;
 
        if (gic_present) {
-               giccount -= gicstart;
+               giccount = div_u64(giccount - gicstart, secs);
                gic_frequency = giccount;
        }
 }