2012-04-17 11:20:49 -07:00
/*
2011-03-12 08:02:07 +09:00
* Copyright ( c ) 2011 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com/
*
* S5P - Common hr - timer support
*
* 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/interrupt.h>
# include <linux/irq.h>
# include <linux/err.h>
# include <linux/clk.h>
# include <linux/clockchips.h>
# include <linux/platform_device.h>
# include <asm/smp_twd.h>
# include <asm/mach/time.h>
# include <asm/mach/arch.h>
# include <asm/mach/map.h>
# include <asm/sched_clock.h>
# include <mach/map.h>
# include <plat/devs.h>
# include <plat/regs-timer.h>
# include <plat/s5p-time.h>
static struct clk * tin_event ;
static struct clk * tin_source ;
static struct clk * tdiv_event ;
static struct clk * tdiv_source ;
static struct clk * timerclk ;
static struct s5p_timer_source timer_source ;
static unsigned long clock_count_per_tick ;
static void s5p_timer_resume ( void ) ;
static void s5p_time_stop ( enum s5p_timer_mode mode )
{
unsigned long tcon ;
tcon = __raw_readl ( S3C2410_TCON ) ;
switch ( mode ) {
case S5P_PWM0 :
tcon & = ~ S3C2410_TCON_T0START ;
break ;
case S5P_PWM1 :
tcon & = ~ S3C2410_TCON_T1START ;
break ;
case S5P_PWM2 :
tcon & = ~ S3C2410_TCON_T2START ;
break ;
case S5P_PWM3 :
tcon & = ~ S3C2410_TCON_T3START ;
break ;
case S5P_PWM4 :
tcon & = ~ S3C2410_TCON_T4START ;
break ;
default :
printk ( KERN_ERR " Invalid Timer %d \n " , mode ) ;
break ;
}
__raw_writel ( tcon , S3C2410_TCON ) ;
}
static void s5p_time_setup ( enum s5p_timer_mode mode , unsigned long tcnt )
{
unsigned long tcon ;
tcon = __raw_readl ( S3C2410_TCON ) ;
tcnt - - ;
switch ( mode ) {
case S5P_PWM0 :
tcon & = ~ ( 0x0f < < 0 ) ;
tcon | = S3C2410_TCON_T0MANUALUPD ;
break ;
case S5P_PWM1 :
tcon & = ~ ( 0x0f < < 8 ) ;
tcon | = S3C2410_TCON_T1MANUALUPD ;
break ;
case S5P_PWM2 :
tcon & = ~ ( 0x0f < < 12 ) ;
tcon | = S3C2410_TCON_T2MANUALUPD ;
break ;
case S5P_PWM3 :
tcon & = ~ ( 0x0f < < 16 ) ;
tcon | = S3C2410_TCON_T3MANUALUPD ;
break ;
case S5P_PWM4 :
tcon & = ~ ( 0x07 < < 20 ) ;
tcon | = S3C2410_TCON_T4MANUALUPD ;
break ;
default :
printk ( KERN_ERR " Invalid Timer %d \n " , mode ) ;
break ;
}
__raw_writel ( tcnt , S3C2410_TCNTB ( mode ) ) ;
__raw_writel ( tcnt , S3C2410_TCMPB ( mode ) ) ;
__raw_writel ( tcon , S3C2410_TCON ) ;
}
static void s5p_time_start ( enum s5p_timer_mode mode , bool periodic )
{
unsigned long tcon ;
tcon = __raw_readl ( S3C2410_TCON ) ;
switch ( mode ) {
case S5P_PWM0 :
tcon | = S3C2410_TCON_T0START ;
tcon & = ~ S3C2410_TCON_T0MANUALUPD ;
if ( periodic )
tcon | = S3C2410_TCON_T0RELOAD ;
else
tcon & = ~ S3C2410_TCON_T0RELOAD ;
break ;
case S5P_PWM1 :
tcon | = S3C2410_TCON_T1START ;
tcon & = ~ S3C2410_TCON_T1MANUALUPD ;
if ( periodic )
tcon | = S3C2410_TCON_T1RELOAD ;
else
tcon & = ~ S3C2410_TCON_T1RELOAD ;
break ;
case S5P_PWM2 :
tcon | = S3C2410_TCON_T2START ;
tcon & = ~ S3C2410_TCON_T2MANUALUPD ;
if ( periodic )
tcon | = S3C2410_TCON_T2RELOAD ;
else
tcon & = ~ S3C2410_TCON_T2RELOAD ;
break ;
case S5P_PWM3 :
tcon | = S3C2410_TCON_T3START ;
tcon & = ~ S3C2410_TCON_T3MANUALUPD ;
if ( periodic )
tcon | = S3C2410_TCON_T3RELOAD ;
else
tcon & = ~ S3C2410_TCON_T3RELOAD ;
break ;
case S5P_PWM4 :
tcon | = S3C2410_TCON_T4START ;
tcon & = ~ S3C2410_TCON_T4MANUALUPD ;
if ( periodic )
tcon | = S3C2410_TCON_T4RELOAD ;
else
tcon & = ~ S3C2410_TCON_T4RELOAD ;
break ;
default :
printk ( KERN_ERR " Invalid Timer %d \n " , mode ) ;
break ;
}
__raw_writel ( tcon , S3C2410_TCON ) ;
}
static int s5p_set_next_event ( unsigned long cycles ,
struct clock_event_device * evt )
{
s5p_time_setup ( timer_source . event_id , cycles ) ;
s5p_time_start ( timer_source . event_id , NON_PERIODIC ) ;
return 0 ;
}
static void s5p_set_mode ( enum clock_event_mode mode ,
struct clock_event_device * evt )
{
s5p_time_stop ( timer_source . event_id ) ;
switch ( mode ) {
case CLOCK_EVT_MODE_PERIODIC :
s5p_time_setup ( timer_source . event_id , clock_count_per_tick ) ;
s5p_time_start ( timer_source . event_id , PERIODIC ) ;
break ;
case CLOCK_EVT_MODE_ONESHOT :
break ;
case CLOCK_EVT_MODE_UNUSED :
case CLOCK_EVT_MODE_SHUTDOWN :
break ;
case CLOCK_EVT_MODE_RESUME :
s5p_timer_resume ( ) ;
break ;
}
}
static void s5p_timer_resume ( void )
{
/* event timer restart */
s5p_time_setup ( timer_source . event_id , clock_count_per_tick ) ;
s5p_time_start ( timer_source . event_id , PERIODIC ) ;
/* source timer restart */
s5p_time_setup ( timer_source . source_id , TCNT_MAX ) ;
s5p_time_start ( timer_source . source_id , PERIODIC ) ;
}
void __init s5p_set_timer_source ( enum s5p_timer_mode event ,
enum s5p_timer_mode source )
{
s3c_device_timer [ event ] . dev . bus = & platform_bus_type ;
s3c_device_timer [ source ] . dev . bus = & platform_bus_type ;
timer_source . event_id = event ;
timer_source . source_id = source ;
}
static struct clock_event_device time_event_device = {
. name = " s5p_event_timer " ,
. features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT ,
. rating = 200 ,
. set_next_event = s5p_set_next_event ,
. set_mode = s5p_set_mode ,
} ;
static irqreturn_t s5p_clock_event_isr ( int irq , void * dev_id )
{
struct clock_event_device * evt = dev_id ;
evt - > event_handler ( evt ) ;
return IRQ_HANDLED ;
}
static struct irqaction s5p_clock_event_irq = {
. name = " s5p_time_irq " ,
. flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL ,
. handler = s5p_clock_event_isr ,
. dev_id = & time_event_device ,
} ;
static void __init s5p_clockevent_init ( void )
{
unsigned long pclk ;
unsigned long clock_rate ;
unsigned int irq_number ;
struct clk * tscaler ;
pclk = clk_get_rate ( timerclk ) ;
tscaler = clk_get_parent ( tdiv_event ) ;
clk_set_rate ( tscaler , pclk / 2 ) ;
clk_set_rate ( tdiv_event , pclk / 2 ) ;
clk_set_parent ( tin_event , tdiv_event ) ;
clock_rate = clk_get_rate ( tin_event ) ;
clock_count_per_tick = clock_rate / HZ ;
time_event_device . cpumask = cpumask_of ( 0 ) ;
2013-01-12 11:50:05 +00:00
clockevents_config_and_register ( & time_event_device , clock_rate , 1 , - 1 ) ;
2011-03-12 08:02:07 +09:00
irq_number = timer_source . event_id + IRQ_TIMER0 ;
setup_irq ( irq_number , & s5p_clock_event_irq ) ;
}
2011-05-08 15:43:07 +01:00
static void __iomem * s5p_timer_reg ( void )
2011-03-12 08:02:07 +09:00
{
unsigned long offset = 0 ;
switch ( timer_source . source_id ) {
case S5P_PWM0 :
case S5P_PWM1 :
case S5P_PWM2 :
case S5P_PWM3 :
offset = ( timer_source . source_id * 0x0c ) + 0x14 ;
break ;
case S5P_PWM4 :
offset = 0x40 ;
break ;
default :
printk ( KERN_ERR " Invalid Timer %d \n " , timer_source . source_id ) ;
2011-05-08 15:43:07 +01:00
return NULL ;
2011-03-12 08:02:07 +09:00
}
2011-05-08 15:43:07 +01:00
return S3C_TIMERREG ( offset ) ;
}
2011-03-12 08:02:07 +09:00
/*
* Override the global weak sched_clock symbol with this
* local implementation which uses the clocksource to get some
* better resolution when scheduling the kernel . We accept that
* this wraps around for now , since it is just a relative time
* stamp . ( Inspired by U300 implementation . )
*/
2011-12-15 12:19:23 +01:00
static u32 notrace s5p_read_sched_clock ( void )
2011-03-12 08:02:07 +09:00
{
2011-05-08 15:43:07 +01:00
void __iomem * reg = s5p_timer_reg ( ) ;
2011-03-12 08:02:07 +09:00
2011-05-08 15:43:07 +01:00
if ( ! reg )
2011-03-12 08:02:07 +09:00
return 0 ;
2011-12-15 12:19:23 +01:00
return ~ __raw_readl ( reg ) ;
2011-03-12 08:02:07 +09:00
}
static void __init s5p_clocksource_init ( void )
{
unsigned long pclk ;
unsigned long clock_rate ;
pclk = clk_get_rate ( timerclk ) ;
clk_set_rate ( tdiv_source , pclk / 2 ) ;
clk_set_parent ( tin_source , tdiv_source ) ;
clock_rate = clk_get_rate ( tin_source ) ;
s5p_time_setup ( timer_source . source_id , TCNT_MAX ) ;
s5p_time_start ( timer_source . source_id , PERIODIC ) ;
2011-12-15 12:19:23 +01:00
setup_sched_clock ( s5p_read_sched_clock , 32 , clock_rate ) ;
2011-06-23 21:43:58 +09:00
2011-07-18 15:07:14 +09:00
if ( clocksource_mmio_init ( s5p_timer_reg ( ) , " s5p_clocksource_timer " ,
clock_rate , 250 , 32 , clocksource_mmio_readl_down ) )
panic ( " s5p_clocksource_timer: can't register clocksource \n " ) ;
2011-03-12 08:02:07 +09:00
}
static void __init s5p_timer_resources ( void )
{
unsigned long event_id = timer_source . event_id ;
unsigned long source_id = timer_source . source_id ;
2011-06-14 19:12:27 +09:00
char devname [ 15 ] ;
2011-03-12 08:02:07 +09:00
timerclk = clk_get ( NULL , " timers " ) ;
if ( IS_ERR ( timerclk ) )
panic ( " failed to get timers clock for timer " ) ;
clk_enable ( timerclk ) ;
2011-06-14 19:12:27 +09:00
sprintf ( devname , " s3c24xx-pwm.%lu " , event_id ) ;
s3c_device_timer [ event_id ] . id = event_id ;
s3c_device_timer [ event_id ] . dev . init_name = devname ;
2011-03-12 08:02:07 +09:00
tin_event = clk_get ( & s3c_device_timer [ event_id ] . dev , " pwm-tin " ) ;
if ( IS_ERR ( tin_event ) )
panic ( " failed to get pwm-tin clock for event timer " ) ;
tdiv_event = clk_get ( & s3c_device_timer [ event_id ] . dev , " pwm-tdiv " ) ;
if ( IS_ERR ( tdiv_event ) )
panic ( " failed to get pwm-tdiv clock for event timer " ) ;
clk_enable ( tin_event ) ;
2011-06-14 19:12:27 +09:00
sprintf ( devname , " s3c24xx-pwm.%lu " , source_id ) ;
s3c_device_timer [ source_id ] . id = source_id ;
s3c_device_timer [ source_id ] . dev . init_name = devname ;
2011-03-12 08:02:07 +09:00
tin_source = clk_get ( & s3c_device_timer [ source_id ] . dev , " pwm-tin " ) ;
if ( IS_ERR ( tin_source ) )
panic ( " failed to get pwm-tin clock for source timer " ) ;
tdiv_source = clk_get ( & s3c_device_timer [ source_id ] . dev , " pwm-tdiv " ) ;
if ( IS_ERR ( tdiv_source ) )
panic ( " failed to get pwm-tdiv clock for source timer " ) ;
clk_enable ( tin_source ) ;
}
2012-11-08 12:40:59 -07:00
void __init s5p_timer_init ( void )
2011-03-12 08:02:07 +09:00
{
s5p_timer_resources ( ) ;
s5p_clockevent_init ( ) ;
s5p_clocksource_init ( ) ;
}