2005-06-24 09:01:16 +04:00
/*
* arch / xtensa / kernel / time . c
*
* Timer and clock support .
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( C ) 2005 Tensilica Inc .
*
* Chris Zankel < chris @ zankel . net >
*/
# include <linux/errno.h>
# include <linux/time.h>
# include <linux/timex.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/irq.h>
# include <linux/profile.h>
# include <linux/delay.h>
# include <asm/timex.h>
# include <asm/platform.h>
2006-06-27 13:53:55 +04:00
DEFINE_SPINLOCK ( rtc_lock ) ;
2005-06-24 09:01:16 +04:00
EXPORT_SYMBOL ( rtc_lock ) ;
# ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT
unsigned long ccount_per_jiffy ; /* per 1/HZ */
2007-08-05 21:26:30 +04:00
unsigned long nsec_per_ccount ; /* nsec per ccount increment */
2005-06-24 09:01:16 +04:00
# endif
static long last_rtc_update = 0 ;
2007-08-05 21:26:30 +04:00
/*
* Scheduler clock - returns current tim in nanosec units .
*/
unsigned long long sched_clock ( void )
{
return ( unsigned long long ) jiffies * ( 1000000000 / HZ ) ;
}
2006-12-10 13:18:47 +03:00
static irqreturn_t timer_interrupt ( int irq , void * dev_id ) ;
2005-06-24 09:01:16 +04:00
static struct irqaction timer_irqaction = {
. handler = timer_interrupt ,
2006-07-02 06:29:31 +04:00
. flags = IRQF_DISABLED ,
2005-06-24 09:01:16 +04:00
. name = " timer " ,
} ;
void __init time_init ( void )
{
time_t sec_o , sec_n = 0 ;
/* The platform must provide a function to calibrate the processor
* speed for the CALIBRATE .
*/
2005-09-23 08:44:23 +04:00
# ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT
2005-06-24 09:01:16 +04:00
printk ( " Calibrating CPU frequency " ) ;
platform_calibrate_ccount ( ) ;
printk ( " %d.%02d MHz \n " , ( int ) ccount_per_jiffy / ( 1000000 / HZ ) ,
( int ) ( ccount_per_jiffy / ( 10000 / HZ ) ) % 100 ) ;
# endif
/* Set time from RTC (if provided) */
if ( platform_get_rtc_time ( & sec_o ) = = 0 )
while ( platform_get_rtc_time ( & sec_n ) )
if ( sec_o ! = sec_n )
break ;
xtime . tv_nsec = 0 ;
last_rtc_update = xtime . tv_sec = sec_n ;
set_normalized_timespec ( & wall_to_monotonic ,
- xtime . tv_sec , - xtime . tv_nsec ) ;
/* Initialize the linux timer interrupt. */
setup_irq ( LINUX_TIMER_INT , & timer_irqaction ) ;
set_linux_timer ( get_ccount ( ) + CCOUNT_PER_JIFFY ) ;
}
int do_settimeofday ( struct timespec * tv )
{
time_t wtm_sec , sec = tv - > tv_sec ;
long wtm_nsec , nsec = tv - > tv_nsec ;
2007-08-05 21:26:30 +04:00
unsigned long delta ;
2005-06-24 09:01:16 +04:00
if ( ( unsigned long ) tv - > tv_nsec > = NSEC_PER_SEC )
return - EINVAL ;
write_seqlock_irq ( & xtime_lock ) ;
/* This is revolting. We need to set "xtime" correctly. However, the
* value in this location is the value at the most recent update of
* wall time . Discover what correction gettimeofday ( ) would have
* made , and then undo it !
*/
2007-08-05 21:26:30 +04:00
delta = CCOUNT_PER_JIFFY ;
delta + = get_ccount ( ) - get_linux_timer ( ) ;
nsec - = delta * NSEC_PER_CCOUNT ;
2005-06-24 09:01:16 +04:00
wtm_sec = wall_to_monotonic . tv_sec + ( xtime . tv_sec - sec ) ;
wtm_nsec = wall_to_monotonic . tv_nsec + ( xtime . tv_nsec - nsec ) ;
set_normalized_timespec ( & xtime , sec , nsec ) ;
set_normalized_timespec ( & wall_to_monotonic , wtm_sec , wtm_nsec ) ;
2005-09-07 02:17:46 +04:00
ntp_clear ( ) ;
2005-06-24 09:01:16 +04:00
write_sequnlock_irq ( & xtime_lock ) ;
return 0 ;
}
EXPORT_SYMBOL ( do_settimeofday ) ;
void do_gettimeofday ( struct timeval * tv )
{
unsigned long flags ;
2007-08-05 21:26:30 +04:00
unsigned long volatile sec , usec , delta , seq ;
2005-06-24 09:01:16 +04:00
do {
seq = read_seqbegin_irqsave ( & xtime_lock , flags ) ;
sec = xtime . tv_sec ;
usec = ( xtime . tv_nsec / NSEC_PER_USEC ) ;
2007-08-05 21:26:30 +04:00
delta = get_linux_timer ( ) - get_ccount ( ) ;
2005-06-24 09:01:16 +04:00
} while ( read_seqretry_irqrestore ( & xtime_lock , seq , flags ) ) ;
2007-08-05 21:26:30 +04:00
usec + = ( ( ( unsigned long ) CCOUNT_PER_JIFFY - delta )
* ( unsigned long ) NSEC_PER_CCOUNT ) / NSEC_PER_USEC ;
2005-06-24 09:01:16 +04:00
for ( ; usec > = 1000000 ; sec + + , usec - = 1000000 )
;
tv - > tv_sec = sec ;
tv - > tv_usec = usec ;
}
EXPORT_SYMBOL ( do_gettimeofday ) ;
/*
* The timer interrupt is called HZ times per second .
*/
2006-12-10 13:18:47 +03:00
irqreturn_t timer_interrupt ( int irq , void * dev_id )
2005-06-24 09:01:16 +04:00
{
unsigned long next ;
next = get_linux_timer ( ) ;
again :
while ( ( signed long ) ( get_ccount ( ) - next ) > 0 ) {
2006-12-10 13:18:47 +03:00
profile_tick ( CPU_PROFILING ) ;
2005-06-24 09:01:16 +04:00
# ifndef CONFIG_SMP
2006-12-10 13:18:47 +03:00
update_process_times ( user_mode ( get_irq_regs ( ) ) ) ;
2005-06-24 09:01:16 +04:00
# endif
write_seqlock ( & xtime_lock ) ;
2007-08-05 21:26:30 +04:00
do_timer ( 1 ) ; /* Linux handler in kernel/timer.c */
/* Note that writing CCOMPARE clears the interrupt. */
2005-06-24 09:01:16 +04:00
next + = CCOUNT_PER_JIFFY ;
2007-08-05 21:26:30 +04:00
set_linux_timer ( next ) ;
2005-06-24 09:01:16 +04:00
2005-09-07 02:17:46 +04:00
if ( ntp_synced ( ) & &
2005-06-24 09:01:16 +04:00
xtime . tv_sec - last_rtc_update > = 659 & &
2006-10-01 10:28:31 +04:00
abs ( ( xtime . tv_nsec / 1000 ) - ( 1000000 - 1000000 / HZ ) ) < 5000000 / HZ ) {
2005-06-24 09:01:16 +04:00
if ( platform_set_rtc_time ( xtime . tv_sec + 1 ) = = 0 )
last_rtc_update = xtime . tv_sec + 1 ;
else
/* Do it again in 60 s */
last_rtc_update + = 60 ;
}
write_sequnlock ( & xtime_lock ) ;
}
2007-08-05 21:26:30 +04:00
/* Allow platform to do something useful (Wdog). */
2005-06-24 09:01:16 +04:00
2007-08-05 21:26:30 +04:00
platform_heartbeat ( ) ;
2005-06-24 09:01:16 +04:00
/* Make sure we didn't miss any tick... */
if ( ( signed long ) ( get_ccount ( ) - next ) > 0 )
goto again ;
return IRQ_HANDLED ;
}
# ifndef CONFIG_GENERIC_CALIBRATE_DELAY
2008-02-06 12:37:51 +03:00
void __cpuinit calibrate_delay ( void )
2005-06-24 09:01:16 +04:00
{
loops_per_jiffy = CCOUNT_PER_JIFFY ;
printk ( " Calibrating delay loop (skipped)... "
" %lu.%02lu BogoMIPS preset \n " ,
loops_per_jiffy / ( 1000000 / HZ ) ,
( loops_per_jiffy / ( 10000 / HZ ) ) % 100 ) ;
}
# endif