aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ubicom32/files/arch/ubicom32/kernel/time.c
blob: 4a99284bd0908a75f88d47cfc1a8a25096fd3ac7 (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
/*
 * arch/ubicom32/kernel/time.c
 *	Initialize the timer list and start the appropriate timers.
 *
 * (C) Copyright 2009, Ubicom, Inc.
 * Copyright (C) 1991, 1992, 1995  Linus Torvalds
 *
 * This file is part of the Ubicom32 Linux Kernel Port.
 *
 * The Ubicom32 Linux Kernel Port is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * The Ubicom32 Linux Kernel Port is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 * the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Ubicom32 Linux Kernel Port.  If not,
 * see <http://www.gnu.org/licenses/>.
 *
 * Ubicom32 implementation derived from (with many thanks):
 *   arch/m68knommu
 *   arch/blackfin
 *   arch/parisc
 */

#include <linux/profile.h>
#include <linux/smp.h>
#include <asm/ip5000.h>
#include <asm/machdep.h>

/*
 * A bitmap of the timers on the processor indicates
 * that the timer is free or in-use.
 */
static unsigned int timers;

/*
 * timer_set()
 *	Init the specified compare register to go off <n> cycles from now.
 */
void timer_set(int timervector, unsigned int cycles)
{
	int idx = UBICOM32_VECTOR_TO_TIMER_INDEX(timervector);
	UBICOM32_IO_TIMER->syscom[idx] =
			UBICOM32_IO_TIMER->sysval + cycles;
	ldsr_enable_vector(timervector);
}

/*
 * timer_reset()
 *	Set/reset the timer to go off again.
 *
 * Because sysval is a continuous timer, this function is able
 * to ensure that we do not have clock sku by using the previous
 * value in syscom to set the next value for syscom.
 *
 * Returns the number of ticks that transpired since the last event.
 */
int timer_reset(int timervector, unsigned int cycles)
{
	/*
	 * Reset the timer in the LDSR thread to go off appropriately.
	 *
	 * Use the previous value of the timer to calculate the new stop
	 * time.  This allows us to account for it taking an
	 * indeterminate amount of time to get here.
	 */
	const int timer_index = UBICOM32_VECTOR_TO_TIMER_INDEX(timervector);
	unsigned int prev = UBICOM32_IO_TIMER->syscom[timer_index];
	unsigned int next = prev + cycles;
	int scratchpad3;
	int diff;
	int ticks = 1;

	/*
	 * If the difference is negative, we have missed at least one
	 * timer tick.
	 *
	 * TODO: Decide if we want to "ignore" time (as done below) or
	 * if we want to process time (unevenly) by calling timer_tick()
	 * lost_ticks times.
	 */
	while (1) {
		/*
		 * Set our future time first.
		 */
		UBICOM32_IO_TIMER->syscom[timer_index] = next;

		/*
		 * Then check if we are really set time in the futrue.
		 */
		diff = (int)next - (int)UBICOM32_IO_TIMER->sysval;
		if (diff >= 0) {
			break;
		}

		/*
		 * Oops, we are too slow. Playing catch up.
		 *
		 * If the debugger is connected the there is a good
		 * chance that we lost time because we were in a
		 * break-point, so in this case we do not print out
		 * diagnostics.
		 */
		asm volatile ("move.4 %0, scratchpad3"
			      : "=r" (scratchpad3));
		if ((scratchpad3 & 0x1) == 0) {
			/*
			 * No debugger attached, print to the console
			 */
			printk(KERN_EMERG "diff: %d, timer has lost %u "
			       "ticks [rounded up]\n",
			       -diff,
			       (unsigned int)((-diff + cycles - 1) / cycles));
		}

		do {
			next += cycles;
			diff = (int)next - (int)UBICOM32_IO_TIMER->sysval;
			ticks++;
		} while (diff < 0);
	}
	return ticks;
}

/*
 * sched_clock()
 *	Returns current time in nano-second units.
 *
 * Notes:
 * 1) This is an override for the weak alias in
 * kernel/sched_clock.c.
 * 2) Do not use xtime_lock as this function is
 * sometimes called with xtime_lock held.
 * 3) We use a retry algorithm to ensure that
 * we get a consistent value.
 * 4) sched_clock must be overwritten if IRQ tracing
 * is enabled because the default implementation uses
 * the xtime_lock sequence while holding xtime_lock.
 */
unsigned long long sched_clock(void)
{
	unsigned long long my_jiffies;
	unsigned long jiffies_top;
	unsigned long jiffies_bottom;

	do {
		jiffies_top = jiffies_64 >> 32;
		jiffies_bottom = jiffies_64 & 0xffffffff;
	} while (unlikely(jiffies_top != (unsigned long)(jiffies_64 >> 32)));

	my_jiffies = ((unsigned long long)jiffies_top << 32) | (jiffies_bottom);
	return (my_jiffies - INITIAL_JIFFIES) * (NSEC_PER_SEC / HZ);
}

/*
 * timer_free()
 *	Free a hardware timer.
 */
void timer_free(int interrupt)
{
	unsigned int bit = interrupt - TIMER_INT(0);

	/*
	 * The timer had not been allocated.
	 */
	BUG_ON(timers & (1 << bit));
	timers |= (1 << bit);
}

/*
 * timer_alloc()
 *	Allocate a hardware timer.
 */
int timer_alloc(void)
{
	unsigned int bit = find_first_bit((unsigned long *)&timers, 32);
	if (!bit) {
		printk(KERN_WARNING "no more free timers\n");
		return -1;
	}

	timers &= ~(1 << bit);
	return bit + TIMER_INT(0);
}

/*
 * time_init()
 *	Time init function.
 */
void time_init(void)
{
	/*
	 * Find the processor node and determine what timers are
	 * available for us.
	 */
	timers = processor_timers();
	if (timers == 0) {
		printk(KERN_WARNING "no timers are available for Linux\n");
		return;
	}

#ifdef CONFIG_GENERIC_CLOCKEVENTS
	timer_device_init();
#else
	timer_tick_init();
#endif
}