summaryrefslogtreecommitdiffstats
path: root/arch/i386/kernel/timers/timer_cyclone.c
blob: f6f1206a11bb7ed9b2afc2c2714ef53b51157f57 (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
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*	Cyclone-timer: 
 *		This code implements timer_ops for the cyclone counter found
 *		on IBM x440, x360, and other Summit based systems.
 *
 *	Copyright (C) 2002 IBM, John Stultz (johnstul@us.ibm.com)
 */


#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/timex.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/jiffies.h>

#include <asm/timer.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/fixmap.h>
#include "io_ports.h"

extern spinlock_t i8253_lock;

/* Number of usecs that the last interrupt was delayed */
static int delay_at_last_interrupt;

#define CYCLONE_CBAR_ADDR 0xFEB00CD0
#define CYCLONE_PMCC_OFFSET 0x51A0
#define CYCLONE_MPMC_OFFSET 0x51D0
#define CYCLONE_MPCS_OFFSET 0x51A8
#define CYCLONE_TIMER_FREQ 100000000
#define CYCLONE_TIMER_MASK (((u64)1<<40)-1) /* 40 bit mask */
int use_cyclone = 0;

static u32* volatile cyclone_timer;	/* Cyclone MPMC0 register */
static u32 last_cyclone_low;
static u32 last_cyclone_high;
static unsigned long long monotonic_base;
static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED;

/* helper macro to atomically read both cyclone counter registers */
#define read_cyclone_counter(low,high) \
	do{ \
		high = cyclone_timer[1]; low = cyclone_timer[0]; \
	} while (high != cyclone_timer[1]);


static void mark_offset_cyclone(void)
{
	unsigned long lost, delay;
	unsigned long delta = last_cyclone_low;
	int count;
	unsigned long long this_offset, last_offset;

	write_seqlock(&monotonic_lock);
	last_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low;
	
	spin_lock(&i8253_lock);
	read_cyclone_counter(last_cyclone_low,last_cyclone_high);

	/* read values for delay_at_last_interrupt */
	outb_p(0x00, 0x43);     /* latch the count ASAP */

	count = inb_p(0x40);    /* read the latched count */
	count |= inb(0x40) << 8;

	/*
	 * VIA686a test code... reset the latch if count > max + 1
	 * from timer_pit.c - cjb
	 */
	if (count > LATCH) {
		outb_p(0x34, PIT_MODE);
		outb_p(LATCH & 0xff, PIT_CH0);
		outb(LATCH >> 8, PIT_CH0);
		count = LATCH - 1;
	}
	spin_unlock(&i8253_lock);

	/* lost tick compensation */
	delta = last_cyclone_low - delta;	
	delta /= (CYCLONE_TIMER_FREQ/1000000);
	delta += delay_at_last_interrupt;
	lost = delta/(1000000/HZ);
	delay = delta%(1000000/HZ);
	if (lost >= 2)
		jiffies_64 += lost-1;
	
	/* update the monotonic base value */
	this_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low;
	monotonic_base += (this_offset - last_offset) & CYCLONE_TIMER_MASK;
	write_sequnlock(&monotonic_lock);

	/* calculate delay_at_last_interrupt */
	count = ((LATCH-1) - count) * TICK_SIZE;
	delay_at_last_interrupt = (count + LATCH/2) / LATCH;


	/* catch corner case where tick rollover occured 
	 * between cyclone and pit reads (as noted when 
	 * usec delta is > 90% # of usecs/tick)
	 */
	if (lost && abs(delay - delay_at_last_interrupt) > (900000/HZ))
		jiffies_64++;
}

static unsigned long get_offset_cyclone(void)
{
	u32 offset;

	if(!cyclone_timer)
		return delay_at_last_interrupt;

	/* Read the cyclone timer */
	offset = cyclone_timer[0];

	/* .. relative to previous jiffy */
	offset = offset - last_cyclone_low;

	/* convert cyclone ticks to microseconds */	
	/* XXX slow, can we speed this up? */
	offset = offset/(CYCLONE_TIMER_FREQ/1000000);

	/* our adjusted time offset in microseconds */
	return delay_at_last_interrupt + offset;
}

static unsigned long long monotonic_clock_cyclone(void)
{
	u32 now_low, now_high;
	unsigned long long last_offset, this_offset, base;
	unsigned long long ret;
	unsigned seq;

	/* atomically read monotonic base & last_offset */
	do {
		seq = read_seqbegin(&monotonic_lock);
		last_offset = ((unsigned long long)last_cyclone_high<<32)|last_cyclone_low;
		base = monotonic_base;
	} while (read_seqretry(&monotonic_lock, seq));


	/* Read the cyclone counter */
	read_cyclone_counter(now_low,now_high);
	this_offset = ((unsigned long long)now_high<<32)|now_low;

	/* convert to nanoseconds */
	ret = base + ((this_offset - last_offset)&CYCLONE_TIMER_MASK);
	return ret * (1000000000 / CYCLONE_TIMER_FREQ);
}

static int __init init_cyclone(char* override)
{
	u32* reg;	
	u32 base;		/* saved cyclone base address */
	u32 pageaddr;	/* page that contains cyclone_timer register */
	u32 offset;		/* offset from pageaddr to cyclone_timer register */
	int i;
	
	/* check clock override */
	if (override[0] && strncmp(override,"cyclone",7))
			return -ENODEV;

	/*make sure we're on a summit box*/
	if(!use_cyclone) return -ENODEV; 
	
	printk(KERN_INFO "Summit chipset: Starting Cyclone Counter.\n");

	/* find base address */
	pageaddr = (CYCLONE_CBAR_ADDR)&PAGE_MASK;
	offset = (CYCLONE_CBAR_ADDR)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!reg){
		printk(KERN_ERR "Summit chipset: Could not find valid CBAR register.\n");
		return -ENODEV;
	}
	base = *reg;	
	if(!base){
		printk(KERN_ERR "Summit chipset: Could not find valid CBAR value.\n");
		return -ENODEV;
	}
	
	/* setup PMCC */
	pageaddr = (base + CYCLONE_PMCC_OFFSET)&PAGE_MASK;
	offset = (base + CYCLONE_PMCC_OFFSET)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!reg){
		printk(KERN_ERR "Summit chipset: Could not find valid PMCC register.\n");
		return -ENODEV;
	}
	reg[0] = 0x00000001;

	/* setup MPCS */
	pageaddr = (base + CYCLONE_MPCS_OFFSET)&PAGE_MASK;
	offset = (base + CYCLONE_MPCS_OFFSET)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	reg = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!reg){
		printk(KERN_ERR "Summit chipset: Could not find valid MPCS register.\n");
		return -ENODEV;
	}
	reg[0] = 0x00000001;

	/* map in cyclone_timer */
	pageaddr = (base + CYCLONE_MPMC_OFFSET)&PAGE_MASK;
	offset = (base + CYCLONE_MPMC_OFFSET)&(~PAGE_MASK);
	set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr);
	cyclone_timer = (u32*)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);
	if(!cyclone_timer){
		printk(KERN_ERR "Summit chipset: Could not find valid MPMC register.\n");
		return -ENODEV;
	}

	/*quick test to make sure its ticking*/
	for(i=0; i<3; i++){
		u32 old = cyclone_timer[0];
		int stall = 100;
		while(stall--) barrier();
		if(cyclone_timer[0] == old){
			printk(KERN_ERR "Summit chipset: Counter not counting! DISABLED\n");
			cyclone_timer = 0;
			return -ENODEV;
		}
	}

	init_cpu_khz();

	/* Everything looks good! */
	return 0;
}


static void delay_cyclone(unsigned long loops)
{
	unsigned long bclock, now;
	if(!cyclone_timer)
		return;
	bclock = cyclone_timer[0];
	do {
		rep_nop();
		now = cyclone_timer[0];
	} while ((now-bclock) < loops);
}
/************************************************************/

/* cyclone timer_opts struct */
static struct timer_opts timer_cyclone = {
	.name = "cyclone",
	.mark_offset = mark_offset_cyclone, 
	.get_offset = get_offset_cyclone,
	.monotonic_clock =	monotonic_clock_cyclone,
	.delay = delay_cyclone,
};

struct init_timer_opts __initdata timer_cyclone_init = {
	.init = init_cyclone,
	.opts = &timer_cyclone,
};
OpenPOWER on IntegriCloud