2007-02-11 18:31:01 +01:00
/* linux/arch/arm/plat-s3c24xx/time.c
2005-04-16 15:20:36 -07:00
*
* Copyright ( C ) 2003 - 2005 Simtec Electronics
* Ben Dooks , < ben @ simtec . co . uk >
*
* 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/init.h>
# include <linux/interrupt.h>
2006-07-01 22:32:39 +01:00
# include <linux/irq.h>
2005-04-16 15:20:36 -07:00
# include <linux/err.h>
2006-01-07 16:15:52 +00:00
# include <linux/clk.h>
2008-09-06 12:10:45 +01:00
# include <linux/io.h>
2008-11-21 10:36:05 +00:00
# include <linux/platform_device.h>
2005-04-16 15:20:36 -07:00
# include <asm/system.h>
# include <asm/leds.h>
# include <asm/mach-types.h>
# include <asm/irq.h>
2008-08-05 16:14:15 +01:00
# include <mach/map.h>
2008-10-07 22:26:09 +01:00
# include <plat/regs-timer.h>
2008-08-05 16:14:15 +01:00
# include <mach/regs-irq.h>
2005-04-16 15:20:36 -07:00
# include <asm/mach/time.h>
2008-10-21 14:06:35 +01:00
# include <mach/tick.h>
2005-04-16 15:20:36 -07:00
2008-10-07 23:09:51 +01:00
# include <plat/clock.h>
2008-10-07 22:26:09 +01:00
# include <plat/cpu.h>
2005-04-16 15:20:36 -07:00
static unsigned long timer_startval ;
static unsigned long timer_usec_ticks ;
2008-10-21 14:07:01 +01:00
# ifndef TICK_MAX
# define TICK_MAX (0xffff)
# endif
2005-04-16 15:20:36 -07:00
# define TIMER_USEC_SHIFT 16
/* we use the shifted arithmetic to work out the ratio of timer ticks
* to usecs , as often the peripheral clock is not a nice even multiple
* of 1 MHz .
*
* shift of 14 and 15 are too low for the 12 MHz , 16 seems to be ok
* for the current HZ value of 200 without producing overflows .
*
* Original patch by Dimitry Andric , updated by Ben Dooks
*/
/* timer_mask_usec_ticks
*
* given a clock and divisor , make the value to pass into timer_ticks_to_usec
* to scale the ticks into usecs
*/
static inline unsigned long
timer_mask_usec_ticks ( unsigned long scaler , unsigned long pclk )
{
unsigned long den = pclk / 1000 ;
return ( ( 1000 < < TIMER_USEC_SHIFT ) * scaler + ( den > > 1 ) ) / den ;
}
/* timer_ticks_to_usec
*
* convert timer ticks to usec .
*/
static inline unsigned long timer_ticks_to_usec ( unsigned long ticks )
{
unsigned long res ;
res = ticks * timer_usec_ticks ;
res + = 1 < < ( TIMER_USEC_SHIFT - 4 ) ; /* round up slightly */
return res > > TIMER_USEC_SHIFT ;
}
/***
* Returns microsecond since last clock interrupt . Note that interrupts
* will have been disabled by do_gettimeoffset ( )
* IRQs are disabled before entering here from do_gettimeofday ( )
*/
static unsigned long s3c2410_gettimeoffset ( void )
{
unsigned long tdone ;
unsigned long tval ;
/* work out how many ticks have gone since last timer interrupt */
2008-10-21 14:06:53 +01:00
tval = __raw_readl ( S3C2410_TCNTO ( 4 ) ) ;
2005-04-16 15:20:36 -07:00
tdone = timer_startval - tval ;
/* check to see if there is an interrupt pending */
2008-10-21 14:06:35 +01:00
if ( s3c24xx_ostimer_pending ( ) ) {
2005-04-16 15:20:36 -07:00
/* re-read the timer, and try and fix up for the missed
* interrupt . Note , the interrupt may go off before the
* timer has re - loaded from wrapping .
*/
tval = __raw_readl ( S3C2410_TCNTO ( 4 ) ) ;
tdone = timer_startval - tval ;
if ( tval ! = 0 )
tdone + = timer_startval ;
}
return timer_ticks_to_usec ( tdone ) ;
}
/*
* IRQ handler for the timer
*/
static irqreturn_t
2006-10-06 10:53:39 -07:00
s3c2410_timer_interrupt ( int irq , void * dev_id )
2005-04-16 15:20:36 -07:00
{
2006-10-06 10:53:39 -07:00
timer_tick ( ) ;
2005-04-16 15:20:36 -07:00
return IRQ_HANDLED ;
}
static struct irqaction s3c2410_timer_irq = {
. name = " S3C2410 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 = s3c2410_timer_interrupt ,
2005-04-16 15:20:36 -07:00
} ;
2006-03-20 17:10:03 +00:00
# define use_tclk1_12() ( \
machine_is_bast ( ) | | \
machine_is_vr1000 ( ) | | \
machine_is_anubis ( ) | | \
2008-10-21 14:06:53 +01:00
machine_is_osiris ( ) )
2006-03-20 17:10:03 +00:00
2008-11-21 10:36:05 +00:00
static struct clk * tin ;
static struct clk * tdiv ;
static struct clk * timerclk ;
2005-04-16 15:20:36 -07:00
/*
* Set up timer interrupt , and return the current time in seconds .
*
* Currently we only use timer4 , as it is the only timer which has no
* other function that can be exploited externally
*/
static void s3c2410_timer_setup ( void )
{
unsigned long tcon ;
unsigned long tcnt ;
unsigned long tcfg1 ;
unsigned long tcfg0 ;
2008-10-21 14:07:01 +01:00
tcnt = TICK_MAX ; /* default value for tcnt */
2005-04-16 15:20:36 -07:00
/* configure the system for whichever machine is in use */
2006-03-20 17:10:03 +00:00
if ( use_tclk1_12 ( ) ) {
2005-04-16 15:20:36 -07:00
/* timer is at 12MHz, scaler is 1 */
timer_usec_ticks = timer_mask_usec_ticks ( 1 , 12000000 ) ;
tcnt = 12000000 / HZ ;
2008-11-21 10:36:05 +00:00
tcfg1 = __raw_readl ( S3C2410_TCFG1 ) ;
2005-04-16 15:20:36 -07:00
tcfg1 & = ~ S3C2410_TCFG1_MUX4_MASK ;
tcfg1 | = S3C2410_TCFG1_MUX4_TCLK1 ;
2008-11-21 10:36:05 +00:00
__raw_writel ( tcfg1 , S3C2410_TCFG1 ) ;
2005-04-16 15:20:36 -07:00
} else {
unsigned long pclk ;
2008-11-21 10:36:05 +00:00
struct clk * tscaler ;
2005-04-16 15:20:36 -07:00
/* for the h1940 (and others), we use the pclk from the core
* to generate the timer values . since values around 50 to
* 70 MHz are not values we can directly generate the timer
* value from , we need to pre - scale and divide before using it .
*
* for instance , using 50.7 MHz and dividing by 6 gives 8.45 MHz
* ( 8.45 ticks per usec )
*/
2008-11-21 10:36:05 +00:00
pclk = clk_get_rate ( timerclk ) ;
2005-04-16 15:20:36 -07:00
/* configure clock tick */
timer_usec_ticks = timer_mask_usec_ticks ( 6 , pclk ) ;
2008-11-21 10:36:05 +00:00
tscaler = clk_get_parent ( tdiv ) ;
2005-04-16 15:20:36 -07:00
2008-11-21 10:36:05 +00:00
clk_set_rate ( tscaler , pclk / 3 ) ;
clk_set_rate ( tdiv , pclk / 6 ) ;
clk_set_parent ( tin , tdiv ) ;
2005-04-16 15:20:36 -07:00
2008-11-21 10:36:05 +00:00
tcnt = clk_get_rate ( tin ) / HZ ;
2005-04-16 15:20:36 -07:00
}
2008-11-21 10:36:05 +00:00
tcon = __raw_readl ( S3C2410_TCON ) ;
tcfg0 = __raw_readl ( S3C2410_TCFG0 ) ;
tcfg1 = __raw_readl ( S3C2410_TCFG1 ) ;
2005-04-16 15:20:36 -07:00
/* timers reload after counting zero, so reduce the count by 1 */
tcnt - - ;
2008-10-21 14:06:53 +01:00
printk ( KERN_DEBUG " timer tcon=%08lx, tcnt %04lx, tcfg %08lx,%08lx, usec %08lx \n " ,
2005-04-16 15:20:36 -07:00
tcon , tcnt , tcfg0 , tcfg1 , timer_usec_ticks ) ;
/* check to see if timer is within 16bit range... */
2008-10-21 14:07:01 +01:00
if ( tcnt > TICK_MAX ) {
2005-04-16 15:20:36 -07:00
panic ( " setup_timer: HZ is too small, cannot configure timer! " ) ;
return ;
}
__raw_writel ( tcfg1 , S3C2410_TCFG1 ) ;
__raw_writel ( tcfg0 , S3C2410_TCFG0 ) ;
timer_startval = tcnt ;
__raw_writel ( tcnt , S3C2410_TCNTB ( 4 ) ) ;
/* ensure timer is stopped... */
tcon & = ~ ( 7 < < 20 ) ;
tcon | = S3C2410_TCON_T4RELOAD ;
tcon | = S3C2410_TCON_T4MANUALUPD ;
__raw_writel ( tcon , S3C2410_TCON ) ;
__raw_writel ( tcnt , S3C2410_TCNTB ( 4 ) ) ;
__raw_writel ( tcnt , S3C2410_TCMPB ( 4 ) ) ;
/* start the timer running */
tcon | = S3C2410_TCON_T4START ;
tcon & = ~ S3C2410_TCON_T4MANUALUPD ;
__raw_writel ( tcon , S3C2410_TCON ) ;
}
2008-11-21 10:36:05 +00:00
static void __init s3c2410_timer_resources ( void )
{
struct platform_device tmpdev ;
tmpdev . dev . bus = & platform_bus_type ;
tmpdev . id = 4 ;
timerclk = clk_get ( NULL , " timers " ) ;
if ( IS_ERR ( timerclk ) )
panic ( " failed to get clock for system timer " ) ;
clk_enable ( timerclk ) ;
if ( ! use_tclk1_12 ( ) ) {
tin = clk_get ( & tmpdev . dev , " pwm-tin " ) ;
if ( IS_ERR ( tin ) )
panic ( " failed to get pwm-tin clock for system timer " ) ;
tdiv = clk_get ( & tmpdev . dev , " pwm-tdiv " ) ;
if ( IS_ERR ( tdiv ) )
panic ( " failed to get pwm-tdiv clock for system timer " ) ;
}
clk_enable ( tin ) ;
}
2008-10-21 14:06:53 +01:00
static void __init s3c2410_timer_init ( void )
2005-04-16 15:20:36 -07:00
{
2008-11-21 10:36:05 +00:00
s3c2410_timer_resources ( ) ;
2005-04-16 15:20:36 -07:00
s3c2410_timer_setup ( ) ;
setup_irq ( IRQ_TIMER4 , & s3c2410_timer_irq ) ;
}
struct sys_timer s3c24xx_timer = {
. init = s3c2410_timer_init ,
. offset = s3c2410_gettimeoffset ,
. resume = s3c2410_timer_setup
} ;