2012-06-13 20:58:09 +04:00
/*
* Marvell Armada 370 / XP SoC timer handling .
*
* Copyright ( C ) 2012 Marvell
*
* Lior Amsalem < alior @ marvell . com >
* Gregory CLEMENT < gregory . clement @ free - electrons . com >
* Thomas Petazzoni < thomas . petazzoni @ free - electrons . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*
* Timer 0 is used as free - running clocksource , while timer 1 is
* used as clock_event_device .
*/
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/kernel.h>
2012-11-17 18:22:25 +04:00
# include <linux/clk.h>
2012-06-13 20:58:09 +04:00
# include <linux/timer.h>
# include <linux/clockchips.h>
# include <linux/interrupt.h>
# include <linux/of.h>
# include <linux/of_irq.h>
# include <linux/of_address.h>
# include <linux/irq.h>
# include <linux/module.h>
2013-06-02 10:39:40 +04:00
# include <linux/sched_clock.h>
2012-06-13 20:58:09 +04:00
2013-01-25 21:32:42 +04:00
# include <asm/localtimer.h>
# include <linux/percpu.h>
2012-06-13 20:58:09 +04:00
/*
* Timer block registers .
*/
# define TIMER_CTRL_OFF 0x0000
# define TIMER0_EN 0x0001
# define TIMER0_RELOAD_EN 0x0002
# define TIMER0_25MHZ 0x0800
# define TIMER0_DIV(div) ((div) << 19)
# define TIMER1_EN 0x0004
# define TIMER1_RELOAD_EN 0x0008
# define TIMER1_25MHZ 0x1000
# define TIMER1_DIV(div) ((div) << 22)
# define TIMER_EVENTS_STATUS 0x0004
# define TIMER0_CLR_MASK (~0x1)
# define TIMER1_CLR_MASK (~0x100)
# define TIMER0_RELOAD_OFF 0x0010
# define TIMER0_VAL_OFF 0x0014
# define TIMER1_RELOAD_OFF 0x0018
# define TIMER1_VAL_OFF 0x001c
2013-01-25 21:32:42 +04:00
# define LCL_TIMER_EVENTS_STATUS 0x0028
2012-06-13 20:58:09 +04:00
/* Global timers are connected to the coherency fabric clock, and the
below divider reduces their incrementing frequency . */
# define TIMER_DIVIDER_SHIFT 5
# define TIMER_DIVIDER (1 << TIMER_DIVIDER_SHIFT)
/*
* SoC - specific data .
*/
2013-01-25 21:32:42 +04:00
static void __iomem * timer_base , * local_base ;
static unsigned int timer_clk ;
static bool timer25Mhz = true ;
2012-06-13 20:58:09 +04:00
/*
* Number of timer ticks per jiffy .
*/
static u32 ticks_per_jiffy ;
2013-01-25 21:32:42 +04:00
static struct clock_event_device __percpu * * percpu_armada_370_xp_evt ;
2012-06-13 20:58:09 +04:00
static u32 notrace armada_370_xp_read_sched_clock ( void )
{
return ~ readl ( timer_base + TIMER0_VAL_OFF ) ;
}
/*
* Clockevent handling .
*/
static int
armada_370_xp_clkevt_next_event ( unsigned long delta ,
struct clock_event_device * dev )
{
u32 u ;
/*
* Clear clockevent timer interrupt .
*/
2013-01-25 21:32:42 +04:00
writel ( TIMER0_CLR_MASK , local_base + LCL_TIMER_EVENTS_STATUS ) ;
2012-06-13 20:58:09 +04:00
/*
* Setup new clockevent timer value .
*/
2013-01-25 21:32:42 +04:00
writel ( delta , local_base + TIMER0_VAL_OFF ) ;
2012-06-13 20:58:09 +04:00
/*
* Enable the timer .
*/
2013-01-25 21:32:42 +04:00
u = readl ( local_base + TIMER_CTRL_OFF ) ;
u = ( ( u & ~ TIMER0_RELOAD_EN ) | TIMER0_EN |
TIMER0_DIV ( TIMER_DIVIDER_SHIFT ) ) ;
writel ( u , local_base + TIMER_CTRL_OFF ) ;
2012-06-13 20:58:09 +04:00
return 0 ;
}
static void
armada_370_xp_clkevt_mode ( enum clock_event_mode mode ,
struct clock_event_device * dev )
{
u32 u ;
if ( mode = = CLOCK_EVT_MODE_PERIODIC ) {
2013-01-25 21:32:42 +04:00
2012-06-13 20:58:09 +04:00
/*
* Setup timer to fire at 1 / HZ intervals .
*/
2013-01-25 21:32:42 +04:00
writel ( ticks_per_jiffy - 1 , local_base + TIMER0_RELOAD_OFF ) ;
writel ( ticks_per_jiffy - 1 , local_base + TIMER0_VAL_OFF ) ;
2012-06-13 20:58:09 +04:00
/*
* Enable timer .
*/
2013-01-25 21:32:42 +04:00
u = readl ( local_base + TIMER_CTRL_OFF ) ;
writel ( ( u | TIMER0_EN | TIMER0_RELOAD_EN |
TIMER0_DIV ( TIMER_DIVIDER_SHIFT ) ) ,
local_base + TIMER_CTRL_OFF ) ;
2012-06-13 20:58:09 +04:00
} else {
/*
* Disable timer .
*/
2013-01-25 21:32:42 +04:00
u = readl ( local_base + TIMER_CTRL_OFF ) ;
writel ( u & ~ TIMER0_EN , local_base + TIMER_CTRL_OFF ) ;
2012-06-13 20:58:09 +04:00
/*
* ACK pending timer interrupt .
*/
2013-01-25 21:32:42 +04:00
writel ( TIMER0_CLR_MASK , local_base + LCL_TIMER_EVENTS_STATUS ) ;
2012-06-13 20:58:09 +04:00
}
}
static struct clock_event_device armada_370_xp_clkevt = {
2013-01-25 21:32:42 +04:00
. name = " armada_370_xp_per_cpu_tick " ,
2012-06-13 20:58:09 +04:00
. features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC ,
. shift = 32 ,
. rating = 300 ,
. set_next_event = armada_370_xp_clkevt_next_event ,
. set_mode = armada_370_xp_clkevt_mode ,
} ;
static irqreturn_t armada_370_xp_timer_interrupt ( int irq , void * dev_id )
{
/*
* ACK timer interrupt and call event handler .
*/
2013-01-25 21:32:42 +04:00
struct clock_event_device * evt = * ( struct clock_event_device * * ) dev_id ;
2012-06-13 20:58:09 +04:00
2013-01-25 21:32:42 +04:00
writel ( TIMER0_CLR_MASK , local_base + LCL_TIMER_EVENTS_STATUS ) ;
evt - > event_handler ( evt ) ;
2012-06-13 20:58:09 +04:00
return IRQ_HANDLED ;
}
2013-01-25 21:32:42 +04:00
/*
* Setup the local clock events for a CPU .
*/
static int __cpuinit armada_370_xp_timer_setup ( struct clock_event_device * evt )
{
u32 u ;
int cpu = smp_processor_id ( ) ;
/* Use existing clock_event for cpu 0 */
if ( ! smp_processor_id ( ) )
return 0 ;
u = readl ( local_base + TIMER_CTRL_OFF ) ;
if ( timer25Mhz )
writel ( u | TIMER0_25MHZ , local_base + TIMER_CTRL_OFF ) ;
else
writel ( u & ~ TIMER0_25MHZ , local_base + TIMER_CTRL_OFF ) ;
evt - > name = armada_370_xp_clkevt . name ;
evt - > irq = armada_370_xp_clkevt . irq ;
evt - > features = armada_370_xp_clkevt . features ;
evt - > shift = armada_370_xp_clkevt . shift ;
evt - > rating = armada_370_xp_clkevt . rating ,
evt - > set_next_event = armada_370_xp_clkevt_next_event ,
evt - > set_mode = armada_370_xp_clkevt_mode ,
evt - > cpumask = cpumask_of ( cpu ) ;
* __this_cpu_ptr ( percpu_armada_370_xp_evt ) = evt ;
clockevents_config_and_register ( evt , timer_clk , 1 , 0xfffffffe ) ;
enable_percpu_irq ( evt - > irq , 0 ) ;
return 0 ;
}
static void armada_370_xp_timer_stop ( struct clock_event_device * evt )
{
evt - > set_mode ( CLOCK_EVT_MODE_UNUSED , evt ) ;
disable_percpu_irq ( evt - > irq ) ;
}
static struct local_timer_ops armada_370_xp_local_timer_ops __cpuinitdata = {
. setup = armada_370_xp_timer_setup ,
. stop = armada_370_xp_timer_stop ,
2012-06-13 20:58:09 +04:00
} ;
void __init armada_370_xp_timer_init ( void )
{
u32 u ;
struct device_node * np ;
2013-01-25 21:32:42 +04:00
int res ;
2012-06-13 20:58:09 +04:00
np = of_find_compatible_node ( NULL , NULL , " marvell,armada-370-xp-timer " ) ;
timer_base = of_iomap ( np , 0 ) ;
WARN_ON ( ! timer_base ) ;
2013-01-25 21:32:42 +04:00
local_base = of_iomap ( np , 1 ) ;
2012-06-13 20:58:09 +04:00
if ( of_find_property ( np , " marvell,timer-25Mhz " , NULL ) ) {
/* The fixed 25MHz timer is available so let's use it */
2013-01-25 21:32:42 +04:00
u = readl ( local_base + TIMER_CTRL_OFF ) ;
writel ( u | TIMER0_25MHZ ,
local_base + TIMER_CTRL_OFF ) ;
2012-06-13 20:58:09 +04:00
u = readl ( timer_base + TIMER_CTRL_OFF ) ;
2013-01-25 21:32:42 +04:00
writel ( u | TIMER0_25MHZ ,
2012-06-13 20:58:09 +04:00
timer_base + TIMER_CTRL_OFF ) ;
timer_clk = 25000000 ;
} else {
2012-11-17 18:22:25 +04:00
unsigned long rate = 0 ;
struct clk * clk = of_clk_get ( np , 0 ) ;
WARN_ON ( IS_ERR ( clk ) ) ;
rate = clk_get_rate ( clk ) ;
2013-01-25 21:32:42 +04:00
u = readl ( local_base + TIMER_CTRL_OFF ) ;
writel ( u & ~ ( TIMER0_25MHZ ) ,
local_base + TIMER_CTRL_OFF ) ;
2012-06-13 20:58:09 +04:00
u = readl ( timer_base + TIMER_CTRL_OFF ) ;
2013-01-25 21:32:42 +04:00
writel ( u & ~ ( TIMER0_25MHZ ) ,
2012-06-13 20:58:09 +04:00
timer_base + TIMER_CTRL_OFF ) ;
2013-01-25 21:32:42 +04:00
2012-11-17 18:22:25 +04:00
timer_clk = rate / TIMER_DIVIDER ;
2013-01-25 21:32:42 +04:00
timer25Mhz = false ;
2012-06-13 20:58:09 +04:00
}
2013-01-25 21:32:42 +04:00
/*
* We use timer 0 as clocksource , and private ( local ) timer 0
* for clockevents
*/
armada_370_xp_clkevt . irq = irq_of_parse_and_map ( np , 4 ) ;
2012-06-13 20:58:09 +04:00
ticks_per_jiffy = ( timer_clk + HZ / 2 ) / HZ ;
/*
* Set scale and timer for sched_clock .
*/
setup_sched_clock ( armada_370_xp_read_sched_clock , 32 , timer_clk ) ;
/*
* Setup free - running clocksource timer ( interrupts
* disabled ) .
*/
writel ( 0xffffffff , timer_base + TIMER0_VAL_OFF ) ;
writel ( 0xffffffff , timer_base + TIMER0_RELOAD_OFF ) ;
u = readl ( timer_base + TIMER_CTRL_OFF ) ;
writel ( ( u | TIMER0_EN | TIMER0_RELOAD_EN |
TIMER0_DIV ( TIMER_DIVIDER_SHIFT ) ) , timer_base + TIMER_CTRL_OFF ) ;
clocksource_mmio_init ( timer_base + TIMER0_VAL_OFF ,
" armada_370_xp_clocksource " ,
timer_clk , 300 , 32 , clocksource_mmio_readl_down ) ;
2013-01-25 21:32:42 +04:00
/* Register the clockevent on the private timer of CPU 0 */
2012-06-13 20:58:09 +04:00
armada_370_xp_clkevt . cpumask = cpumask_of ( 0 ) ;
clockevents_config_and_register ( & armada_370_xp_clkevt ,
timer_clk , 1 , 0xfffffffe ) ;
2013-01-25 21:32:42 +04:00
percpu_armada_370_xp_evt = alloc_percpu ( struct clock_event_device * ) ;
/*
* Setup clockevent timer ( interrupt - driven ) .
*/
* __this_cpu_ptr ( percpu_armada_370_xp_evt ) = & armada_370_xp_clkevt ;
res = request_percpu_irq ( armada_370_xp_clkevt . irq ,
armada_370_xp_timer_interrupt ,
armada_370_xp_clkevt . name ,
percpu_armada_370_xp_evt ) ;
if ( ! res ) {
enable_percpu_irq ( armada_370_xp_clkevt . irq , 0 ) ;
# ifdef CONFIG_LOCAL_TIMERS
local_timer_register ( & armada_370_xp_local_timer_ops ) ;
# endif
}
}