aboutsummaryrefslogtreecommitdiff
path: root/sys/arm/arm/mpcore_timer.c
blob: 719685980e5711c65950d5a1beb29d2561c5efd4 (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
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*-
 * Copyright (c) 2011 The FreeBSD Foundation
 * All rights reserved.
 *
 * Developed by Ben Gray <ben.r.gray@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the company nor the name of the author may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/**
 *	The ARM Cortex-A9 core can support a global timer plus a private and
 *	watchdog timer per core.  This driver reserves memory and interrupt
 *	resources for accessing both timer register sets, these resources are
 *	stored globally and used to setup the timecount and eventtimer.
 *
 *	The timecount timer uses the global 64-bit counter, whereas the
 *	per-CPU eventtimer uses the private 32-bit counters.
 *
 *
 *	REF: ARM Cortex-A9 MPCore, Technical Reference Manual (rev. r2p2)
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/intr.h>

#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>

#include <machine/bus.h>
#include <machine/fdt.h>

/* Private (per-CPU) timer register map */
#define PRV_TIMER_LOAD                 0x0000
#define PRV_TIMER_COUNT                0x0004
#define PRV_TIMER_CTRL                 0x0008
#define PRV_TIMER_INTR                 0x000C

#define PRV_TIMER_CTR_PRESCALER_SHIFT  8
#define PRV_TIMER_CTRL_IRQ_ENABLE      (1UL << 2)
#define PRV_TIMER_CTRL_AUTO_RELOAD     (1UL << 1)
#define PRV_TIMER_CTRL_TIMER_ENABLE    (1UL << 0)

#define PRV_TIMER_INTR_EVENT           (1UL << 0)

/* Global timer register map */
#define GBL_TIMER_COUNT_LOW            0x0000
#define GBL_TIMER_COUNT_HIGH           0x0004
#define GBL_TIMER_CTRL                 0x0008
#define GBL_TIMER_INTR                 0x000C

#define GBL_TIMER_CTR_PRESCALER_SHIFT  8
#define GBL_TIMER_CTRL_AUTO_INC        (1UL << 3)
#define GBL_TIMER_CTRL_IRQ_ENABLE      (1UL << 2)
#define GBL_TIMER_CTRL_COMP_ENABLE     (1UL << 1)
#define GBL_TIMER_CTRL_TIMER_ENABLE    (1UL << 0)

#define GBL_TIMER_INTR_EVENT           (1UL << 0)

struct arm_tmr_softc {
	struct resource *	tmr_res[4];
	bus_space_tag_t		prv_bst;
	bus_space_tag_t		gbl_bst;
	bus_space_handle_t	prv_bsh;
	bus_space_handle_t	gbl_bsh;
	uint32_t		clkfreq;
	struct eventtimer	et;
};

static struct resource_spec arm_tmr_spec[] = {
	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },	/* Global registers */
	{ SYS_RES_IRQ,		0,	RF_ACTIVE },    /* Global timer interrupt (unused) */
	{ SYS_RES_MEMORY,	1,	RF_ACTIVE },	/* Private (per-CPU) registers */
	{ SYS_RES_IRQ,		1,	RF_ACTIVE },    /* Private timer interrupt */
	{ -1, 0 }
};

static struct arm_tmr_softc *arm_tmr_sc = NULL;

uint32_t platform_arm_tmr_freq = 0;

#define	tmr_prv_read_4(reg)		\
    bus_space_read_4(arm_tmr_sc->prv_bst, arm_tmr_sc->prv_bsh, reg)
#define	tmr_prv_write_4(reg, val)		\
    bus_space_write_4(arm_tmr_sc->prv_bst, arm_tmr_sc->prv_bsh, reg, val)
#define	tmr_gbl_read_4(reg)		\
    bus_space_read_4(arm_tmr_sc->gbl_bst, arm_tmr_sc->gbl_bsh, reg)
#define	tmr_gbl_write_4(reg, val)		\
    bus_space_write_4(arm_tmr_sc->gbl_bst, arm_tmr_sc->gbl_bsh, reg, val)


static timecounter_get_t arm_tmr_get_timecount;

static struct timecounter arm_tmr_timecount = {
	.tc_name           = "ARM MPCore Timecounter",
	.tc_get_timecount  = arm_tmr_get_timecount,
	.tc_poll_pps       = NULL,
	.tc_counter_mask   = ~0u,
	.tc_frequency      = 0,
	.tc_quality        = 1000,
};

