2006-11-05 15:40:13 +09:00
/*
* arch / sh / kernel / timers / timer - mtu2 . c - MTU2 Timer Support
*
* Copyright ( C ) 2005 Paul Mundt
*
* Based off of arch / sh / kernel / timers / timer - tmu . c
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*/
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/seqlock.h>
# include <asm/timer.h>
# include <asm/io.h>
# include <asm/irq.h>
# include <asm/clock.h>
/*
* We use channel 1 for our lowly system timer . Channel 2 would be the other
* likely candidate , but we leave it alone as it has higher divisors that
* would be of more use to other more interesting applications .
*
* TODO : Presently we only implement a 16 - bit single - channel system timer .
* However , we can implement channel cascade if we go the overflow route and
* get away with using 2 MTU2 channels as a 32 - bit timer .
*/
# define MTU2_TSTR 0xfffe4280
# define MTU2_TCR_1 0xfffe4380
# define MTU2_TMDR_1 0xfffe4381
# define MTU2_TIOR_1 0xfffe4382
# define MTU2_TIER_1 0xfffe4384
# define MTU2_TSR_1 0xfffe4385
# define MTU2_TCNT_1 0xfffe4386 /* 16-bit counter */
# define MTU2_TGRA_1 0xfffe438a
# define STBCR3 0xfffe0408
# define MTU2_TSTR_CST1 (1 << 1) /* Counter Start 1 */
# define MTU2_TSR_TGFA (1 << 0) /* GRA compare match */
# define MTU2_TIER_TGIEA (1 << 0) /* GRA compare match interrupt enable */
# define MTU2_TCR_INIT 0x22
# define MTU2_TCR_CALIB 0x00
static unsigned long mtu2_timer_get_offset ( void )
{
int count ;
static int count_p = 0x7fff ; /* for the first call after boot */
static unsigned long jiffies_p = 0 ;
/*
* cache volatile jiffies temporarily ; we have IRQs turned off .
*/
unsigned long jiffies_t ;
/* timer count may underflow right here */
count = ctrl_inw ( MTU2_TCNT_1 ) ; /* read the latched count */
jiffies_t = jiffies ;
/*
* avoiding timer inconsistencies ( they are rare , but they happen ) . . .
* there is one kind of problem that must be avoided here :
* 1. the timer counter underflows
*/
if ( jiffies_t = = jiffies_p ) {
if ( count > count_p ) {
if ( ctrl_inb ( MTU2_TSR_1 ) & MTU2_TSR_TGFA ) {
count - = LATCH ;
} else {
printk ( " %s (): hardware timer problem? \n " ,
__FUNCTION__ ) ;
}
}
} else
jiffies_p = jiffies_t ;
count_p = count ;
count = ( ( LATCH - 1 ) - count ) * TICK_SIZE ;
count = ( count + LATCH / 2 ) / LATCH ;
return count ;
}
2006-11-05 16:48:42 +09:00
static irqreturn_t mtu2_timer_interrupt ( int irq , void * dev_id )
2006-11-05 15:40:13 +09:00
{
unsigned long timer_status ;
/* Clear TGFA bit */
timer_status = ctrl_inb ( MTU2_TSR_1 ) ;
timer_status & = ~ MTU2_TSR_TGFA ;
ctrl_outb ( timer_status , MTU2_TSR_1 ) ;
/* Do timer tick */
write_seqlock ( & xtime_lock ) ;
2006-11-05 16:48:42 +09:00
handle_timer_tick ( ) ;
2006-11-05 15:40:13 +09:00
write_sequnlock ( & xtime_lock ) ;
return IRQ_HANDLED ;
}
static struct irqaction mtu2_irq = {
. name = " timer " ,
. handler = mtu2_timer_interrupt ,
2006-12-01 13:12:05 +09:00
. flags = IRQF_DISABLED | IRQF_TIMER ,
2006-11-05 15:40:13 +09:00
. mask = CPU_MASK_NONE ,
} ;
static unsigned int divisors [ ] = { 1 , 4 , 16 , 64 , 1 , 1 , 256 } ;
static void mtu2_clk_init ( struct clk * clk )
{
u8 idx = MTU2_TCR_INIT & 0x7 ;
clk - > rate = clk - > parent - > rate / divisors [ idx ] ;
/* Start TCNT counting */
ctrl_outb ( ctrl_inb ( MTU2_TSTR ) | MTU2_TSTR_CST1 , MTU2_TSTR ) ;
}
static void mtu2_clk_recalc ( struct clk * clk )
{
u8 idx = ctrl_inb ( MTU2_TCR_1 ) & 0x7 ;
clk - > rate = clk - > parent - > rate / divisors [ idx ] ;
}
static struct clk_ops mtu2_clk_ops = {
. init = mtu2_clk_init ,
. recalc = mtu2_clk_recalc ,
} ;
static struct clk mtu2_clk1 = {
. name = " mtu2_clk1 " ,
. ops = & mtu2_clk_ops ,
} ;
static int mtu2_timer_start ( void )
{
ctrl_outb ( ctrl_inb ( MTU2_TSTR ) | MTU2_TSTR_CST1 , MTU2_TSTR ) ;
return 0 ;
}
static int mtu2_timer_stop ( void )
{
ctrl_outb ( ctrl_inb ( MTU2_TSTR ) & ~ MTU2_TSTR_CST1 , MTU2_TSTR ) ;
return 0 ;
}
static int mtu2_timer_init ( void )
{
u8 tmp ;
unsigned long interval ;
2006-11-20 11:18:30 +09:00
setup_irq ( CONFIG_SH_TIMER_IRQ , & mtu2_irq ) ;
2006-11-05 15:40:13 +09:00
2006-12-01 13:15:14 +09:00
mtu2_clk1 . parent = clk_get ( NULL , " module_clk " ) ;
2006-11-05 15:40:13 +09:00
ctrl_outb ( ctrl_inb ( STBCR3 ) & ( ~ 0x20 ) , STBCR3 ) ;
/* Normal operation */
ctrl_outb ( 0 , MTU2_TMDR_1 ) ;
ctrl_outb ( MTU2_TCR_INIT , MTU2_TCR_1 ) ;
ctrl_outb ( 0x01 , MTU2_TIOR_1 ) ;
/* Enable underflow interrupt */
ctrl_outb ( ctrl_inb ( MTU2_TIER_1 ) | MTU2_TIER_TGIEA , MTU2_TIER_1 ) ;
interval = CONFIG_SH_PCLK_FREQ / 16 / HZ ;
printk ( KERN_INFO " Interval = %ld \n " , interval ) ;
ctrl_outw ( interval , MTU2_TGRA_1 ) ;
ctrl_outw ( 0 , MTU2_TCNT_1 ) ;
clk_register ( & mtu2_clk1 ) ;
clk_enable ( & mtu2_clk1 ) ;
return 0 ;
}
struct sys_timer_ops mtu2_timer_ops = {
. init = mtu2_timer_init ,
. start = mtu2_timer_start ,
. stop = mtu2_timer_stop ,
2006-11-05 16:48:42 +09:00
# ifndef CONFIG_GENERIC_TIME
2006-11-05 15:40:13 +09:00
. get_offset = mtu2_timer_get_offset ,
2006-11-05 16:48:42 +09:00
# endif
2006-11-05 15:40:13 +09:00
} ;
struct sys_timer mtu2_timer = {
. name = " mtu2 " ,
. ops = & mtu2_timer_ops ,
} ;