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>
2009-10-07 17:09:06 +04:00
# include <linux/sched.h>
2005-06-24 09:01:16 +04:00
# include <linux/time.h>
2009-03-04 23:39:12 +03:00
# include <linux/clocksource.h>
2013-06-18 09:48:53 +04:00
# include <linux/clockchips.h>
2005-06-24 09:01:16 +04:00
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/irq.h>
# include <linux/profile.h>
# include <linux/delay.h>
2012-11-04 00:29:12 +04:00
# include <linux/irqdomain.h>
2013-06-17 12:29:46 +04:00
# include <linux/sched_clock.h>
2005-06-24 09:01:16 +04:00
# include <asm/timex.h>
# include <asm/platform.h>
2013-06-17 12:29:43 +04:00
unsigned long ccount_freq ; /* ccount Hz */
2014-01-19 20:00:48 +04:00
EXPORT_SYMBOL ( ccount_freq ) ;
2005-06-24 09:01:16 +04:00
2011-06-01 18:37:43 +04:00
static cycle_t ccount_read ( struct clocksource * cs )
2009-03-04 23:39:12 +03:00
{
return ( cycle_t ) get_ccount ( ) ;
}
2013-12-13 13:43:58 +04:00
static u64 notrace ccount_sched_clock_read ( void )
2013-06-17 12:29:46 +04:00
{
return get_ccount ( ) ;
}
2009-03-04 23:39:12 +03:00
static struct clocksource ccount_clocksource = {
. name = " ccount " ,
. rating = 200 ,
. read = ccount_read ,
. mask = CLOCKSOURCE_MASK ( 32 ) ,
2013-10-17 02:42:18 +04:00
. flags = CLOCK_SOURCE_IS_CONTINUOUS ,
2009-03-04 23:39:12 +03:00
} ;
2013-06-18 09:48:53 +04:00
static int ccount_timer_set_next_event ( unsigned long delta ,
struct clock_event_device * dev ) ;
static void ccount_timer_set_mode ( enum clock_event_mode mode ,
struct clock_event_device * evt ) ;
2013-10-17 02:42:19 +04:00
struct ccount_timer {
2013-06-18 09:48:53 +04:00
struct clock_event_device evt ;
int irq_enabled ;
2013-10-17 02:42:19 +04:00
char name [ 24 ] ;
2013-06-18 09:48:53 +04:00
} ;
2013-10-17 02:42:19 +04:00
static DEFINE_PER_CPU ( struct ccount_timer , ccount_timer ) ;
2013-06-18 09:48:53 +04:00
static int ccount_timer_set_next_event ( unsigned long delta ,
struct clock_event_device * dev )
{
unsigned long flags , next ;
int ret = 0 ;
local_irq_save ( flags ) ;
next = get_ccount ( ) + delta ;
set_linux_timer ( next ) ;
if ( next - get_ccount ( ) > delta )
ret = - ETIME ;
local_irq_restore ( flags ) ;
return ret ;
}
static void ccount_timer_set_mode ( enum clock_event_mode mode ,
struct clock_event_device * evt )
{
2013-10-17 02:42:19 +04:00
struct ccount_timer * timer =
container_of ( evt , struct ccount_timer , evt ) ;
2013-06-18 09:48:53 +04:00
/*
* There is no way to disable the timer interrupt at the device level ,
* only at the intenable register itself . Since enable_irq / disable_irq
* calls are nested , we need to make sure that these calls are
* balanced .
*/
switch ( mode ) {
case CLOCK_EVT_MODE_SHUTDOWN :
case CLOCK_EVT_MODE_UNUSED :
if ( timer - > irq_enabled ) {
disable_irq ( evt - > irq ) ;
timer - > irq_enabled = 0 ;
}
break ;
case CLOCK_EVT_MODE_RESUME :
case CLOCK_EVT_MODE_ONESHOT :
if ( ! timer - > irq_enabled ) {
enable_irq ( evt - > irq ) ;
timer - > irq_enabled = 1 ;
}
default :
break ;
}
}
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 ,
2013-06-18 09:48:53 +04:00
. flags = IRQF_TIMER ,
2005-06-24 09:01:16 +04:00
. name = " timer " ,
} ;
2013-10-17 02:42:19 +04:00
void local_timer_setup ( unsigned cpu )
{
struct ccount_timer * timer = & per_cpu ( ccount_timer , cpu ) ;
struct clock_event_device * clockevent = & timer - > evt ;
timer - > irq_enabled = 1 ;
clockevent - > name = timer - > name ;
snprintf ( timer - > name , sizeof ( timer - > name ) , " ccount_clockevent_%u " , cpu ) ;
clockevent - > features = CLOCK_EVT_FEAT_ONESHOT ;
clockevent - > rating = 300 ;
clockevent - > set_next_event = ccount_timer_set_next_event ;
clockevent - > set_mode = ccount_timer_set_mode ;
clockevent - > cpumask = cpumask_of ( cpu ) ;
clockevent - > irq = irq_create_mapping ( NULL , LINUX_TIMER_INT ) ;
if ( WARN ( ! clockevent - > irq , " error: can't map timer irq " ) )
return ;
clockevents_config_and_register ( clockevent , ccount_freq ,
0xf , 0xffffffff ) ;
}
2005-06-24 09:01:16 +04:00
void __init time_init ( void )
{
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 ( ) ;
2013-06-17 12:29:43 +04:00
printk ( " %d.%02d MHz \n " , ( int ) ccount_freq / 1000000 ,
( int ) ( ccount_freq / 10000 ) % 100 ) ;
2013-07-15 08:03:38 +04:00
# else
ccount_freq = CONFIG_XTENSA_CPU_CLOCK * 1000000UL ;
2005-06-24 09:01:16 +04:00
# endif
2013-07-15 09:24:22 +04:00
clocksource_register_hz ( & ccount_clocksource , ccount_freq ) ;
2013-10-17 02:42:19 +04:00
local_timer_setup ( 0 ) ;
setup_irq ( this_cpu_ptr ( & ccount_timer ) - > evt . irq , & timer_irqaction ) ;
2013-12-13 13:43:58 +04:00
sched_clock_register ( ccount_sched_clock_read , 32 , ccount_freq ) ;
2013-12-23 22:49:56 +04:00
clocksource_of_init ( ) ;
2005-06-24 09:01:16 +04:00
}
/*
* The timer interrupt is called HZ times per second .
*/
2013-10-17 02:42:19 +04:00
irqreturn_t timer_interrupt ( int irq , void * dev_id )
2005-06-24 09:01:16 +04:00
{
2013-10-17 02:42:19 +04:00
struct clock_event_device * evt = & this_cpu_ptr ( & ccount_timer ) - > evt ;
2005-06-24 09:01:16 +04:00
2013-10-17 02:42:24 +04:00
set_linux_timer ( get_linux_timer ( ) ) ;
2013-06-18 09:48:53 +04:00
evt - > event_handler ( evt ) ;
2005-06-24 09:01:16 +04:00
2007-08-05 21:26:30 +04:00
/* Allow platform to do something useful (Wdog). */
platform_heartbeat ( ) ;
2005-06-24 09:01:16 +04:00
return IRQ_HANDLED ;
}
# ifndef CONFIG_GENERIC_CALIBRATE_DELAY
2013-06-19 01:54:49 +04:00
void calibrate_delay ( void )
2005-06-24 09:01:16 +04:00
{
2013-07-15 09:24:22 +04:00
loops_per_jiffy = ccount_freq / HZ ;
2005-06-24 09:01:16 +04:00
printk ( " Calibrating delay loop (skipped)... "
" %lu.%02lu BogoMIPS preset \n " ,
loops_per_jiffy / ( 1000000 / HZ ) ,
( loops_per_jiffy / ( 10000 / HZ ) ) % 100 ) ;
}
# endif