agtimer.c revision 1.1
1/* $OpenBSD: agtimer.c,v 1.1 2016/12/17 23:38:33 patrick Exp $ */
2/*
3 * Copyright (c) 2011 Dale Rahn <drahn@openbsd.org>
4 * Copyright (c) 2013 Patrick Wildt <patrick@blueri.se>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/systm.h>
21#include <sys/queue.h>
22#include <sys/malloc.h>
23#include <sys/device.h>
24#include <sys/kernel.h>
25#include <sys/timetc.h>
26#include <sys/evcount.h>
27
28#include <machine/intr.h>
29#include <machine/bus.h>
30#include <machine/fdt.h>
31
32#include <dev/ofw/fdt.h>
33#include <dev/ofw/openfirm.h>
34
35/* registers */
36#define GTIMER_CNTP_CTL_ENABLE		(1 << 0)
37#define GTIMER_CNTP_CTL_IMASK		(1 << 1)
38#define GTIMER_CNTP_CTL_ISTATUS		(1 << 2)
39
40#define TIMER_FREQUENCY		24 * 1000 * 1000 /* ARM core clock */
41int32_t agtimer_frequency = TIMER_FREQUENCY;
42
43u_int agtimer_get_timecount(struct timecounter *);
44
45static struct timecounter agtimer_timecounter = {
46	agtimer_get_timecount, NULL, 0x7fffffff, 0, "agtimer", 0, NULL
47};
48
49#define MAX_ARM_CPUS	8
50
51struct agtimer_pcpu_softc {
52	uint64_t 		pc_nexttickevent;
53	uint64_t 		pc_nextstatevent;
54	u_int32_t		pc_ticks_err_sum;
55};
56
57struct agtimer_softc {
58	struct device		sc_dev;
59	int			sc_node;
60
61	struct agtimer_pcpu_softc sc_pstat[MAX_ARM_CPUS];
62
63	u_int32_t		sc_ticks_err_cnt;
64	u_int32_t		sc_ticks_per_second;
65	u_int32_t		sc_ticks_per_intr;
66	u_int32_t		sc_statvar;
67	u_int32_t		sc_statmin;
68
69#ifdef AMPTIMER_DEBUG
70	struct evcount		sc_clk_count;
71	struct evcount		sc_stat_count;
72#endif
73};
74
75int		agtimer_match(struct device *, void *, void *);
76void		agtimer_attach(struct device *, struct device *, void *);
77uint64_t	agtimer_readcnt64(void);
78int		agtimer_intr(void *);
79void		agtimer_cpu_initclocks(void);
80void		agtimer_delay(u_int);
81void		agtimer_setstatclockrate(int stathz);
82void		agtimer_set_clockrate(int32_t new_frequency);
83void		agtimer_startclock(void);
84
85struct cfattach agtimer_ca = {
86	sizeof (struct agtimer_softc), agtimer_match, agtimer_attach
87};
88
89struct cfdriver agtimer_cd = {
90	NULL, "agtimer", DV_DULL
91};
92
93uint64_t
94agtimer_readcnt64(void)
95{
96	uint64_t val;
97
98	__asm volatile("MRS %x0, CNTPCT_EL0" : "=r" (val));
99
100	return (val);
101}
102
103static inline int
104agtimer_get_ctrl(void)
105{
106	uint32_t val;
107
108	__asm volatile("MRS %x0, CNTP_CTL_EL0" : "=r" (val));
109
110	return (val);
111}
112
113static inline int
114agtimer_set_ctrl(uint32_t val)
115{
116	__asm volatile("MSR CNTP_CTL_EL0, %x0" : "=r" (val));
117
118	//cpu_drain_writebuf();
119	//isb();
120
121	return (0);
122}
123
124static inline int
125agtimer_set_tval(uint32_t val)
126{
127	__asm volatile("MSR CNTP_TVAL_EL0, %x0" : : [val] "r" (val));
128
129	//cpu_drain_writebuf();
130	//isb();
131
132	return (0);
133}
134
135int
136agtimer_match(struct device *parent, void *cfdata, void *aux)
137{
138	struct fdt_attach_args *faa = (struct fdt_attach_args *)aux;
139
140	return OF_is_compatible(faa->fa_node, "arm,armv8-timer");
141}
142
143void
144agtimer_attach(struct device *parent, struct device *self, void *aux)
145{
146	struct agtimer_softc *sc = (struct agtimer_softc *)self;
147	struct fdt_attach_args *faa = aux;
148
149	sc->sc_node = faa->fa_node;
150
151	agtimer_frequency =
152	    OF_getpropint(sc->sc_node, "clock-frequency", agtimer_frequency);
153	sc->sc_ticks_per_second = agtimer_frequency;
154
155	printf(": tick rate %d KHz\n", sc->sc_ticks_per_second /1000);
156
157	/* XXX: disable user access */
158
159#ifdef AMPTIMER_DEBUG
160	evcount_attach(&sc->sc_clk_count, "clock", NULL);
161	evcount_attach(&sc->sc_stat_count, "stat", NULL);
162#endif
163
164	/*
165	 * private timer and interrupts not enabled until
166	 * timer configures
167	 */
168
169	arm_clock_register(agtimer_cpu_initclocks, agtimer_delay,
170	    agtimer_setstatclockrate, agtimer_startclock);
171
172	agtimer_timecounter.tc_frequency = sc->sc_ticks_per_second;
173	agtimer_timecounter.tc_priv = sc;
174
175	tc_init(&agtimer_timecounter);
176}
177
178u_int
179agtimer_get_timecount(struct timecounter *tc)
180{
181	return agtimer_readcnt64();
182}
183
184int
185agtimer_intr(void *frame)
186{
187	struct agtimer_softc	*sc = agtimer_cd.cd_devs[0];
188	struct agtimer_pcpu_softc *pc = &sc->sc_pstat[CPU_INFO_UNIT(curcpu())];
189	uint64_t		 now;
190	uint64_t		 nextevent;
191	uint32_t		 r;
192#if defined(USE_GTIMER_CMP)
193	int			 skip = 1;
194#else
195	int64_t			 delay;
196#endif
197	int			 rc = 0;
198
199	/*
200	 * DSR - I know that the tick timer is 64 bits, but the following
201	 * code deals with rollover, so there is no point in dealing
202	 * with the 64 bit math, just let the 32 bit rollover
203	 * do the right thing
204	 */
205
206	now = agtimer_readcnt64();
207
208	while (pc->pc_nexttickevent <= now) {
209		pc->pc_nexttickevent += sc->sc_ticks_per_intr;
210		pc->pc_ticks_err_sum += sc->sc_ticks_err_cnt;
211
212		/* looping a few times is faster than divide */
213		while (pc->pc_ticks_err_sum > hz) {
214			pc->pc_nexttickevent += 1;
215			pc->pc_ticks_err_sum -= hz;
216		}
217
218#ifdef AMPTIMER_DEBUG
219		sc->sc_clk_count.ec_count++;
220#endif
221		rc = 1;
222		hardclock(frame);
223	}
224	while (pc->pc_nextstatevent <= now) {
225		do {
226			r = random() & (sc->sc_statvar -1);
227		} while (r == 0); /* random == 0 not allowed */
228		pc->pc_nextstatevent += sc->sc_statmin + r;
229
230		/* XXX - correct nextstatevent? */
231#ifdef AMPTIMER_DEBUG
232		sc->sc_stat_count.ec_count++;
233#endif
234		rc = 1;
235		statclock(frame);
236	}
237
238	if (pc->pc_nexttickevent < pc->pc_nextstatevent)
239		nextevent = pc->pc_nexttickevent;
240	else
241		nextevent = pc->pc_nextstatevent;
242
243	delay = nextevent - now;
244	if (delay < 0)
245		delay = 1;
246
247	agtimer_set_tval(delay);
248
249	return (rc);
250}
251
252void
253agtimer_set_clockrate(int32_t new_frequency)
254{
255	struct agtimer_softc	*sc = agtimer_cd.cd_devs[0];
256
257	agtimer_frequency = new_frequency;
258
259	if (sc == NULL)
260		return;
261
262	sc->sc_ticks_per_second = agtimer_frequency;
263	agtimer_timecounter.tc_frequency = sc->sc_ticks_per_second;
264	printf("agtimer0: adjusting clock: new tick rate %d KHz\n",
265	    sc->sc_ticks_per_second /1000);
266}
267
268void
269agtimer_cpu_initclocks()
270{
271	struct agtimer_softc	*sc = agtimer_cd.cd_devs[0];
272	struct agtimer_pcpu_softc *pc = &sc->sc_pstat[CPU_INFO_UNIT(curcpu())];
273	uint32_t		 reg;
274	uint64_t		 next;
275
276	stathz = hz;
277	profhz = hz * 10;
278
279	if (sc->sc_ticks_per_second != agtimer_frequency) {
280		agtimer_set_clockrate(agtimer_frequency);
281	}
282
283	agtimer_setstatclockrate(stathz);
284
285	sc->sc_ticks_per_intr = sc->sc_ticks_per_second / hz;
286	sc->sc_ticks_err_cnt = sc->sc_ticks_per_second % hz;
287	pc->pc_ticks_err_sum = 0;
288
289	/* Setup secure and non-secure timer IRQs. */
290	arm_intr_establish_fdt_idx(sc->sc_node, 0, IPL_CLOCK,
291	    agtimer_intr, NULL, "tick");
292	arm_intr_establish_fdt_idx(sc->sc_node, 1, IPL_CLOCK,
293	    agtimer_intr, NULL, "tick");
294
295	next = agtimer_readcnt64() + sc->sc_ticks_per_intr;
296	pc->pc_nexttickevent = pc->pc_nextstatevent = next;
297
298	reg = agtimer_get_ctrl();
299	reg &= ~GTIMER_CNTP_CTL_IMASK;
300	reg |= GTIMER_CNTP_CTL_ENABLE;
301	agtimer_set_tval(sc->sc_ticks_per_second);
302	agtimer_set_ctrl(reg);
303}
304
305void
306agtimer_delay(u_int usecs)
307{
308	u_int32_t		clock, oclock, delta, delaycnt;
309	volatile int		j;
310	int			csec, usec;
311
312	if (usecs > (0x80000000 / agtimer_frequency)) {
313		csec = usecs / 10000;
314		usec = usecs % 10000;
315
316		delaycnt = (agtimer_frequency / 100) * csec +
317		    (agtimer_frequency / 100) * usec / 10000;
318	} else {
319		delaycnt = agtimer_frequency * usecs / 1000000;
320	}
321	if (delaycnt <= 1)
322		for (j = 100; j > 0; j--)
323			;
324
325	oclock = agtimer_readcnt64();
326	while (1) {
327		for (j = 100; j > 0; j--)
328			;
329		clock = agtimer_readcnt64();
330		delta = clock - oclock;
331		if (delta > delaycnt)
332			break;
333	}
334}
335
336void
337agtimer_setstatclockrate(int newhz)
338{
339	struct agtimer_softc	*sc = agtimer_cd.cd_devs[0];
340	int			 minint, statint;
341	int			 s;
342
343	s = splclock();
344
345	statint = sc->sc_ticks_per_second / newhz;
346	/* calculate largest 2^n which is smaller that just over half statint */
347	sc->sc_statvar = 0x40000000; /* really big power of two */
348	minint = statint / 2 + 100;
349	while (sc->sc_statvar > minint)
350		sc->sc_statvar >>= 1;
351
352	sc->sc_statmin = statint - (sc->sc_statvar >> 1);
353
354	splx(s);
355
356	/*
357	 * XXX this allows the next stat timer to occur then it switches
358	 * to the new frequency. Rather than switching instantly.
359	 */
360}
361
362void
363agtimer_startclock(void)
364{
365	struct agtimer_softc	*sc = agtimer_cd.cd_devs[0];
366	struct agtimer_pcpu_softc *pc = &sc->sc_pstat[CPU_INFO_UNIT(curcpu())];
367	uint64_t nextevent;
368	uint32_t reg;
369
370	nextevent = agtimer_readcnt64() + sc->sc_ticks_per_intr;
371	pc->pc_nexttickevent = pc->pc_nextstatevent = nextevent;
372
373	reg = agtimer_get_ctrl();
374	reg &= ~GTIMER_CNTP_CTL_IMASK;
375	reg |= GTIMER_CNTP_CTL_ENABLE;
376	agtimer_set_tval(sc->sc_ticks_per_second);
377	agtimer_set_ctrl(reg);
378}
379
380void
381agtimer_init(void)
382{
383	uint32_t cntfrq = 0;
384
385	/* XXX: Check for Generic Timer support. */
386	__asm volatile("MRS %x0, CNTFRQ_EL0" : "=r" (cntfrq));
387
388	if (cntfrq != 0) {
389		agtimer_frequency = cntfrq;
390		arm_clock_register(NULL, agtimer_delay, NULL, NULL);
391	}
392}
393