| /* |
| * arch/arm/mach-pnx4008/time.c |
| * |
| * PNX4008 Timers |
| * |
| * Authors: Vitaly Wool, Dmitry Chigirev, Grigory Tolstolytkin <source@mvista.com> |
| * |
| * 2005 (c) MontaVista Software, Inc. This file is licensed under |
| * the terms of the GNU General Public License version 2. This program |
| * is licensed "as is" without any warranty of any kind, whether express |
| * or implied. |
| */ |
| |
| #include <linux/config.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/sched.h> |
| #include <linux/spinlock.h> |
| #include <linux/module.h> |
| #include <linux/kallsyms.h> |
| #include <linux/time.h> |
| #include <linux/timex.h> |
| #include <linux/irq.h> |
| |
| #include <asm/system.h> |
| #include <asm/hardware.h> |
| #include <asm/io.h> |
| #include <asm/leds.h> |
| #include <asm/mach/time.h> |
| #include <asm/errno.h> |
| |
| /*! Note: all timers are UPCOUNTING */ |
| |
| /*! |
| * Returns number of us since last clock interrupt. Note that interrupts |
| * will have been disabled by do_gettimeoffset() |
| */ |
| static unsigned long pnx4008_gettimeoffset(void) |
| { |
| u32 ticks_to_match = |
| __raw_readl(HSTIM_MATCH0) - __raw_readl(HSTIM_COUNTER); |
| u32 elapsed = LATCH - ticks_to_match; |
| return (elapsed * (tick_nsec / 1000)) / LATCH; |
| } |
| |
| /*! |
| * IRQ handler for the timer |
| */ |
| static irqreturn_t pnx4008_timer_interrupt(int irq, void *dev_id, |
| struct pt_regs *regs) |
| { |
| if (__raw_readl(HSTIM_INT) & MATCH0_INT) { |
| |
| write_seqlock(&xtime_lock); |
| |
| do { |
| timer_tick(regs); |
| |
| /* |
| * this algorithm takes care of possible delay |
| * for this interrupt handling longer than a normal |
| * timer period |
| */ |
| __raw_writel(__raw_readl(HSTIM_MATCH0) + LATCH, |
| HSTIM_MATCH0); |
| __raw_writel(MATCH0_INT, HSTIM_INT); /* clear interrupt */ |
| |
| /* |
| * The goal is to keep incrementing HSTIM_MATCH0 |
| * register until HSTIM_MATCH0 indicates time after |
| * what HSTIM_COUNTER indicates. |
| */ |
| } while ((signed) |
| (__raw_readl(HSTIM_MATCH0) - |
| __raw_readl(HSTIM_COUNTER)) < 0); |
| |
| write_sequnlock(&xtime_lock); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static struct irqaction pnx4008_timer_irq = { |
| .name = "PNX4008 Tick Timer", |
| .flags = IRQF_DISABLED | IRQF_TIMER, |
| .handler = pnx4008_timer_interrupt |
| }; |
| |
| /*! |
| * Set up timer and timer interrupt. |
| */ |
| static __init void pnx4008_setup_timer(void) |
| { |
| __raw_writel(RESET_COUNT, MSTIM_CTRL); |
| while (__raw_readl(MSTIM_COUNTER)) ; /* wait for reset to complete. 100% guarantee event */ |
| __raw_writel(0, MSTIM_CTRL); /* stop the timer */ |
| __raw_writel(0, MSTIM_MCTRL); |
| |
| __raw_writel(RESET_COUNT, HSTIM_CTRL); |
| while (__raw_readl(HSTIM_COUNTER)) ; /* wait for reset to complete. 100% guarantee event */ |
| __raw_writel(0, HSTIM_CTRL); |
| __raw_writel(0, HSTIM_MCTRL); |
| __raw_writel(0, HSTIM_CCR); |
| __raw_writel(12, HSTIM_PMATCH); /* scale down to 1 MHZ */ |
| __raw_writel(LATCH, HSTIM_MATCH0); |
| __raw_writel(MR0_INT, HSTIM_MCTRL); |
| |
| setup_irq(HSTIMER_INT, &pnx4008_timer_irq); |
| |
| __raw_writel(COUNT_ENAB | DEBUG_EN, HSTIM_CTRL); /*start timer, stop when JTAG active */ |
| } |
| |
| /* Timer Clock Control in PM register */ |
| #define TIMCLK_CTRL_REG IO_ADDRESS((PNX4008_PWRMAN_BASE + 0xBC)) |
| #define WATCHDOG_CLK_EN 1 |
| #define TIMER_CLK_EN 2 /* HS and MS timers? */ |
| |
| static u32 timclk_ctrl_reg_save; |
| |
| void pnx4008_timer_suspend(void) |
| { |
| timclk_ctrl_reg_save = __raw_readl(TIMCLK_CTRL_REG); |
| __raw_writel(0, TIMCLK_CTRL_REG); /* disable timers */ |
| } |
| |
| void pnx4008_timer_resume(void) |
| { |
| __raw_writel(timclk_ctrl_reg_save, TIMCLK_CTRL_REG); /* enable timers */ |
| } |
| |
| struct sys_timer pnx4008_timer = { |
| .init = pnx4008_setup_timer, |
| .offset = pnx4008_gettimeoffset, |
| .suspend = pnx4008_timer_suspend, |
| .resume = pnx4008_timer_resume, |
| }; |
| |