78818e477b
Patch from Vitaly Wool This patch adds basic chip support for PNX4008 ARM platform. It's basically the same as the previous one, but with the rmk's comments taken into account. Signed-off-by: Vitaly Wool <vwool@ru.mvista.com> Signed-off-by: Dmitry Pervushin <dpervushin@gmail.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
142 lines
3.6 KiB
C
142 lines
3.6 KiB
C
/*
|
|
* 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 <asm/system.h>
|
|
#include <asm/hardware.h>
|
|
#include <asm/io.h>
|
|
#include <asm/leds.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/mach/irq.h>
|
|
#include <asm/mach/time.h>
|
|
|
|
#include <linux/time.h>
|
|
#include <linux/timex.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 = SA_INTERRUPT | SA_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,
|
|
};
|
|
|