aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-nomadik/timer.c
blob: d1738e7061d4b3e03cdf7f7b5ea71db3c4589cb9 (plain) (blame)
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
 *  linux/arch/arm/mach-nomadik/timer.c
 *
 * Copyright (C) 2008 STMicroelectronics
 * Copyright (C) 2009 Alessandro Rubini, somewhat based on at91sam926x
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 */
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/clockchips.h>
#include <linux/jiffies.h>
#include <asm/mach/time.h>
#include <mach/mtu.h>

#define TIMER_CTRL	0x80	/* No divisor */
#define TIMER_PERIODIC	0x40
#define TIMER_SZ32BIT	0x02

/* Initial value for SRC control register: all timers use MXTAL/8 source */
#define SRC_CR_INIT_MASK	0x00007fff
#define SRC_CR_INIT_VAL		0x2aaa8000

static u32	nmdk_count;		/* accumulated count */
static u32	nmdk_cycle;		/* write-once */
static __iomem void *mtu_base;

/*
 * clocksource: the MTU device is a decrementing counters, so we negate
 * the value being read.
 */
static cycle_t nmdk_read_timer(struct clocksource *cs)
{
	u32 count = readl(mtu_base + MTU_VAL(0));
	return nmdk_count + nmdk_cycle - count;

}

static struct clocksource nmdk_clksrc = {
	.name		= "mtu_0",
	.rating		= 120,
	.read		= nmdk_read_timer,
	.shift		= 20,
	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
};

/*
 * Clockevent device: currently only periodic mode is supported
 */
static void nmdk_clkevt_mode(enum clock_event_mode mode,
			     struct clock_event_device *dev)
{
	unsigned long flags;

	switch (mode) {
	case CLOCK_EVT_MODE_PERIODIC:
		/* enable interrupts -- and count current value? */
		raw_local_irq_save(flags);
		writel(readl(mtu_base + MTU_IMSC) | 1, mtu_base + MTU_IMSC);
		raw_local_irq_restore(flags);
		break;
	case CLOCK_EVT_MODE_ONESHOT:
		BUG(); /* Not supported, yet */
		/* FALLTHROUGH */
	case CLOCK_EVT_MODE_SHUTDOWN:
	case CLOCK_EVT_MODE_UNUSED:
		/* disable irq */
		raw_local_irq_save(flags);
		writel(readl(mtu_base + MTU_IMSC) & ~1, mtu_base + MTU_IMSC);
		raw_local_irq_restore(flags);
		break;
	case CLOCK_EVT_MODE_RESUME:
		break;
	}
}

static struct clock_event_device nmdk_clkevt = {
	.name		= "mtu_0",
	.features	= CLOCK_EVT_FEAT_PERIODIC,
	.shift		= 32,
	.rating		= 100,
	.set_mode	= nmdk_clkevt_mode,
};

/*
 * IRQ Handler for the timer 0 of the MTU block. The irq is not shared
 * as we are the only users of mtu0 by now.
 */
static irqreturn_t nmdk_timer_interrupt(int irq, void *dev_id)
{
	/* ack: "interrupt clear register" */
	writel( 1 << 0, mtu_base + MTU_ICR);

	/* we can't count lost ticks, unfortunately */
	nmdk_count += nmdk_cycle;
	nmdk_clkevt.event_handler(&nmdk_clkevt);

	return IRQ_HANDLED;
}

/*
 * Set up timer interrupt, and return the current time in seconds.
 */
static struct irqaction nmdk_timer_irq = {
	.name		= "Nomadik Timer Tick",
	.flags		= IRQF_DISABLED | IRQF_TIMER,
	.handler	= nmdk_timer_interrupt,
};

static void nmdk_timer_reset(void)
{
	u32 cr;

	writel(0, mtu_base + MTU_CR(0)); /* off */

	/* configure load and background-load, and fire it up */
	writel(nmdk_cycle, mtu_base + MTU_LR(0));
	writel(nmdk_cycle, mtu_base + MTU_BGLR(0));
	cr = MTU_CRn_PERIODIC | MTU_CRn_PRESCALE_1 | MTU_CRn_32BITS;
	writel(cr, mtu_base + MTU_CR(0));
	writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(0));
}

static void __init nmdk_timer_init(void)
{
	u32 src_cr;
	unsigned long rate;
	int bits;

	rate = CLOCK_TICK_RATE; /* 2.4MHz */
	nmdk_cycle = (rate + HZ/2) / HZ;

	/* Configure timer sources in "system reset controller" ctrl reg */
	src_cr = readl(io_p2v(NOMADIK_SRC_BASE));
	src_cr &= SRC_CR_INIT_MASK;
	src_cr |= SRC_CR_INIT_VAL;
	writel(src_cr, io_p2v(NOMADIK_SRC_BASE));

	/* Save global pointer to mtu, used by functions above */
	mtu_base = io_p2v(NOMADIK_MTU0_BASE);

	/* Init the timer and register clocksource */
	nmdk_timer_reset();

	nmdk_clksrc.mult = clocksource_hz2mult(rate, nmdk_clksrc.shift);
	bits =  8*sizeof(nmdk_count);
	nmdk_clksrc.mask = CLOCKSOURCE_MASK(bits);

	clocksource_register(&nmdk_clksrc);

	/* Register irq and clockevents */
	setup_irq(IRQ_MTU0, &nmdk_timer_irq);
	nmdk_clkevt.mult = div_sc(rate, NSEC_PER_SEC, nmdk_clkevt.shift);
	nmdk_clkevt.cpumask = cpumask_of(0);
	clockevents_register_device(&nmdk_clkevt);
}

struct sys_timer nomadik_timer = {
	.init		= nmdk_timer_init,
};