2007-11-26 15:11:58 +03:00
/* linux/arch/arm/mach-msm/timer.c
*
* Copyright ( C ) 2007 Google , Inc .
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/init.h>
# include <linux/time.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/clk.h>
# include <linux/clockchips.h>
# include <linux/delay.h>
2008-09-06 15:10:45 +04:00
# include <linux/io.h>
2007-11-26 15:11:58 +03:00
# include <asm/mach/time.h>
2008-08-05 19:14:15 +04:00
# include <mach/msm_iomap.h>
2011-01-07 21:20:49 +03:00
# include <mach/cpu.h>
2007-11-26 15:11:58 +03:00
# define TIMER_MATCH_VAL 0x0000
# define TIMER_COUNT_VAL 0x0004
# define TIMER_ENABLE 0x0008
# define TIMER_ENABLE_CLR_ON_MATCH_EN 2
# define TIMER_ENABLE_EN 1
# define TIMER_CLEAR 0x000C
2010-10-06 02:23:57 +04:00
# define DGT_CLK_CTL 0x0034
enum {
DGT_CLK_CTL_DIV_1 = 0 ,
DGT_CLK_CTL_DIV_2 = 1 ,
DGT_CLK_CTL_DIV_3 = 2 ,
DGT_CLK_CTL_DIV_4 = 3 ,
} ;
2007-11-26 15:11:58 +03:00
# define CSR_PROTECTION 0x0020
# define CSR_PROTECTION_EN 1
# define GPT_HZ 32768
2010-10-06 02:23:57 +04:00
2010-12-02 23:05:12 +03:00
enum timer_location {
LOCAL_TIMER = 0 ,
GLOBAL_TIMER = 1 ,
} ;
# define MSM_GLOBAL_TIMER MSM_CLOCK_DGT
2011-01-07 21:20:49 +03:00
/* TODO: Remove these ifdefs */
2010-10-06 02:23:57 +04:00
# if defined(CONFIG_ARCH_QSD8X50)
# define DGT_HZ (19200000 / 4) /* 19.2 MHz / 4 by default */
# define MSM_DGT_SHIFT (0)
# elif defined(CONFIG_ARCH_MSM7X30) || defined(CONFIG_ARCH_MSM8X60)
# define DGT_HZ (24576000 / 4) /* 24.576 MHz (LPXO) / 4 by default */
# define MSM_DGT_SHIFT (0)
# else
2007-11-26 15:11:58 +03:00
# define DGT_HZ 19200000 /* 19.2 MHz or 600 KHz after shift */
2010-10-06 02:23:57 +04:00
# define MSM_DGT_SHIFT (5)
# endif
2007-11-26 15:11:58 +03:00
struct msm_clock {
struct clock_event_device clockevent ;
struct clocksource clocksource ;
struct irqaction irq ;
2008-09-11 01:00:53 +04:00
void __iomem * regbase ;
2007-11-26 15:11:58 +03:00
uint32_t freq ;
uint32_t shift ;
2010-12-02 23:05:12 +03:00
void __iomem * global_counter ;
void __iomem * local_counter ;
} ;
enum {
MSM_CLOCK_GPT ,
MSM_CLOCK_DGT ,
NR_TIMERS ,
2007-11-26 15:11:58 +03:00
} ;
2010-12-02 23:05:12 +03:00
static struct msm_clock msm_clocks [ ] ;
static struct clock_event_device * local_clock_event ;
2007-11-26 15:11:58 +03:00
static irqreturn_t msm_timer_interrupt ( int irq , void * dev_id )
{
struct clock_event_device * evt = dev_id ;
2010-12-02 23:05:12 +03:00
if ( smp_processor_id ( ) ! = 0 )
evt = local_clock_event ;
if ( evt - > event_handler = = NULL )
return IRQ_HANDLED ;
2007-11-26 15:11:58 +03:00
evt - > event_handler ( evt ) ;
return IRQ_HANDLED ;
}
2010-12-02 23:05:12 +03:00
static cycle_t msm_read_timer_count ( struct clocksource * cs )
2007-11-26 15:11:58 +03:00
{
2010-12-02 23:05:12 +03:00
struct msm_clock * clk = container_of ( cs , struct msm_clock , clocksource ) ;
return readl ( clk - > global_counter ) ;
2007-11-26 15:11:58 +03:00
}
2010-12-02 23:05:12 +03:00
static struct msm_clock * clockevent_to_clock ( struct clock_event_device * evt )
2007-11-26 15:11:58 +03:00
{
2010-12-02 23:05:12 +03:00
# ifdef CONFIG_SMP
int i ;
for ( i = 0 ; i < NR_TIMERS ; i + + )
if ( evt = = & ( msm_clocks [ i ] . clockevent ) )
return & msm_clocks [ i ] ;
return & msm_clocks [ MSM_GLOBAL_TIMER ] ;
# else
return container_of ( evt , struct msm_clock , clockevent ) ;
# endif
2007-11-26 15:11:58 +03:00
}
static int msm_timer_set_next_event ( unsigned long cycles ,
struct clock_event_device * evt )
{
2010-12-02 23:05:12 +03:00
struct msm_clock * clock = clockevent_to_clock ( evt ) ;
uint32_t now = readl ( clock - > local_counter ) ;
2007-11-26 15:11:58 +03:00
uint32_t alarm = now + ( cycles < < clock - > shift ) ;
writel ( alarm , clock - > regbase + TIMER_MATCH_VAL ) ;
return 0 ;
}
static void msm_timer_set_mode ( enum clock_event_mode mode ,
struct clock_event_device * evt )
{
2010-12-02 23:05:12 +03:00
struct msm_clock * clock = clockevent_to_clock ( evt ) ;
2007-11-26 15:11:58 +03:00
switch ( mode ) {
case CLOCK_EVT_MODE_RESUME :
case CLOCK_EVT_MODE_PERIODIC :
break ;
case CLOCK_EVT_MODE_ONESHOT :
writel ( TIMER_ENABLE_EN , clock - > regbase + TIMER_ENABLE ) ;
break ;
case CLOCK_EVT_MODE_UNUSED :
case CLOCK_EVT_MODE_SHUTDOWN :
writel ( 0 , clock - > regbase + TIMER_ENABLE ) ;
break ;
}
}
static struct msm_clock msm_clocks [ ] = {
2010-12-02 23:05:12 +03:00
[ MSM_CLOCK_GPT ] = {
2007-11-26 15:11:58 +03:00
. clockevent = {
. name = " gp_timer " ,
. features = CLOCK_EVT_FEAT_ONESHOT ,
. shift = 32 ,
. rating = 200 ,
. set_next_event = msm_timer_set_next_event ,
. set_mode = msm_timer_set_mode ,
} ,
. clocksource = {
. name = " gp_timer " ,
. rating = 200 ,
2010-12-02 23:05:12 +03:00
. read = msm_read_timer_count ,
2007-11-26 15:11:58 +03:00
. mask = CLOCKSOURCE_MASK ( 32 ) ,
. flags = CLOCK_SOURCE_IS_CONTINUOUS ,
} ,
. irq = {
. name = " gp_timer " ,
. flags = IRQF_DISABLED | IRQF_TIMER | IRQF_TRIGGER_RISING ,
. handler = msm_timer_interrupt ,
. dev_id = & msm_clocks [ 0 ] . clockevent ,
. irq = INT_GP_TIMER_EXP
} ,
2010-12-02 23:05:12 +03:00
. freq = GPT_HZ ,
2007-11-26 15:11:58 +03:00
} ,
2010-12-02 23:05:12 +03:00
[ MSM_CLOCK_DGT ] = {
2007-11-26 15:11:58 +03:00
. clockevent = {
. name = " dg_timer " ,
. features = CLOCK_EVT_FEAT_ONESHOT ,
. shift = 32 + MSM_DGT_SHIFT ,
. rating = 300 ,
. set_next_event = msm_timer_set_next_event ,
. set_mode = msm_timer_set_mode ,
} ,
. clocksource = {
. name = " dg_timer " ,
. rating = 300 ,
2010-12-02 23:05:12 +03:00
. read = msm_read_timer_count ,
2007-11-26 15:11:58 +03:00
. mask = CLOCKSOURCE_MASK ( ( 32 - MSM_DGT_SHIFT ) ) ,
. flags = CLOCK_SOURCE_IS_CONTINUOUS ,
} ,
. irq = {
. name = " dg_timer " ,
. flags = IRQF_DISABLED | IRQF_TIMER | IRQF_TRIGGER_RISING ,
. handler = msm_timer_interrupt ,
. dev_id = & msm_clocks [ 1 ] . clockevent ,
. irq = INT_DEBUG_TIMER_EXP
} ,
. freq = DGT_HZ > > MSM_DGT_SHIFT ,
2010-12-02 23:05:12 +03:00
. shift = MSM_DGT_SHIFT ,
2007-11-26 15:11:58 +03:00
}
} ;
static void __init msm_timer_init ( void )
{
int i ;
int res ;
2011-01-07 21:20:49 +03:00
int global_offset = 0 ;
if ( cpu_is_msm7x01 ( ) ) {
msm_clocks [ MSM_CLOCK_GPT ] . regbase = MSM_CSR_BASE ;
msm_clocks [ MSM_CLOCK_DGT ] . regbase = MSM_CSR_BASE + 0x10 ;
} else if ( cpu_is_msm7x30 ( ) ) {
msm_clocks [ MSM_CLOCK_GPT ] . regbase = MSM_CSR_BASE + 0x04 ;
msm_clocks [ MSM_CLOCK_DGT ] . regbase = MSM_CSR_BASE + 0x24 ;
} else if ( cpu_is_qsd8x50 ( ) ) {
msm_clocks [ MSM_CLOCK_GPT ] . regbase = MSM_CSR_BASE ;
msm_clocks [ MSM_CLOCK_DGT ] . regbase = MSM_CSR_BASE + 0x10 ;
} else if ( cpu_is_msm8x60 ( ) ) {
msm_clocks [ MSM_CLOCK_GPT ] . regbase = MSM_TMR_BASE + 0x04 ;
msm_clocks [ MSM_CLOCK_DGT ] . regbase = MSM_TMR_BASE + 0x24 ;
/* Use CPU0's timer as the global timer. */
global_offset = MSM_TMR0_BASE - MSM_TMR_BASE ;
} else
BUG ( ) ;
2007-11-26 15:11:58 +03:00
2010-12-02 23:05:12 +03:00
# ifdef CONFIG_ARCH_MSM_SCORPIONMP
2010-10-06 02:23:57 +04:00
writel ( DGT_CLK_CTL_DIV_4 , MSM_TMR_BASE + DGT_CLK_CTL ) ;
# endif
2007-11-26 15:11:58 +03:00
for ( i = 0 ; i < ARRAY_SIZE ( msm_clocks ) ; i + + ) {
struct msm_clock * clock = & msm_clocks [ i ] ;
struct clock_event_device * ce = & clock - > clockevent ;
struct clocksource * cs = & clock - > clocksource ;
2011-01-07 21:20:49 +03:00
clock - > local_counter = clock - > regbase + TIMER_COUNT_VAL ;
clock - > global_counter = clock - > local_counter + global_offset ;
2007-11-26 15:11:58 +03:00
writel ( 0 , clock - > regbase + TIMER_ENABLE ) ;
writel ( 0 , clock - > regbase + TIMER_CLEAR ) ;
writel ( ~ 0 , clock - > regbase + TIMER_MATCH_VAL ) ;
ce - > mult = div_sc ( clock - > freq , NSEC_PER_SEC , ce - > shift ) ;
/* allow at least 10 seconds to notice that the timer wrapped */
ce - > max_delta_ns =
clockevent_delta2ns ( 0xf0000000 > > clock - > shift , ce ) ;
/* 4 gets rounded down to 3 */
ce - > min_delta_ns = clockevent_delta2ns ( 4 , ce ) ;
2008-12-13 13:50:26 +03:00
ce - > cpumask = cpumask_of ( 0 ) ;
2007-11-26 15:11:58 +03:00
2010-12-13 16:18:12 +03:00
res = clocksource_register_hz ( cs , clock - > freq ) ;
2007-11-26 15:11:58 +03:00
if ( res )
printk ( KERN_ERR " msm_timer_init: clocksource_register "
" failed for %s \n " , cs - > name ) ;
res = setup_irq ( clock - > irq . irq , & clock - > irq ) ;
if ( res )
printk ( KERN_ERR " msm_timer_init: setup_irq "
" failed for %s \n " , cs - > name ) ;
clockevents_register_device ( ce ) ;
}
}
2010-12-02 23:05:12 +03:00
# ifdef CONFIG_SMP
void __cpuinit local_timer_setup ( struct clock_event_device * evt )
{
struct msm_clock * clock = & msm_clocks [ MSM_GLOBAL_TIMER ] ;
/* Use existing clock_event for cpu 0 */
if ( ! smp_processor_id ( ) )
return ;
writel ( DGT_CLK_CTL_DIV_4 , MSM_TMR_BASE + DGT_CLK_CTL ) ;
if ( ! local_clock_event ) {
writel ( 0 , clock - > regbase + TIMER_ENABLE ) ;
writel ( 0 , clock - > regbase + TIMER_CLEAR ) ;
writel ( ~ 0 , clock - > regbase + TIMER_MATCH_VAL ) ;
}
evt - > irq = clock - > irq . irq ;
evt - > name = " local_timer " ;
evt - > features = CLOCK_EVT_FEAT_ONESHOT ;
evt - > rating = clock - > clockevent . rating ;
evt - > set_mode = msm_timer_set_mode ;
evt - > set_next_event = msm_timer_set_next_event ;
evt - > shift = clock - > clockevent . shift ;
evt - > mult = div_sc ( clock - > freq , NSEC_PER_SEC , evt - > shift ) ;
evt - > max_delta_ns =
clockevent_delta2ns ( 0xf0000000 > > clock - > shift , evt ) ;
evt - > min_delta_ns = clockevent_delta2ns ( 4 , evt ) ;
local_clock_event = evt ;
gic_enable_ppi ( clock - > irq . irq ) ;
clockevents_register_device ( evt ) ;
}
inline int local_timer_ack ( void )
{
return 1 ;
}
# endif
2007-11-26 15:11:58 +03:00
struct sys_timer msm_timer = {
. init = msm_timer_init
} ;