2009-01-20 14:15:18 +08:00
/*
* linux / arch / arm / mach - mmp / time . c
*
* Support for clocksource and clockevents
*
* Copyright ( C ) 2008 Marvell International Ltd .
* All rights reserved .
*
* 2008 - 04 - 11 : Jason Chagas < Jason . chagas @ marvell . com >
* 2008 - 10 - 08 : Bin Yang < bin . yang @ marvell . com >
*
* The timers module actually includes three timers , each timer with upto
* three match comparators . Timer # 0 is used here in free - running mode as
* the clock source , and match comparator # 1 used as clock event device .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/clockchips.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/sched.h>
# include <linux/cnt32_to_63.h>
# include <mach/addr-map.h>
# include <mach/regs-timers.h>
# include <mach/irqs.h>
# include "clock.h"
# define TIMERS_VIRT_BASE TIMERS1_VIRT_BASE
# define MAX_DELTA (0xfffffffe)
# define MIN_DELTA (16)
# define TCR2NS_SCALE_FACTOR 10
static unsigned long tcr2ns_scale ;
static void __init set_tcr2ns_scale ( unsigned long tcr_rate )
{
unsigned long long v = 1000000000ULL < < TCR2NS_SCALE_FACTOR ;
do_div ( v , tcr_rate ) ;
tcr2ns_scale = v ;
/*
* We want an even value to automatically clear the top bit
* returned by cnt32_to_63 ( ) without an additional run time
* instruction . So if the LSB is 1 then round it up .
*/
if ( tcr2ns_scale & 1 )
tcr2ns_scale + + ;
}
/*
* FIXME : the timer needs some delay to stablize the counter capture
*/
static inline uint32_t timer_read ( void )
{
int delay = 100 ;
__raw_writel ( 1 , TIMERS_VIRT_BASE + TMR_CVWR ( 0 ) ) ;
while ( delay - - )
cpu_relax ( ) ;
return __raw_readl ( TIMERS_VIRT_BASE + TMR_CVWR ( 0 ) ) ;
}
unsigned long long sched_clock ( void )
{
unsigned long long v = cnt32_to_63 ( timer_read ( ) ) ;
return ( v * tcr2ns_scale ) > > TCR2NS_SCALE_FACTOR ;
}
static irqreturn_t timer_interrupt ( int irq , void * dev_id )
{
struct clock_event_device * c = dev_id ;
/* disable and clear pending interrupt status */
__raw_writel ( 0x0 , TIMERS_VIRT_BASE + TMR_IER ( 0 ) ) ;
__raw_writel ( 0x1 , TIMERS_VIRT_BASE + TMR_ICR ( 0 ) ) ;
c - > event_handler ( c ) ;
return IRQ_HANDLED ;
}
static int timer_set_next_event ( unsigned long delta ,
struct clock_event_device * dev )
{
unsigned long flags , next ;
local_irq_save ( flags ) ;
/* clear pending interrupt status and enable */
__raw_writel ( 0x01 , TIMERS_VIRT_BASE + TMR_ICR ( 0 ) ) ;
__raw_writel ( 0x01 , TIMERS_VIRT_BASE + TMR_IER ( 0 ) ) ;
next = timer_read ( ) + delta ;
__raw_writel ( next , TIMERS_VIRT_BASE + TMR_TN_MM ( 0 , 0 ) ) ;
local_irq_restore ( flags ) ;
return 0 ;
}
static void timer_set_mode ( enum clock_event_mode mode ,
struct clock_event_device * dev )
{
unsigned long flags ;
local_irq_save ( flags ) ;
switch ( mode ) {
case CLOCK_EVT_MODE_ONESHOT :
case CLOCK_EVT_MODE_UNUSED :
case CLOCK_EVT_MODE_SHUTDOWN :
/* disable the matching interrupt */
__raw_writel ( 0x00 , TIMERS_VIRT_BASE + TMR_IER ( 0 ) ) ;
break ;
case CLOCK_EVT_MODE_RESUME :
case CLOCK_EVT_MODE_PERIODIC :
break ;
}
local_irq_restore ( flags ) ;
}
static struct clock_event_device ckevt = {
. name = " clockevent " ,
. features = CLOCK_EVT_FEAT_ONESHOT ,
. shift = 32 ,
. rating = 200 ,
. set_next_event = timer_set_next_event ,
. set_mode = timer_set_mode ,
} ;
2009-04-23 03:04:45 +08:00
static cycle_t clksrc_read ( struct clocksource * cs )
2009-01-20 14:15:18 +08:00
{
return timer_read ( ) ;
}
static struct clocksource cksrc = {
. name = " clocksource " ,
. shift = 20 ,
. rating = 200 ,
. read = clksrc_read ,
. mask = CLOCKSOURCE_MASK ( 32 ) ,
. flags = CLOCK_SOURCE_IS_CONTINUOUS ,
} ;
static void __init timer_config ( void )
{
uint32_t ccr = __raw_readl ( TIMERS_VIRT_BASE + TMR_CCR ) ;
uint32_t cer = __raw_readl ( TIMERS_VIRT_BASE + TMR_CER ) ;
uint32_t cmr = __raw_readl ( TIMERS_VIRT_BASE + TMR_CMR ) ;
__raw_writel ( cer & ~ 0x1 , TIMERS_VIRT_BASE + TMR_CER ) ; /* disable */
ccr & = TMR_CCR_CS_0 ( 0x3 ) ;
__raw_writel ( ccr , TIMERS_VIRT_BASE + TMR_CCR ) ;
/* free-running mode */
__raw_writel ( cmr | 0x01 , TIMERS_VIRT_BASE + TMR_CMR ) ;
__raw_writel ( 0x0 , TIMERS_VIRT_BASE + TMR_PLCR ( 0 ) ) ; /* free-running */
__raw_writel ( 0x7 , TIMERS_VIRT_BASE + TMR_ICR ( 0 ) ) ; /* clear status */
__raw_writel ( 0x0 , TIMERS_VIRT_BASE + TMR_IER ( 0 ) ) ;
/* enable timer counter */
__raw_writel ( cer | 0x01 , TIMERS_VIRT_BASE + TMR_CER ) ;
}
static struct irqaction timer_irq = {
. name = " timer " ,
. flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL ,
. handler = timer_interrupt ,
. dev_id = & ckevt ,
} ;
void __init timer_init ( int irq )
{
timer_config ( ) ;
set_tcr2ns_scale ( CLOCK_TICK_RATE ) ;
ckevt . mult = div_sc ( CLOCK_TICK_RATE , NSEC_PER_SEC , ckevt . shift ) ;
ckevt . max_delta_ns = clockevent_delta2ns ( MAX_DELTA , & ckevt ) ;
ckevt . min_delta_ns = clockevent_delta2ns ( MIN_DELTA , & ckevt ) ;
ckevt . cpumask = cpumask_of ( 0 ) ;
cksrc . mult = clocksource_hz2mult ( CLOCK_TICK_RATE , cksrc . shift ) ;
setup_irq ( irq , & timer_irq ) ;
clocksource_register ( & cksrc ) ;
clockevents_register_device ( & ckevt ) ;
}