/* Cyclone-timer: * This code implements timer_ops for the cyclone counter found * on IBM x440, x360, and other Summit based systems. * * Copyright (C) 2002 IBM, John Stultz (johnstul@us.ibm.com) */ #include #include #include #include #include #include #include #include #include #include #include "io_ports.h" extern spinlock_t i8253_lock; /* Number of usecs that the last interrupt was delayed */ static int delay_at_last_interrupt; #define CYCLONE_CBAR_ADDR 0xFEB00CD0 #define CYCLONE_PMCC_OFFSET 0x51A0 #define CYCLONE_MPMC_OFFSET 0x51D0 #define CYCLONE_MPCS_OFFSET 0x51A8 #define CYCLONE_TIMER_FREQ 100000000 #define CYCLONE_TIMER_MASK (((u64)1<<40)-1) /* 40 bit mask */ int use_cyclone = 0; static u32* volatile cyclone_timer; /* Cyclone MPMC0 register */ static u32 last_cyclone_low; static u32 last_cyclone_high; static unsigned long long monotonic_base; static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED; /* helper macro to atomically read both cyclone counter registers */ #define read_cyclone_counter(low,high) \ do{ \ high = cyclone_timer[1]; low = cyclone_timer[0]; \ } while (high != cyclone_timer[1]); static void mark_offset_cyclone(void) { unsigned long lost, delay; unsigned long delta = last_cyclone_low; int count; unsigned long long this_offset, last_offset; write_seqlock(&monotonic_lock); last_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low; spin_lock(&i8253_lock); read_cyclone_counter(last_cyclone_low,last_cyclone_high); /* read values for delay_at_last_interrupt */ outb_p(0x00, 0x43); /* latch the count ASAP */ count = inb_p(0x40); /* read the latched count */ count |= inb(0x40) << 8; /* * VIA686a test code... reset the latch if count > max + 1 * from timer_pit.c - cjb */ if (count > LATCH) { outb_p(0x34, PIT_MODE); outb_p(LATCH & 0xff, PIT_CH0); outb(LATCH >> 8, PIT_CH0); count = LATCH - 1; } spin_unlock(&i8253_lock); /* lost tick compensation */ delta = last_cyclone_low - delta; delta /= (CYCLONE_TIMER_FREQ/1000000); delta += delay_at_last_interrupt; lost = delta/(1000000/HZ); delay = delta%(1000000/HZ); if (lost >= 2) jiffies_64 += lost-1; /* update the monotonic base value */ this_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low; monotonic_base += (this_offset - last_offset) & CYCLONE_TIMER_MASK; write_sequnlock(&monotonic_lock); /* calculate delay_at_last_interrupt */ count = ((LATCH-1) - count) * TICK_SIZE; delay_at_last_interrupt = (count + LATCH/2) / LATCH; /* catch corner case where tick rollover occured * between cyclone and pit reads (as noted when * usec delta is > 90% # of usecs/tick) */ if (lost && abs(delay - delay_at_last_interrupt) > (900000/HZ)) jiffies_64++; } static unsigned long get_offset_cyclone(void) { u32 offset; if(!cyclone_timer) return delay_at_last_interrupt; /* Read the cyclone timer */ offset = cyclone_timer[0]; /* .. relative to previous jiffy */ offset = offset - last_cyclone_low; /* convert cyclone ticks to microseconds */ /* XXX slow, can we speed this up? */ offset = offset/(CYCLONE_TIMER_FREQ/1000000); /* our adjusted time offset in microseconds */ return delay_at_last_interrupt + offset; } static unsigned long long monotonic_clock_cyclone(void) { u32 now_low, now_high; unsigned long long last_offset, this_offset, base; unsigned long long ret; unsigned seq; /* atomically read monotonic base & last_offset */ do { seq = read_seqbegin(&monotonic_lock); last_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low; base = monotonic_base; } while (read_seqretry(&monotonic_lock, seq)); /* Read the cyclone counter */ read_cyclone_counter(now_low,now_high); this_offset = ((unsigned long long)now_high<<32)|now_low; /* convert to nanoseconds */ ret = base + ((this_offset - last_offset)&CYCLONE_TIMER_MASK); return ret * (1000000000 / CYCLONE_TIMER_FREQ); } static int __init init_cyclone(char* override) { u32* reg; u32 base; /* saved cyclone base address */ u32 pageaddr; /* page that contains cyclone_timer register */ u32 offset; /* offset from pageaddr to cyclone_timer register */ int i; /* check clock override */ if (override[0] && strncmp(override,"cyclone",7)) return -ENODEV; /*make sure we're on a summit box*/ if(!use_cyclone) return -ENODEV; printk(KERN_INFO "Summit chipset: Starting Cyclone Counter.\n"); /* find base address */ pageaddr = (CYCLONE_CBAR_ADDR)&PAGE_MASK; offset = (CYCLONE_CBAR_ADDR)&(~PAGE_MASK); set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr); reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset); if(!reg){ printk(KERN_ERR "Summit chipset: Could not find valid CBAR register.\n"); return -ENODEV; } base = *reg; if(!base){ printk(KERN_ERR "Summit chipset: Could not find valid CBAR value.\n"); return -ENODEV; } /* setup PMCC */ pageaddr = (base + CYCLONE_PMCC_OFFSET)&PAGE_MASK; offset = (base + CYCLONE_PMCC_OFFSET)&(~PAGE_MASK); set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr); reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset); if(!reg){ printk(KERN_ERR "Summit chipset: Could not find valid PMCC register.\n"); return -ENODEV; } reg[0] = 0x00000001; /* setup MPCS */ pageaddr = (base + CYCLONE_MPCS_OFFSET)&PAGE_MASK; offset = (base + CYCLONE_MPCS_OFFSET)&(~PAGE_MASK); set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr); reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset); if(!reg){ printk(KERN_ERR "Summit chipset: Could not find valid MPCS register.\n"); return -ENODEV; } reg[0] = 0x00000001; /* map in cyclone_timer */ pageaddr = (base + CYCLONE_MPMC_OFFSET)&PAGE_MASK; offset = (base + CYCLONE_MPMC_OFFSET)&(~PAGE_MASK); set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr); cyclone_timer = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset); if(!cyclone_timer){ printk(KERN_ERR "Summit chipset: Could not find valid MPMC register.\n"); return -ENODEV; } /*quick test to make sure its ticking*/ for(i=0; i<3; i++){ u32 old = cyclone_timer[0]; int stall = 100; while(stall--) barrier(); if(cyclone_timer[0] == old){ printk(KERN_ERR "Summit chipset: Counter not counting! DISABLED\n"); cyclone_timer = 0; return -ENODEV; } } init_cpu_khz(); /* Everything looks good! */ return 0; } static void delay_cyclone(unsigned long loops) { unsigned long bclock, now; if(!cyclone_timer) return; bclock = cyclone_timer[0]; do { rep_nop(); now = cyclone_timer[0]; } while ((now-bclock) < loops); } /************************************************************/ /* cyclone timer_opts struct */ static struct timer_opts timer_cyclone = { .name = "cyclone", .mark_offset = mark_offset_cyclone, .get_offset = get_offset_cyclone, .monotonic_clock = monotonic_clock_cyclone, .delay = delay_cyclone, }; struct init_timer_opts __initdata timer_cyclone_init = { .init = init_cyclone, .opts = &timer_cyclone, };