aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-pnx4008/time.c
blob: 756228ddd035de276cb27fef771e55ec5b92f532 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * 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,
};