2009-01-20 09:15:18 +03: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 >
*
2011-03-31 05:57:33 +04:00
* The timers module actually includes three timers , each timer with up to
2009-01-20 09:15:18 +03:00
* 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>
2010-12-16 00:46:48 +03:00
# include <asm/sched_clock.h>
2009-01-20 09:15:18 +03:00
# include <mach/addr-map.h>
# include <mach/regs-timers.h>
2009-12-04 17:41:28 +03:00
# include <mach/regs-apbc.h>
2009-01-20 09:15:18 +03:00
# include <mach/irqs.h>
2009-12-04 17:41:28 +03:00
# include <mach/cputype.h>
# include <asm/mach/time.h>
2009-01-20 09:15:18 +03:00
# include "clock.h"
# define TIMERS_VIRT_BASE TIMERS1_VIRT_BASE
# define MAX_DELTA (0xfffffffe)
# define MIN_DELTA (16)
2010-12-16 00:46:48 +03:00
static DEFINE_CLOCK_DATA ( cd ) ;
2009-01-20 09:15:18 +03:00
/*
* 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 ) ) ;
}
2010-12-15 22:19:25 +03:00
unsigned long long notrace sched_clock ( void )
2009-01-20 09:15:18 +03:00
{
2010-12-16 00:46:48 +03:00
u32 cyc = timer_read ( ) ;
return cyc_to_sched_clock ( & cd , cyc , ( u32 ) ~ 0 ) ;
}
static void notrace mmp_update_sched_clock ( void )
{
u32 cyc = timer_read ( ) ;
update_sched_clock ( & cd , cyc , ( u32 ) ~ 0 ) ;
2009-01-20 09:15:18 +03:00
}
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-22 23:04:45 +04:00
static cycle_t clksrc_read ( struct clocksource * cs )
2009-01-20 09:15:18 +03:00
{
return timer_read ( ) ;
}
static struct clocksource cksrc = {
. name = " clocksource " ,
. 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 */
2009-12-04 17:41:28 +03:00
ccr & = ( cpu_is_mmp2 ( ) ) ? TMR_CCR_CS_0 ( 0 ) : TMR_CCR_CS_0 ( 3 ) ;
2009-01-20 09:15:18 +03:00
__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 ( ) ;
2010-12-16 00:46:48 +03:00
init_sched_clock ( & cd , mmp_update_sched_clock , 32 , CLOCK_TICK_RATE ) ;
2009-01-20 09:15:18 +03:00
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 ) ;
setup_irq ( irq , & timer_irq ) ;
2010-12-13 16:18:04 +03:00
clocksource_register_hz ( & cksrc , CLOCK_TICK_RATE ) ;
2009-01-20 09:15:18 +03:00
clockevents_register_device ( & ckevt ) ;
}