/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 };