393 lines
9.5 KiB
C
393 lines
9.5 KiB
C
/* linux/arch/arm/mach-s3c64xx/time.c
|
|
*
|
|
* Copyright (C) 2006 Samsung Electronics
|
|
*
|
|
* 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>
|
|
#include <linux/irq.h>
|
|
#include <linux/err.h>
|
|
#include <linux/clk.h>
|
|
|
|
#include <asm/system.h>
|
|
#include <asm/leds.h>
|
|
#include <asm/mach-types.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/arch/map.h>
|
|
#include <asm/arch/regs-timer.h>
|
|
#include <asm/arch/regs-irq.h>
|
|
#include <asm/arch/regs-gpio.h>
|
|
#include <asm/mach/time.h>
|
|
|
|
#include <asm/plat-s3c24xx/clock.h>
|
|
#include <asm/plat-s3c24xx/cpu.h>
|
|
|
|
static unsigned long timer_startval;
|
|
static unsigned long timer_usec_ticks;
|
|
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
static unsigned long s3c_timer_cnt_per_tick;
|
|
static unsigned long previous_dyn_ticks;
|
|
static unsigned long last_dyn_ticks;
|
|
static unsigned long sync_timer_startval;
|
|
static unsigned long last_sync_timer_count;
|
|
static unsigned long last_tick_count;
|
|
#define JIFFIES_TO_HW_TICKS(nr_jiffies, timer_cnt_per_tick)\
|
|
((nr_jiffies) * timer_cnt_per_tick)
|
|
|
|
|
|
#endif
|
|
|
|
//#define T32_PROBE_TEST
|
|
|
|
#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 1MHz.
|
|
*
|
|
* shift of 14 and 15 are too low for the 12MHz, 16 seems to be ok
|
|
* for the current HZ value of 200 without producing overflows.
|
|
*
|
|
* Original patch by Dimitry Andric, updated by Ben Dooks
|
|
*/
|
|
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
|
|
static inline unsigned long s3c_sync_timer_read(void)
|
|
{
|
|
return __raw_readl(S3C_TCNTO(3));
|
|
}
|
|
|
|
static inline void s3c_init_sync_timer(void)
|
|
{
|
|
unsigned long tcon;
|
|
unsigned long tcfg1;
|
|
unsigned long tcfg0;
|
|
|
|
sync_timer_startval = 0xffffffff;
|
|
|
|
tcon = __raw_readl(S3C_TCON);
|
|
tcfg1 = __raw_readl(S3C_TCFG1);
|
|
tcfg0 = __raw_readl(S3C_TCFG0);
|
|
|
|
/* Sync timer setting */
|
|
tcfg1 &= ~S3C_TCFG1_MUX3_MASK;
|
|
tcfg1 |= S3C_TCFG1_MUX3_DIV1;
|
|
__raw_writel(tcfg1, S3C_TCFG1);
|
|
|
|
tcfg0 &= ~S3C_TCFG_PRESCALER1_MASK;
|
|
tcfg0 |= (PRESCALER) << S3C_TCFG_PRESCALER1_SHIFT;
|
|
|
|
__raw_writel(0xffffffff, S3C_TCNTB(3));
|
|
tcon &= ~(0xf<<16);
|
|
tcon |= S3C_TCON_T3RELOAD;
|
|
tcon |= S3C_TCON_T3MANUALUPD;
|
|
__raw_writel(tcon, S3C_TCON);
|
|
|
|
|
|
}
|
|
#endif
|
|
|
|
/* 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 s3c_gettimeoffset (void)
|
|
{
|
|
|
|
unsigned long tval;
|
|
unsigned long tdone;
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
tval = s3c_sync_timer_read();
|
|
if(last_tick_count >= tval)
|
|
tdone = last_tick_count - tval;
|
|
else
|
|
tdone = 0xffffffff - tval + last_tick_count + 1;
|
|
#else
|
|
tval = __raw_readl(S3C_TCNTO(4));
|
|
|
|
tdone = timer_startval - tval;
|
|
#endif
|
|
return timer_ticks_to_usec(tdone);
|
|
|
|
}
|
|
|
|
static inline irqreturn_t _s3c_timer_interrupt(int irq, void *dev_id)
|
|
{
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
unsigned long now,tmp;
|
|
now = s3c_sync_timer_read();
|
|
if(now <= last_tick_count) {
|
|
tmp = last_tick_count - now;
|
|
} else {
|
|
tmp = 0xffffffff - now + last_tick_count + 1;
|
|
}
|
|
while(tmp >= s3c_timer_cnt_per_tick) {
|
|
tmp -= s3c_timer_cnt_per_tick;
|
|
if(last_tick_count > s3c_timer_cnt_per_tick)
|
|
last_tick_count -= s3c_timer_cnt_per_tick;
|
|
else
|
|
last_tick_count = (0xffffffff - s3c_timer_cnt_per_tick + last_tick_count + 1);
|
|
timer_tick();
|
|
}
|
|
//printk("z=0x%x\n",__raw_readl(S3C_TCNTO(4)));
|
|
#else
|
|
timer_tick();
|
|
#endif
|
|
return IRQ_HANDLED;
|
|
}
|
|
/*
|
|
* IRQ handler for the timer
|
|
*/
|
|
static irqreturn_t
|
|
s3c_timer_interrupt(int irq, void *dev_id)
|
|
{
|
|
|
|
|
|
#ifdef T32_PROBE_TEST
|
|
s3c_gpio_setpin(S3C_GPN14, 1);
|
|
#endif
|
|
write_seqlock(&xtime_lock);
|
|
_s3c_timer_interrupt(irq, dev_id);
|
|
write_sequnlock(&xtime_lock);
|
|
|
|
__raw_writel((0x1f & __raw_readl(S3C_TINT_CSTAT)) | S3C_TINT_CSTAT_T4INT, S3C_TINT_CSTAT);
|
|
// __raw_writel((0x1f & __raw_readl(S3C_TINT_CSTAT)) | S3C_TINT_CSTAT_T4INTEN, S3C_TINT_CSTAT);
|
|
#ifdef T32_PROBE_TEST
|
|
s3c_gpio_setpin(S3C_GPN14, 0);
|
|
#endif
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct irqaction s3c_timer_irq = {
|
|
.name = "S3C Timer Tick",
|
|
.flags = IRQF_DISABLED | IRQF_TIMER,
|
|
.handler = s3c_timer_interrupt,
|
|
};
|
|
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
/*
|
|
* Programs the next timer interrupt needed. Called when dynamic tick is
|
|
* enabled, and to reprogram the ticks to skip from pm_idle. Note that
|
|
* we can keep the timer continuous, and don't need to set it to run in
|
|
* one-shot mode. This is because the timer will get reprogrammed again
|
|
* after next interrupt.
|
|
*/
|
|
void s3c_timer_reprogram(unsigned long next_tick)
|
|
{
|
|
unsigned long tcnt;
|
|
#ifdef T32_PROBE_TEST
|
|
s3c_gpio_setpin(S3C_GPN15, 1);
|
|
#endif
|
|
//printk("next_tick=%d\n",next_tick);
|
|
if(next_tick == 0)
|
|
next_tick = 1;
|
|
last_dyn_ticks = next_tick;
|
|
tcnt = JIFFIES_TO_HW_TICKS(next_tick, s3c_timer_cnt_per_tick);
|
|
//printk("tcnt=0x%x\n",tcnt);
|
|
__raw_writel((tcnt - 1), S3C_TCNTB(4));
|
|
|
|
|
|
#ifdef T32_PROBE_TEST
|
|
s3c_gpio_setpin(S3C_GPN15, 0);
|
|
#endif
|
|
|
|
}
|
|
|
|
static int s3c_timer_enable_dyn_tick(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int s3c_timer_disable_dyn_tick(void)
|
|
{
|
|
unsigned long tcnt;
|
|
|
|
last_dyn_ticks = 1;
|
|
tcnt = JIFFIES_TO_HW_TICKS(1, s3c_timer_cnt_per_tick);
|
|
|
|
__raw_writel((tcnt - 1), S3C_TCNTB(0));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t s3c_dyn_tick_handler(int irq, void *dev_id)
|
|
{
|
|
//printk("dyntick irq%d\n",irq);
|
|
return _s3c_timer_interrupt(irq, dev_id);
|
|
}
|
|
|
|
static struct dyn_tick_timer s3c_dyn_tick_timer = {
|
|
.enable = s3c_timer_enable_dyn_tick,
|
|
.disable = s3c_timer_disable_dyn_tick,
|
|
.reprogram = s3c_timer_reprogram,
|
|
.handler = s3c_dyn_tick_handler,
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* 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 s3c_timer_setup (void)
|
|
{
|
|
unsigned long tcon;
|
|
unsigned long tcnt;
|
|
unsigned long tcfg1;
|
|
unsigned long tcfg0;
|
|
unsigned long pclk;
|
|
unsigned long timerclock;
|
|
struct clk *clk;
|
|
|
|
#ifdef T32_PROBE_TEST
|
|
|
|
s3c_gpio_cfgpin(S3C_GPN14, S3C_GPN14_OUTP);
|
|
s3c_gpio_cfgpin(S3C_GPN15, S3C_GPN15_OUTP);
|
|
s3c_gpio_setpin(S3C_GPN14, 0);
|
|
s3c_gpio_setpin(S3C_GPN15, 0);
|
|
/* read the current timer configuration bits */
|
|
#endif
|
|
|
|
tcnt = 0xffffffff; /* default value for tcnt */
|
|
|
|
/* read the current timer configuration bits */
|
|
|
|
tcfg1 = __raw_readl(S3C_TCFG1);
|
|
tcfg0 = __raw_readl(S3C_TCFG0);
|
|
|
|
/* this is used as default if no other timer can be found */
|
|
|
|
clk = clk_get(NULL, "timers");
|
|
if (IS_ERR(clk))
|
|
panic("failed to get clock for system timer");
|
|
|
|
clk_enable(clk);
|
|
|
|
pclk = clk_get_rate(clk);
|
|
timerclock = (pclk / ((PRESCALER + 1)*DIVIDER));
|
|
|
|
/* configure clock tick */
|
|
|
|
timer_usec_ticks = timer_mask_usec_ticks(((PRESCALER + 1)*DIVIDER), pclk);
|
|
|
|
tcfg1 &= ~S3C_TCFG1_MUX4_MASK;
|
|
tcfg1 |= S3C_TCFG1_MUX4_DIV1;
|
|
|
|
tcfg0 &= ~S3C_TCFG_PRESCALER1_MASK;
|
|
tcfg0 |= (PRESCALER) << S3C_TCFG_PRESCALER1_SHIFT;
|
|
|
|
tcnt = timerclock / HZ;
|
|
/* timers reload after counting zero, so reduce the count by 1 */
|
|
|
|
tcnt--;
|
|
|
|
__raw_writel(tcfg1, S3C_TCFG1);
|
|
__raw_writel(tcfg0, S3C_TCFG0);
|
|
|
|
timer_startval = tcnt;
|
|
__raw_writel(tcnt, S3C_TCNTB(4));
|
|
|
|
/* ensure timer is stopped... */
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
s3c_timer_cnt_per_tick = (timerclock/HZ);
|
|
previous_dyn_ticks = 1;
|
|
last_dyn_ticks = 1;
|
|
last_sync_timer_count = 0xffffffff;
|
|
last_tick_count = 0xffffffff;
|
|
s3c_init_sync_timer();
|
|
#endif
|
|
tcon = __raw_readl(S3C_TCON);
|
|
|
|
tcon &= ~(7<<20);
|
|
tcon |= S3C_TCON_T4RELOAD;
|
|
tcon |= S3C_TCON_T4MANUALUPD;
|
|
|
|
__raw_writel(tcon, S3C_TCON);
|
|
__raw_writel(tcnt, S3C_TCNTB(4));
|
|
//__raw_writel(tcnt, S3C_TCMPB(4));
|
|
|
|
printk("timer tcon=%08lx, tcnt %04lx, tcfg %08lx,%08lx, usec %08lx\n",
|
|
tcon, tcnt, tcfg0, tcfg1, timer_usec_ticks);
|
|
|
|
/* start the timer running */
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
tcon |= S3C_TCON_T4START|S3C_TCON_T3START;
|
|
tcon &= ~(S3C_TCON_T4MANUALUPD|S3C_TCON_T3MANUALUPD);
|
|
#else
|
|
tcon |= S3C_TCON_T4START;
|
|
tcon &= ~S3C_TCON_T4MANUALUPD;
|
|
#endif
|
|
__raw_writel(tcon, S3C_TCON);
|
|
|
|
/* timer4 interrupt enable */
|
|
__raw_writel(__raw_readl(S3C_TINT_CSTAT) | S3C_TINT_CSTAT_T4INTEN, S3C_TINT_CSTAT);
|
|
|
|
}
|
|
|
|
static void __init s3c_timer_init (void)
|
|
{
|
|
s3c_timer_setup();
|
|
setup_irq(IRQ_TIMER4, &s3c_timer_irq);
|
|
}
|
|
|
|
struct sys_timer s3c_timer = {
|
|
.init = s3c_timer_init,
|
|
.offset = s3c_gettimeoffset,
|
|
.resume = s3c_timer_setup,
|
|
#ifdef CONFIG_NO_IDLE_HZ
|
|
.dyn_tick = &s3c_dyn_tick_timer,
|
|
#endif
|
|
};
|