| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 1 | /* | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 2 | * arch/blackfin/kernel/time.c | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 3 | * | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 4 | * This file contains the Blackfin-specific time handling details. | 
|  | 5 | * Most of the stuff is located in the machine specific files. | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 6 | * | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 7 | * Copyright 2004-2008 Analog Devices Inc. | 
|  | 8 | * Licensed under the GPL-2 or later. | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 9 | */ | 
|  | 10 |  | 
|  | 11 | #include <linux/module.h> | 
|  | 12 | #include <linux/profile.h> | 
|  | 13 | #include <linux/interrupt.h> | 
|  | 14 | #include <linux/time.h> | 
|  | 15 | #include <linux/irq.h> | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 16 | #include <linux/delay.h> | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 17 |  | 
|  | 18 | #include <asm/blackfin.h> | 
| Michael Hennerich | e6c91b6 | 2008-04-25 04:58:29 +0800 | [diff] [blame] | 19 | #include <asm/time.h> | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 20 | #include <asm/gptimers.h> | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 21 |  | 
|  | 22 | /* This is an NTP setting */ | 
|  | 23 | #define	TICK_SIZE (tick_nsec / 1000) | 
|  | 24 |  | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 25 | static struct irqaction bfin_timer_irq = { | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 26 | .name = "Blackfin Timer Tick", | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 27 | #ifdef CONFIG_IRQ_PER_CPU | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 28 | .flags = IRQF_DISABLED | IRQF_PERCPU, | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 29 | #else | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 30 | .flags = IRQF_DISABLED | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 31 | #endif | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 32 | }; | 
|  | 33 |  | 
| Yi Li | 6a01f23 | 2009-01-07 23:14:39 +0800 | [diff] [blame] | 34 | #if defined(CONFIG_TICK_SOURCE_SYSTMR0) || defined(CONFIG_IPIPE) | 
| Mike Frysinger | a1ee74c | 2009-01-07 23:14:38 +0800 | [diff] [blame] | 35 | void __init setup_system_timer0(void) | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 36 | { | 
|  | 37 | /* Power down the core timer, just to play safe. */ | 
|  | 38 | bfin_write_TCNTL(0); | 
|  | 39 |  | 
|  | 40 | disable_gptimers(TIMER0bit); | 
|  | 41 | set_gptimer_status(0, TIMER_STATUS_TRUN0); | 
|  | 42 | while (get_gptimer_status(0) & TIMER_STATUS_TRUN0) | 
|  | 43 | udelay(10); | 
|  | 44 |  | 
|  | 45 | set_gptimer_config(0, 0x59); /* IRQ enable, periodic, PWM_OUT, SCLKed, OUT PAD disabled */ | 
|  | 46 | set_gptimer_period(TIMER0_id, get_sclk() / HZ); | 
|  | 47 | set_gptimer_pwidth(TIMER0_id, 1); | 
|  | 48 | SSYNC(); | 
|  | 49 | enable_gptimers(TIMER0bit); | 
|  | 50 | } | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 51 | #else | 
| Mike Frysinger | a1ee74c | 2009-01-07 23:14:38 +0800 | [diff] [blame] | 52 | void __init setup_core_timer(void) | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 53 | { | 
|  | 54 | u32 tcount; | 
|  | 55 |  | 
|  | 56 | /* power up the timer, but don't enable it just yet */ | 
|  | 57 | bfin_write_TCNTL(1); | 
|  | 58 | CSYNC(); | 
|  | 59 |  | 
|  | 60 | /* the TSCALE prescaler counter */ | 
|  | 61 | bfin_write_TSCALE(TIME_SCALE - 1); | 
|  | 62 |  | 
|  | 63 | tcount = ((get_cclk() / (HZ * TIME_SCALE)) - 1); | 
|  | 64 | bfin_write_TPERIOD(tcount); | 
|  | 65 | bfin_write_TCOUNT(tcount); | 
|  | 66 |  | 
|  | 67 | /* now enable the timer */ | 
|  | 68 | CSYNC(); | 
|  | 69 |  | 
|  | 70 | bfin_write_TCNTL(7); | 
|  | 71 | } | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 72 | #endif | 
|  | 73 |  | 
| Mike Frysinger | a1ee74c | 2009-01-07 23:14:38 +0800 | [diff] [blame] | 74 | static void __init | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 75 | time_sched_init(irqreturn_t(*timer_routine) (int, void *)) | 
|  | 76 | { | 
| Yi Li | 6a01f23 | 2009-01-07 23:14:39 +0800 | [diff] [blame] | 77 | #if defined(CONFIG_TICK_SOURCE_SYSTMR0) || defined(CONFIG_IPIPE) | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 78 | setup_system_timer0(); | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 79 | bfin_timer_irq.handler = timer_routine; | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 80 | setup_irq(IRQ_TIMER0, &bfin_timer_irq); | 
|  | 81 | #else | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 82 | setup_core_timer(); | 
|  | 83 | bfin_timer_irq.handler = timer_routine; | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 84 | setup_irq(IRQ_CORETMR, &bfin_timer_irq); | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 85 | #endif | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 86 | } | 
|  | 87 |  | 
|  | 88 | /* | 
|  | 89 | * Should return useconds since last timer tick | 
|  | 90 | */ | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 91 | #ifndef CONFIG_GENERIC_TIME | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 92 | static unsigned long gettimeoffset(void) | 
|  | 93 | { | 
|  | 94 | unsigned long offset; | 
|  | 95 | unsigned long clocks_per_jiffy; | 
|  | 96 |  | 
| Yi Li | 6a01f23 | 2009-01-07 23:14:39 +0800 | [diff] [blame] | 97 | #if defined(CONFIG_TICK_SOURCE_SYSTMR0) || defined(CONFIG_IPIPE) | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 98 | clocks_per_jiffy = bfin_read_TIMER0_PERIOD(); | 
|  | 99 | offset = bfin_read_TIMER0_COUNTER() / \ | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 100 | (((clocks_per_jiffy + 1) * HZ) / USEC_PER_SEC); | 
|  | 101 |  | 
|  | 102 | if ((get_gptimer_status(0) & TIMER_STATUS_TIMIL0) && offset < (100000 / HZ / 2)) | 
|  | 103 | offset += (USEC_PER_SEC / HZ); | 
|  | 104 | #else | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 105 | clocks_per_jiffy = bfin_read_TPERIOD(); | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 106 | offset = (clocks_per_jiffy - bfin_read_TCOUNT()) / \ | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 107 | (((clocks_per_jiffy + 1) * HZ) / USEC_PER_SEC); | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 108 |  | 
|  | 109 | /* Check if we just wrapped the counters and maybe missed a tick */ | 
|  | 110 | if ((bfin_read_ILAT() & (1 << IRQ_CORETMR)) | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 111 | && (offset < (100000 / HZ / 2))) | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 112 | offset += (USEC_PER_SEC / HZ); | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 113 | #endif | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 114 | return offset; | 
|  | 115 | } | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 116 | #endif | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 117 |  | 
|  | 118 | static inline int set_rtc_mmss(unsigned long nowtime) | 
|  | 119 | { | 
|  | 120 | return 0; | 
|  | 121 | } | 
|  | 122 |  | 
|  | 123 | /* | 
|  | 124 | * timer_interrupt() needs to keep up the real-time clock, | 
|  | 125 | * as well as call the "do_timer()" routine every clocktick | 
|  | 126 | */ | 
|  | 127 | #ifdef CONFIG_CORE_TIMER_IRQ_L1 | 
| Mike Frysinger | 1b047d8 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 128 | __attribute__((l1_text)) | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 129 | #endif | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 130 | irqreturn_t timer_interrupt(int irq, void *dummy) | 
|  | 131 | { | 
|  | 132 | /* last time the cmos clock got updated */ | 
| Mike Frysinger | 1f83b8f | 2007-07-12 22:58:21 +0800 | [diff] [blame] | 133 | static long last_rtc_update; | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 134 |  | 
|  | 135 | write_seqlock(&xtime_lock); | 
| Yi Li | 6a01f23 | 2009-01-07 23:14:39 +0800 | [diff] [blame] | 136 | #if defined(CONFIG_TICK_SOURCE_SYSTMR0) && !defined(CONFIG_IPIPE) | 
| Philippe Gerum | 9bd50df | 2009-03-04 16:52:38 +0800 | [diff] [blame] | 137 | /* | 
|  | 138 | * TIMIL0 is latched in __ipipe_grab_irq() when the I-Pipe is | 
|  | 139 | * enabled. | 
|  | 140 | */ | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 141 | if (get_gptimer_status(0) & TIMER_STATUS_TIMIL0) { | 
|  | 142 | #endif | 
|  | 143 | do_timer(1); | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 144 |  | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 145 | /* | 
|  | 146 | * If we have an externally synchronized Linux clock, then update | 
|  | 147 | * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be | 
|  | 148 | * called as close as possible to 500 ms before the new second starts. | 
|  | 149 | */ | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 150 | if (ntp_synced() && | 
|  | 151 | xtime.tv_sec > last_rtc_update + 660 && | 
|  | 152 | (xtime.tv_nsec / NSEC_PER_USEC) >= | 
|  | 153 | 500000 - ((unsigned)TICK_SIZE) / 2 | 
|  | 154 | && (xtime.tv_nsec / NSEC_PER_USEC) <= | 
|  | 155 | 500000 + ((unsigned)TICK_SIZE) / 2) { | 
|  | 156 | if (set_rtc_mmss(xtime.tv_sec) == 0) | 
|  | 157 | last_rtc_update = xtime.tv_sec; | 
|  | 158 | else | 
|  | 159 | /* Do it again in 60s. */ | 
|  | 160 | last_rtc_update = xtime.tv_sec - 600; | 
|  | 161 | } | 
| Yi Li | 6a01f23 | 2009-01-07 23:14:39 +0800 | [diff] [blame] | 162 | #if defined(CONFIG_TICK_SOURCE_SYSTMR0) && !defined(CONFIG_IPIPE) | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 163 | set_gptimer_status(0, TIMER_STATUS_TIMIL0); | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 164 | } | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 165 | #endif | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 166 | write_sequnlock(&xtime_lock); | 
| Peter Zijlstra | aa02cd2 | 2008-02-13 21:33:16 +0100 | [diff] [blame] | 167 |  | 
| Yi Li | 6a01f23 | 2009-01-07 23:14:39 +0800 | [diff] [blame] | 168 | #ifdef CONFIG_IPIPE | 
|  | 169 | update_root_process_times(get_irq_regs()); | 
|  | 170 | #else | 
| Peter Zijlstra | aa02cd2 | 2008-02-13 21:33:16 +0100 | [diff] [blame] | 171 | update_process_times(user_mode(get_irq_regs())); | 
| Yi Li | 6a01f23 | 2009-01-07 23:14:39 +0800 | [diff] [blame] | 172 | #endif | 
| Graf Yang | 8f65873 | 2008-11-18 17:48:22 +0800 | [diff] [blame] | 173 | profile_tick(CPU_PROFILING); | 
| Peter Zijlstra | aa02cd2 | 2008-02-13 21:33:16 +0100 | [diff] [blame] | 174 |  | 
| Bryan Wu | 1394f03 | 2007-05-06 14:50:22 -0700 | [diff] [blame] | 175 | return IRQ_HANDLED; | 
|  | 176 | } | 
|  | 177 |  | 
|  | 178 | void __init time_init(void) | 
|  | 179 | { | 
|  | 180 | time_t secs_since_1970 = (365 * 37 + 9) * 24 * 60 * 60;	/* 1 Jan 2007 */ | 
|  | 181 |  | 
|  | 182 | #ifdef CONFIG_RTC_DRV_BFIN | 
|  | 183 | /* [#2663] hack to filter junk RTC values that would cause | 
|  | 184 | * userspace to have to deal with time values greater than | 
|  | 185 | * 2^31 seconds (which uClibc cannot cope with yet) | 
|  | 186 | */ | 
|  | 187 | if ((bfin_read_RTC_STAT() & 0xC0000000) == 0xC0000000) { | 
|  | 188 | printk(KERN_NOTICE "bfin-rtc: invalid date; resetting\n"); | 
|  | 189 | bfin_write_RTC_STAT(0); | 
|  | 190 | } | 
|  | 191 | #endif | 
|  | 192 |  | 
|  | 193 | /* Initialize xtime. From now on, xtime is updated with timer interrupts */ | 
|  | 194 | xtime.tv_sec = secs_since_1970; | 
|  | 195 | xtime.tv_nsec = 0; | 
|  | 196 |  | 
|  | 197 | wall_to_monotonic.tv_sec = -xtime.tv_sec; | 
|  | 198 |  | 
|  | 199 | time_sched_init(timer_interrupt); | 
|  | 200 | } | 
|  | 201 |  | 
|  | 202 | #ifndef CONFIG_GENERIC_TIME | 
|  | 203 | void do_gettimeofday(struct timeval *tv) | 
|  | 204 | { | 
|  | 205 | unsigned long flags; | 
|  | 206 | unsigned long seq; | 
|  | 207 | unsigned long usec, sec; | 
|  | 208 |  | 
|  | 209 | do { | 
|  | 210 | seq = read_seqbegin_irqsave(&xtime_lock, flags); | 
|  | 211 | usec = gettimeoffset(); | 
|  | 212 | sec = xtime.tv_sec; | 
|  | 213 | usec += (xtime.tv_nsec / NSEC_PER_USEC); | 
|  | 214 | } | 
|  | 215 | while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); | 
|  | 216 |  | 
|  | 217 | while (usec >= USEC_PER_SEC) { | 
|  | 218 | usec -= USEC_PER_SEC; | 
|  | 219 | sec++; | 
|  | 220 | } | 
|  | 221 |  | 
|  | 222 | tv->tv_sec = sec; | 
|  | 223 | tv->tv_usec = usec; | 
|  | 224 | } | 
|  | 225 | EXPORT_SYMBOL(do_gettimeofday); | 
|  | 226 |  | 
|  | 227 | int do_settimeofday(struct timespec *tv) | 
|  | 228 | { | 
|  | 229 | time_t wtm_sec, sec = tv->tv_sec; | 
|  | 230 | long wtm_nsec, nsec = tv->tv_nsec; | 
|  | 231 |  | 
|  | 232 | if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) | 
|  | 233 | return -EINVAL; | 
|  | 234 |  | 
|  | 235 | write_seqlock_irq(&xtime_lock); | 
|  | 236 | /* | 
|  | 237 | * This is revolting. We need to set the xtime.tv_usec | 
|  | 238 | * correctly. However, the value in this location is | 
|  | 239 | * is value at the last tick. | 
|  | 240 | * Discover what correction gettimeofday | 
|  | 241 | * would have done, and then undo it! | 
|  | 242 | */ | 
|  | 243 | nsec -= (gettimeoffset() * NSEC_PER_USEC); | 
|  | 244 |  | 
|  | 245 | wtm_sec = wall_to_monotonic.tv_sec + (xtime.tv_sec - sec); | 
|  | 246 | wtm_nsec = wall_to_monotonic.tv_nsec + (xtime.tv_nsec - nsec); | 
|  | 247 |  | 
|  | 248 | set_normalized_timespec(&xtime, sec, nsec); | 
|  | 249 | set_normalized_timespec(&wall_to_monotonic, wtm_sec, wtm_nsec); | 
|  | 250 |  | 
|  | 251 | ntp_clear(); | 
|  | 252 |  | 
|  | 253 | write_sequnlock_irq(&xtime_lock); | 
|  | 254 | clock_was_set(); | 
|  | 255 |  | 
|  | 256 | return 0; | 
|  | 257 | } | 
|  | 258 | EXPORT_SYMBOL(do_settimeofday); | 
|  | 259 | #endif /* !CONFIG_GENERIC_TIME */ | 
|  | 260 |  | 
|  | 261 | /* | 
|  | 262 | * Scheduler clock - returns current time in nanosec units. | 
|  | 263 | */ | 
|  | 264 | unsigned long long sched_clock(void) | 
|  | 265 | { | 
|  | 266 | return (unsigned long long)jiffies *(NSEC_PER_SEC / HZ); | 
|  | 267 | } |