2005-04-16 15:20:36 -07:00
/*
* linux / arch / arm / mach - imx / time . c
*
* Copyright ( C ) 2000 - 2001 Deep Blue Solutions
* Copyright ( C ) 2002 Shane Nay ( shane @ minirl . com )
2007-05-13 17:37:33 +01:00
* Copyright ( C ) 2006 - 2007 Pavel Pisa ( ppisa @ pikron . com )
2005-04-16 15:20:36 -07: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/kernel.h>
# include <linux/sched.h>
# include <linux/init.h>
# include <linux/interrupt.h>
2006-07-01 22:32:34 +01:00
# include <linux/irq.h>
2005-04-16 15:20:36 -07:00
# include <linux/time.h>
2006-12-06 17:19:44 +01:00
# include <linux/clocksource.h>
2007-05-13 17:37:33 +01:00
# include <linux/clockchips.h>
2005-04-16 15:20:36 -07:00
# include <asm/hardware.h>
# include <asm/io.h>
# include <asm/leds.h>
# include <asm/irq.h>
# include <asm/mach/time.h>
/* Use timer 1 as system timer */
# define TIMER_BASE IMX_TIM1_BASE
2007-05-13 17:37:33 +01:00
static struct clock_event_device clockevent_imx ;
static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED ;
2005-04-16 15:20:36 -07:00
/*
* IRQ handler for the timer
*/
static irqreturn_t
2006-10-06 10:53:39 -07:00
imx_timer_interrupt ( int irq , void * dev_id )
2005-04-16 15:20:36 -07:00
{
2007-05-13 17:37:33 +01:00
struct clock_event_device * evt = & clockevent_imx ;
2006-12-06 17:19:44 +01:00
uint32_t tstat ;
2007-05-13 17:37:33 +01:00
irqreturn_t ret = IRQ_NONE ;
2005-04-16 15:20:36 -07:00
/* clear the interrupt */
2006-12-06 17:19:44 +01:00
tstat = IMX_TSTAT ( TIMER_BASE ) ;
IMX_TSTAT ( TIMER_BASE ) = 0 ;
if ( tstat & TSTAT_COMP ) {
2007-05-13 17:37:33 +01:00
evt - > event_handler ( evt ) ;
ret = IRQ_HANDLED ;
2006-12-06 17:19:44 +01:00
}
2005-04-16 15:20:36 -07:00
2007-05-13 17:37:33 +01:00
return ret ;
2005-04-16 15:20:36 -07:00
}
static struct irqaction imx_timer_irq = {
. name = " i.MX Timer Tick " ,
2007-05-08 00:35:39 -07:00
. flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL ,
2005-06-26 17:06:36 +01:00
. handler = imx_timer_interrupt ,
2005-04-16 15:20:36 -07:00
} ;
/*
2006-12-06 17:19:44 +01:00
* Set up timer hardware into expected mode and state .
2005-04-16 15:20:36 -07:00
*/
2006-12-06 17:19:44 +01:00
static void __init imx_timer_hardware_init ( void )
2005-04-16 15:20:36 -07:00
{
/*
* Initialise to a known state ( all timers off , and timing reset )
*/
IMX_TCTL ( TIMER_BASE ) = 0 ;
IMX_TPRER ( TIMER_BASE ) = 0 ;
2006-12-06 17:19:44 +01:00
2007-05-13 17:37:33 +01:00
IMX_TCTL ( TIMER_BASE ) = TCTL_FRR | TCTL_CLK_PCLK1 | TCTL_TEN ;
2006-12-06 17:19:44 +01:00
}
cycle_t imx_get_cycles ( void )
{
return IMX_TCN ( TIMER_BASE ) ;
}
static struct clocksource clocksource_imx = {
. name = " imx_timer1 " ,
. rating = 200 ,
. read = imx_get_cycles ,
. mask = 0xFFFFFFFF ,
. shift = 20 ,
2007-02-16 01:27:37 -08:00
. flags = CLOCK_SOURCE_IS_CONTINUOUS ,
2006-12-06 17:19:44 +01:00
} ;
static int __init imx_clocksource_init ( void )
{
clocksource_imx . mult =
clocksource_hz2mult ( imx_get_perclk1 ( ) , clocksource_imx . shift ) ;
clocksource_register ( & clocksource_imx ) ;
return 0 ;
}
2007-05-13 17:37:33 +01:00
static int imx_set_next_event ( unsigned long evt ,
struct clock_event_device * unused )
{
unsigned long tcmp ;
tcmp = IMX_TCN ( TIMER_BASE ) + evt ;
IMX_TCMP ( TIMER_BASE ) = tcmp ;
return ( int32_t ) ( tcmp - IMX_TCN ( TIMER_BASE ) ) < 0 ? - ETIME : 0 ;
}
# ifdef DEBUG
static const char * clock_event_mode_label [ ] = {
[ CLOCK_EVT_MODE_PERIODIC ] = " CLOCK_EVT_MODE_PERIODIC " ,
[ CLOCK_EVT_MODE_ONESHOT ] = " CLOCK_EVT_MODE_ONESHOT " ,
[ CLOCK_EVT_MODE_SHUTDOWN ] = " CLOCK_EVT_MODE_SHUTDOWN " ,
[ CLOCK_EVT_MODE_UNUSED ] = " CLOCK_EVT_MODE_UNUSED "
} ;
# endif /*DEBUG*/
static void imx_set_mode ( enum clock_event_mode mode , struct clock_event_device * evt )
{
unsigned long flags ;
/*
* The timer interrupt generation is disabled at least
* for enough time to call imx_set_next_event ( )
*/
local_irq_save ( flags ) ;
/* Disable interrupt in GPT module */
IMX_TCTL ( TIMER_BASE ) & = ~ TCTL_IRQEN ;
if ( mode ! = clockevent_mode ) {
/* Set event time into far-far future */
IMX_TCMP ( TIMER_BASE ) = IMX_TCN ( TIMER_BASE ) - 3 ;
/* Clear pending interrupt */
IMX_TSTAT ( TIMER_BASE ) & = ~ TSTAT_COMP ;
}
# ifdef DEBUG
printk ( KERN_INFO " imx_set_mode: changing mode from %s to %s \n " ,
clock_event_mode_label [ clockevent_mode ] , clock_event_mode_label [ mode ] ) ;
# endif /*DEBUG*/
/* Remember timer mode */
clockevent_mode = mode ;
local_irq_restore ( flags ) ;
switch ( mode ) {
case CLOCK_EVT_MODE_PERIODIC :
printk ( KERN_ERR " imx_set_mode: Periodic mode is not supported for i.MX \n " ) ;
break ;
case CLOCK_EVT_MODE_ONESHOT :
/*
* Do not put overhead of interrupt enable / disable into
* imx_set_next_event ( ) , the core has about 4 minutes
* to call imx_set_next_event ( ) or shutdown clock after
* mode switching
*/
local_irq_save ( flags ) ;
IMX_TCTL ( TIMER_BASE ) | = TCTL_IRQEN ;
local_irq_restore ( flags ) ;
break ;
case CLOCK_EVT_MODE_SHUTDOWN :
case CLOCK_EVT_MODE_UNUSED :
/* Left event sources disabled, no more interrupts appears */
break ;
}
}
static struct clock_event_device clockevent_imx = {
. name = " imx_timer1 " ,
. features = CLOCK_EVT_FEAT_ONESHOT ,
. shift = 32 ,
. set_mode = imx_set_mode ,
. set_next_event = imx_set_next_event ,
. rating = 200 ,
} ;
static int __init imx_clockevent_init ( void )
{
clockevent_imx . mult = div_sc ( imx_get_perclk1 ( ) , NSEC_PER_SEC ,
clockevent_imx . shift ) ;
clockevent_imx . max_delta_ns =
clockevent_delta2ns ( 0xfffffffe , & clockevent_imx ) ;
clockevent_imx . min_delta_ns =
clockevent_delta2ns ( 0xf , & clockevent_imx ) ;
clockevent_imx . cpumask = cpumask_of_cpu ( 0 ) ;
clockevents_register_device ( & clockevent_imx ) ;
return 0 ;
}
2006-12-06 17:19:44 +01:00
static void __init imx_timer_init ( void )
{
imx_timer_hardware_init ( ) ;
imx_clocksource_init ( ) ;
2005-04-16 15:20:36 -07:00
2007-05-13 17:37:33 +01:00
imx_clockevent_init ( ) ;
2005-04-16 15:20:36 -07:00
/*
* Make irqs happen for the system timer
*/
setup_irq ( TIM1_INT , & imx_timer_irq ) ;
}
struct sys_timer imx_timer = {
. init = imx_timer_init ,
} ;