| /* 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 <linux/spinlock.h> |
| #include <linux/init.h> |
| #include <linux/timex.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/jiffies.h> |
| |
| #include <asm/timer.h> |
| #include <asm/io.h> |
| #include <asm/pgtable.h> |
| #include <asm/fixmap.h> |
| #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, |
| }; |