/**
 *	arm_tmr_get_timecount - reads the timecount (global) timer
 *	@tc: pointer to arm_tmr_timecount struct
 *
 *	We only read the lower 32-bits, the timecount stuff only uses 32-bits
 *	so (for now?) ignore the upper 32-bits.
 *
 *	RETURNS
 *	The lower 32-bits of the counter.
 */
static unsigned
arm_tmr_get_timecount(struct timecounter *tc)
{
	return (tmr_gbl_read_4(GBL_TIMER_COUNT_LOW));
}

/**
 *	arm_tmr_start - starts the eventtimer (private) timer
 *	@et: pointer to eventtimer struct
 *	@first: the number of seconds and fractional sections to trigger in
 *	@period: the period (in seconds and fractional sections) to set
 *
 *	If the eventtimer is required to be in oneshot mode, period will be
 *	NULL and first will point to the time to trigger.  If in periodic mode
 *	period will contain the time period and first may optionally contain
 *	the time for the first period.
 *
 *	RETURNS
 *	Always returns 0
 */
static int
arm_tmr_start(struct eventtimer *et, sbintime_t first, sbintime_t period)
{
	uint32_t load, count;
	uint32_t ctrl;

	ctrl = PRV_TIMER_CTRL_IRQ_ENABLE | PRV_TIMER_CTRL_TIMER_ENABLE;

	if (period != 0) {
		load = ((uint32_t)et->et_frequency * period) >> 32;
		ctrl |= PRV_TIMER_CTRL_AUTO_RELOAD;
	} else
		load = 0;

	if (first != 0)
		count = ((uint32_t)et->et_frequency * first) >> 32;
	else
		count = load;

	tmr_prv_write_4(PRV_TIMER_LOAD, load);
	tmr_prv_write_4(PRV_TIMER_COUNT, count);

	tmr_prv_write_4(PRV_TIMER_CTRL, ctrl);
	return (0);
}

/**
 *	arm_tmr_stop - stops the eventtimer (private) timer
 *	@et: pointer to eventtimer struct
 *
 *	Simply stops the private timer by clearing all bits in the ctrl register.
 *
 *	RETURNS
 *	Always returns 0
 */
static int
arm_tmr_stop(struct eventtimer *et)
{
	tmr_prv_write_4(PRV_TIMER_CTRL, 0);
	return (0);
}

/**
 *	arm_tmr_intr - ISR for the eventtimer (private) timer
 *	@arg: pointer to arm_tmr_softc struct
 *
 *	Clears the event register and then calls the eventtimer callback.
 *
 *	RETURNS
 *	Always returns FILTER_HANDLED
 */
static int
arm_tmr_intr(void *arg)
{
	struct arm_tmr_softc *sc = (struct arm_tmr_softc *)arg;

	tmr_prv_write_4(PRV_TIMER_INTR, PRV_TIMER_INTR_EVENT);

	if (sc->et.et_active)
		sc->et.et_event_cb(&sc->et, sc->et.et_arg);

	return (FILTER_HANDLED);
}




/**
 *	arm_tmr_probe - timer probe routine
 *	@dev: new device
 *
 *	The probe function returns success when probed with the fdt compatible
 *	string set to "arm,mpcore-timers".
 *
 *	RETURNS
 *	BUS_PROBE_DEFAULT if the fdt device is compatible, otherwise ENXIO.
 */
static int
arm_tmr_probe(device_t dev)
{

	if (!ofw_bus_status_okay(dev))
		return (ENXIO);

	if (!ofw_bus_is_compatible(dev, "arm,mpcore-timers"))
		return (ENXIO);

	device_set_desc(dev, "ARM Generic MPCore Timers");
	return (BUS_PROBE_DEFAULT);
}

/**
 *	arm_tmr_attach - attaches the timer to the simplebus
 *	@dev: new device
 *
 *	Reserves memory and interrupt resources, stores the softc structure
 *	globally and registers both the timecount and eventtimer objects.
 *
 *	RETURNS
 *	Zero on sucess or ENXIO if an error occuried.
 */
