2013-03-14 01:27:27 +04:00
/*
* Copyright ( C ) 2012 Broadcom Corporation
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; 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/irq.h>
# include <linux/interrupt.h>
# include <linux/jiffies.h>
# include <linux/clockchips.h>
# include <linux/types.h>
# include <linux/io.h>
# include <asm/mach/time.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# define KONA_GPTIMER_STCS_OFFSET 0x00000000
# define KONA_GPTIMER_STCLO_OFFSET 0x00000004
# define KONA_GPTIMER_STCHI_OFFSET 0x00000008
# define KONA_GPTIMER_STCM0_OFFSET 0x0000000C
# define KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT 0
# define KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT 4
struct kona_bcm_timers {
int tmr_irq ;
void __iomem * tmr_regs ;
} ;
static struct kona_bcm_timers timers ;
static u32 arch_timer_rate ;
/*
* We use the peripheral timers for system tick , the cpu global timer for
* profile tick
*/
static void kona_timer_disable_and_clear ( void __iomem * base )
{
uint32_t reg ;
/*
* clear and disable interrupts
* We are using compare / match register 0 for our system interrupts
*/
reg = readl ( base + KONA_GPTIMER_STCS_OFFSET ) ;
/* Clear compare (0) interrupt */
reg | = 1 < < KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT ;
/* disable compare */
reg & = ~ ( 1 < < KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT ) ;
writel ( reg , base + KONA_GPTIMER_STCS_OFFSET ) ;
}
static void
kona_timer_get_counter ( void * timer_base , uint32_t * msw , uint32_t * lsw )
{
void __iomem * base = IOMEM ( timer_base ) ;
int loop_limit = 4 ;
/*
* Read 64 - bit free running counter
* 1. Read hi - word
* 2. Read low - word
* 3. Read hi - word again
* 4.1
* if new hi - word is not equal to previously read hi - word , then
* start from # 1
* 4.2
* if new hi - word is equal to previously read hi - word then stop .
*/
while ( - - loop_limit ) {
* msw = readl ( base + KONA_GPTIMER_STCHI_OFFSET ) ;
* lsw = readl ( base + KONA_GPTIMER_STCLO_OFFSET ) ;
if ( * msw = = readl ( base + KONA_GPTIMER_STCHI_OFFSET ) )
break ;
}
if ( ! loop_limit ) {
pr_err ( " bcm_kona_timer: getting counter failed. \n " ) ;
pr_err ( " Timer will be impacted \n " ) ;
}
return ;
}
static const struct of_device_id bcm_timer_ids [ ] __initconst = {
{ . compatible = " bcm,kona-timer " } ,
{ } ,
} ;
2013-04-23 18:04:14 +04:00
static void __init kona_timers_init ( struct device_node * node )
2013-03-14 01:27:27 +04:00
{
u32 freq ;
if ( ! of_property_read_u32 ( node , " clock-frequency " , & freq ) )
arch_timer_rate = freq ;
else
panic ( " clock-frequency not set in the .dts file " ) ;
/* Setup IRQ numbers */
timers . tmr_irq = irq_of_parse_and_map ( node , 0 ) ;
/* Setup IO addresses */
timers . tmr_regs = of_iomap ( node , 0 ) ;
kona_timer_disable_and_clear ( timers . tmr_regs ) ;
}
static int kona_timer_set_next_event ( unsigned long clc ,
struct clock_event_device * unused )
{
/*
* timer ( 0 ) is disabled by the timer interrupt already
* so , here we reload the next event value and re - enable
* the timer .
*
* This way , we are potentially losing the time between
* timer - interrupt - > set_next_event . CPU local timers , when
* they come in should get rid of skew .
*/
uint32_t lsw , msw ;
uint32_t reg ;
kona_timer_get_counter ( timers . tmr_regs , & msw , & lsw ) ;
/* Load the "next" event tick value */
writel ( lsw + clc , timers . tmr_regs + KONA_GPTIMER_STCM0_OFFSET ) ;
/* Enable compare */
reg = readl ( timers . tmr_regs + KONA_GPTIMER_STCS_OFFSET ) ;
reg | = ( 1 < < KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT ) ;
writel ( reg , timers . tmr_regs + KONA_GPTIMER_STCS_OFFSET ) ;
return 0 ;
}
static void kona_timer_set_mode ( enum clock_event_mode mode ,
struct clock_event_device * unused )
{
switch ( mode ) {
case CLOCK_EVT_MODE_ONESHOT :
/* by default mode is one shot don't do any thing */
break ;
case CLOCK_EVT_MODE_UNUSED :
case CLOCK_EVT_MODE_SHUTDOWN :
default :
kona_timer_disable_and_clear ( timers . tmr_regs ) ;
}
}
static struct clock_event_device kona_clockevent_timer = {
. name = " timer 1 " ,
. features = CLOCK_EVT_FEAT_ONESHOT ,
. set_next_event = kona_timer_set_next_event ,
. set_mode = kona_timer_set_mode
} ;
static void __init kona_timer_clockevents_init ( void )
{
kona_clockevent_timer . cpumask = cpumask_of ( 0 ) ;
clockevents_config_and_register ( & kona_clockevent_timer ,
arch_timer_rate , 6 , 0xffffffff ) ;
}
static irqreturn_t kona_timer_interrupt ( int irq , void * dev_id )
{
struct clock_event_device * evt = & kona_clockevent_timer ;
kona_timer_disable_and_clear ( timers . tmr_regs ) ;
evt - > event_handler ( evt ) ;
return IRQ_HANDLED ;
}
static struct irqaction kona_timer_irq = {
. name = " Kona Timer Tick " ,
. flags = IRQF_TIMER ,
. handler = kona_timer_interrupt ,
} ;
2013-04-23 18:04:14 +04:00
static void __init kona_timer_init ( struct device_node * node )
2013-03-14 01:27:27 +04:00
{
2013-04-23 18:04:14 +04:00
kona_timers_init ( node ) ;
2013-03-14 01:27:27 +04:00
kona_timer_clockevents_init ( ) ;
setup_irq ( timers . tmr_irq , & kona_timer_irq ) ;
kona_timer_set_next_event ( ( arch_timer_rate / HZ ) , NULL ) ;
}
2013-04-23 18:04:14 +04:00
CLOCKSOURCE_OF_DECLARE ( bcm_kona , " bcm,kona-timer " , kona_timer_init ) ;