2009-07-02 19:06:47 +01:00
/*
2010-09-13 13:40:04 +01:00
* linux / arch / arm / plat - nomadik / timer . c
2009-07-02 19:06:47 +01:00
*
* Copyright ( C ) 2008 STMicroelectronics
2010-03-05 12:38:51 +01:00
* Copyright ( C ) 2010 Alessandro Rubini
2010-11-19 10:16:05 +01:00
* Copyright ( C ) 2010 Linus Walleij for ST - Ericsson
2009-07-02 19:06:47 +01:00
*
* 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/interrupt.h>
# include <linux/irq.h>
# include <linux/io.h>
# include <linux/clockchips.h>
2010-05-26 07:38:54 +01:00
# include <linux/clk.h>
2009-07-02 19:06:47 +01:00
# include <linux/jiffies.h>
2010-05-26 07:38:54 +01:00
# include <linux/err.h>
2010-12-15 19:19:25 +00:00
# include <linux/sched.h>
2009-07-02 19:06:47 +01:00
# include <asm/mach/time.h>
2010-12-15 21:53:02 +00:00
# include <asm/sched_clock.h>
2009-07-02 19:06:47 +01:00
2009-11-12 06:20:54 +01:00
# include <plat/mtu.h>
2009-07-02 19:06:47 +01:00
2010-11-19 10:16:05 +01:00
void __iomem * mtu_base ; /* Assigned by machine code */
2009-11-12 06:20:54 +01:00
2010-05-07 10:03:02 +01:00
/*
* Override the global weak sched_clock symbol with this
* local implementation which uses the clocksource to get some
2010-11-19 10:16:05 +01:00
* better resolution when scheduling the kernel .
2010-05-07 10:03:02 +01:00
*/
2010-12-15 21:53:02 +00:00
static DEFINE_CLOCK_DATA ( cd ) ;
2010-11-19 10:16:05 +01:00
2010-05-07 10:03:02 +01:00
unsigned long long notrace sched_clock ( void )
{
2010-12-15 21:53:02 +00:00
u32 cyc ;
2010-11-19 10:16:05 +01:00
if ( unlikely ( ! mtu_base ) )
return 0 ;
2010-12-15 21:53:02 +00:00
cyc = - readl ( mtu_base + MTU_VAL ( 0 ) ) ;
return cyc_to_sched_clock ( & cd , cyc , ( u32 ) ~ 0 ) ;
2010-11-19 10:16:05 +01:00
}
2010-12-15 21:53:02 +00:00
static void notrace nomadik_update_sched_clock ( void )
2010-11-19 10:16:05 +01:00
{
2010-12-15 21:53:02 +00:00
u32 cyc = - readl ( mtu_base + MTU_VAL ( 0 ) ) ;
update_sched_clock ( & cd , cyc , ( u32 ) ~ 0 ) ;
2010-05-07 10:03:02 +01:00
}
2010-03-05 12:38:51 +01:00
/* Clockevent device: use one-shot mode */
2009-07-02 19:06:47 +01:00
static void nmdk_clkevt_mode ( enum clock_event_mode mode ,
struct clock_event_device * dev )
{
2010-03-05 12:38:51 +01:00
u32 cr ;
2009-07-02 19:06:47 +01:00
switch ( mode ) {
case CLOCK_EVT_MODE_PERIODIC :
2010-03-05 12:38:51 +01:00
pr_err ( " %s: periodic mode not supported \n " , __func__ ) ;
2009-07-02 19:06:47 +01:00
break ;
case CLOCK_EVT_MODE_ONESHOT :
2010-03-05 12:38:51 +01:00
/* Load highest value, enable device, enable interrupts */
cr = readl ( mtu_base + MTU_CR ( 1 ) ) ;
writel ( 0 , mtu_base + MTU_LR ( 1 ) ) ;
writel ( cr | MTU_CRn_ENA , mtu_base + MTU_CR ( 1 ) ) ;
2010-09-13 13:40:04 +01:00
writel ( 1 < < 1 , mtu_base + MTU_IMSC ) ;
2010-03-05 12:38:51 +01:00
break ;
2009-07-02 19:06:47 +01:00
case CLOCK_EVT_MODE_SHUTDOWN :
case CLOCK_EVT_MODE_UNUSED :
2010-03-05 12:38:51 +01:00
/* disable irq */
writel ( 0 , mtu_base + MTU_IMSC ) ;
2010-06-01 08:26:49 +01:00
/* disable timer */
cr = readl ( mtu_base + MTU_CR ( 1 ) ) ;
cr & = ~ MTU_CRn_ENA ;
writel ( cr , mtu_base + MTU_CR ( 1 ) ) ;
/* load some high default value */
writel ( 0xffffffff , mtu_base + MTU_LR ( 1 ) ) ;
2009-07-02 19:06:47 +01:00
break ;
case CLOCK_EVT_MODE_RESUME :
break ;
}
}
2010-03-05 12:38:51 +01:00
static int nmdk_clkevt_next ( unsigned long evt , struct clock_event_device * ev )
{
/* writing the value has immediate effect */
writel ( evt , mtu_base + MTU_LR ( 1 ) ) ;
return 0 ;
}
2009-07-02 19:06:47 +01:00
static struct clock_event_device nmdk_clkevt = {
2010-03-05 12:38:51 +01:00
. name = " mtu_1 " ,
. features = CLOCK_EVT_FEAT_ONESHOT ,
. rating = 200 ,
2009-07-02 19:06:47 +01:00
. set_mode = nmdk_clkevt_mode ,
2010-03-05 12:38:51 +01:00
. set_next_event = nmdk_clkevt_next ,
2009-07-02 19:06:47 +01:00
} ;
/*
2010-03-05 12:38:51 +01:00
* IRQ Handler for timer 1 of the MTU block .
2009-07-02 19:06:47 +01:00
*/
static irqreturn_t nmdk_timer_interrupt ( int irq , void * dev_id )
{
2010-03-05 12:38:51 +01:00
struct clock_event_device * evdev = dev_id ;
2009-07-02 19:06:47 +01:00
2010-03-05 12:38:51 +01:00
writel ( 1 < < 1 , mtu_base + MTU_ICR ) ; /* Interrupt clear reg */
evdev - > event_handler ( evdev ) ;
2009-07-02 19:06:47 +01:00
return IRQ_HANDLED ;
}
static struct irqaction nmdk_timer_irq = {
. name = " Nomadik Timer Tick " ,
. flags = IRQF_DISABLED | IRQF_TIMER ,
. handler = nmdk_timer_interrupt ,
2010-03-05 12:38:51 +01:00
. dev_id = & nmdk_clkevt ,
2009-07-02 19:06:47 +01:00
} ;
2009-11-12 06:20:54 +01:00
void __init nmdk_timer_init ( void )
2009-07-02 19:06:47 +01:00
{
unsigned long rate ;
2010-05-26 07:38:54 +01:00
struct clk * clk0 ;
2010-09-13 13:40:04 +01:00
u32 cr = MTU_CRn_32BITS ;
2010-05-26 07:38:54 +01:00
clk0 = clk_get_sys ( " mtu0 " , NULL ) ;
BUG_ON ( IS_ERR ( clk0 ) ) ;
clk_enable ( clk0 ) ;
2010-03-05 12:38:51 +01:00
/*
2010-09-13 13:40:04 +01:00
* Tick rate is 2.4 MHz for Nomadik and 2.4 Mhz , 100 MHz or 133 MHz
* for ux500 .
* Use a divide - by - 16 counter if the tick rate is more than 32 MHz .
* At 32 MHz , the timer ( with 32 bit counter ) can be programmed
* to wake - up at a max 127 s a head in time . Dividing a 2.4 MHz timer
* with 16 gives too low timer resolution .
2010-03-05 12:38:51 +01:00
*/
2010-05-26 07:38:54 +01:00
rate = clk_get_rate ( clk0 ) ;
2010-09-13 13:40:04 +01:00
if ( rate > 32000000 ) {
2010-03-05 12:38:51 +01:00
rate / = 16 ;
cr | = MTU_CRn_PRESCALE_16 ;
} else {
cr | = MTU_CRn_PRESCALE_1 ;
}
2009-07-02 19:06:47 +01:00
2010-03-05 12:38:51 +01:00
/* Timer 0 is the free running clocksource */
writel ( cr , mtu_base + MTU_CR ( 0 ) ) ;
writel ( 0 , mtu_base + MTU_LR ( 0 ) ) ;
writel ( 0 , mtu_base + MTU_BGLR ( 0 ) ) ;
writel ( cr | MTU_CRn_ENA , mtu_base + MTU_CR ( 0 ) ) ;
2009-07-02 19:06:47 +01:00
2011-05-08 15:33:30 +01:00
if ( clocksource_mmio_init ( mtu_base + MTU_VAL ( 0 ) , " mtu_0 " ,
rate , 200 , 32 , clocksource_mmio_readl_down ) )
2010-03-05 12:38:51 +01:00
pr_err ( " timer: failed to initialize clock source %s \n " ,
2011-05-08 15:33:30 +01:00
" mtu_0 " ) ;
2010-03-05 12:38:51 +01:00
2010-12-15 21:53:02 +00:00
init_sched_clock ( & cd , nomadik_update_sched_clock , 32 , rate ) ;
2010-11-19 10:16:05 +01:00
2010-09-13 13:38:55 +01:00
/* Timer 1 is used for events */
2010-06-01 08:26:49 +01:00
clockevents_calc_mult_shift ( & nmdk_clkevt , rate , MTU_MIN_RANGE ) ;
2010-03-05 12:38:51 +01:00
writel ( cr | MTU_CRn_ONESHOT , mtu_base + MTU_CR ( 1 ) ) ; /* off, currently */
2010-06-01 08:26:49 +01:00
2010-03-05 12:38:51 +01:00
nmdk_clkevt . max_delta_ns =
clockevent_delta2ns ( 0xffffffff , & nmdk_clkevt ) ;
nmdk_clkevt . min_delta_ns =
clockevent_delta2ns ( 0x00000002 , & nmdk_clkevt ) ;
nmdk_clkevt . cpumask = cpumask_of ( 0 ) ;
2009-07-02 19:06:47 +01:00
/* Register irq and clockevents */
setup_irq ( IRQ_MTU0 , & nmdk_timer_irq ) ;
clockevents_register_device ( & nmdk_clkevt ) ;
}