static int
arm_tmr_attach(device_t dev)
{
	struct arm_tmr_softc *sc = device_get_softc(dev);
	phandle_t node;
	pcell_t clock;
	void *ihl;

	if (arm_tmr_sc)
		return (ENXIO);

	if (platform_arm_tmr_freq != 0)
		sc->clkfreq = platform_arm_tmr_freq;
	else {
		/* Get the base clock frequency */
		node = ofw_bus_get_node(dev);
		if ((OF_getprop(node, "clock-frequency", &clock,
		    sizeof(clock))) <= 0) {
			device_printf(dev, "missing clock-frequency attribute in FDT\n");
			return (ENXIO);
		}
		sc->clkfreq = fdt32_to_cpu(clock);
	}


	if (bus_alloc_resources(dev, arm_tmr_spec, sc->tmr_res)) {
		device_printf(dev, "could not allocate resources\n");
		return (ENXIO);
	}

	/* Global timer interface */
	sc->gbl_bst = rman_get_bustag(sc->tmr_res[0]);
	sc->gbl_bsh = rman_get_bushandle(sc->tmr_res[0]);

	/* Private per-CPU timer interface */
	sc->prv_bst = rman_get_bustag(sc->tmr_res[2]);
	sc->prv_bsh = rman_get_bushandle(sc->tmr_res[2]);

	arm_tmr_sc = sc;

	/* Disable both timers to start off */
	tmr_prv_write_4(PRV_TIMER_CTRL, 0x00000000);
	tmr_gbl_write_4(GBL_TIMER_CTRL, 0x00000000);

	/* Setup and enable the global timer to use as the timecounter */
	tmr_gbl_write_4(GBL_TIMER_CTRL, (0x00 << GBL_TIMER_CTR_PRESCALER_SHIFT) | 
					GBL_TIMER_CTRL_TIMER_ENABLE);

	arm_tmr_timecount.tc_frequency = sc->clkfreq;
	tc_init(&arm_tmr_timecount);

	/* Setup and enable the timer */
	if (bus_setup_intr(dev, sc->tmr_res[3], INTR_TYPE_CLK, arm_tmr_intr,
			NULL, sc, &ihl) != 0) {
		bus_release_resources(dev, arm_tmr_spec, sc->tmr_res);
		device_printf(dev, "Unable to setup the clock irq handler.\n");
		return (ENXIO);
	}

	sc->et.et_name = "ARM MPCore Eventtimer";
	sc->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT | ET_FLAGS_PERCPU;
	sc->et.et_quality = 1000;

	sc->et.et_frequency = sc->clkfreq;
	sc->et.et_min_period = (0x00000002LLU << 32) / sc->et.et_frequency;
	sc->et.et_max_period = (0xfffffffeLLU << 32) / sc->et.et_frequency;
	sc->et.et_start = arm_tmr_start;
	sc->et.et_stop = arm_tmr_stop;
	sc->et.et_priv = sc;
	et_register(&sc->et);

	return (0);
}

static device_method_t arm_tmr_methods[] = {
	DEVMETHOD(device_probe,		arm_tmr_probe),
	DEVMETHOD(device_attach,	arm_tmr_attach),
	{ 0, 0 }
};

static driver_t arm_tmr_driver = {
	"mp_tmr",
	arm_tmr_methods,
	sizeof(struct arm_tmr_softc),
};

static devclass_t arm_tmr_devclass;

DRIVER_MODULE(mp_tmr, simplebus, arm_tmr_driver, arm_tmr_devclass, 0, 0);

/**
 *	DELAY - Delay for at least usec microseconds.
 *	@usec: number of microseconds to delay by
 *
 *	This function is called all over the kernel and is suppose to provide a
 *	consistent delay.  This function may also be called before the console 
 *	is setup so no printf's can be called here.
 *
 *	RETURNS:
 *	nothing
 */
void
DELAY(int usec)
{
	int32_t counts_per_usec;
	int32_t counts;
	uint32_t first, last;

	/* Check the timers are setup, if not just use a for loop for the meantime */
	if (arm_tmr_sc == NULL) {
		for (; usec > 0; usec--)
			for (counts = 200; counts > 0; counts--)
				cpufunc_nullop();	/* Prevent gcc from optimizing
							 * out the loop
							 */
		return;
	}

	/* Get the number of times to count */
	counts_per_usec = ((arm_tmr_timecount.tc_frequency / 1000000) + 1);

	/*
	 * Clamp the timeout at a maximum value (about 32 seconds with
	 * a 66MHz clock). *Nobody* should be delay()ing for anywhere
	 * near that length of time and if they are, they should be hung
	 * out to dry.
	 */
	if (usec >= (0x80000000U / counts_per_usec))
		counts = (0x80000000U / counts_per_usec) - 1;
	else
		counts = usec * counts_per_usec;

	first = tmr_gbl_read_4(GBL_TIMER_COUNT_LOW);

	while (counts > 0) {
		last = tmr_gbl_read_4(GBL_TIMER_COUNT_LOW);
		counts -= (int32_t)(last - first);
		first = last;
	}
}