summaryrefslogtreecommitdiff
path: root/chip/g/hwtimer.c
blob: 3c1ef737593d90801af66662e0b4399d8bc41217 (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
/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "common.h"
#include "hooks.h"
#include "hwtimer.h"
#include "registers.h"
#include "task.h"
#include "util.h"

/*
 * Other chips can interrupt at arbitrary match points. We can only interrupt
 * at zero, so we'll have to use a separate timer for events. We'll use module
 * 0, timer 1 for the current time and module 0, timer 2 for event timers.
 *
 * Oh, and we can't control the rate at which the timers tick. We expect to
 * have all counter values in microseconds, but instead they'll be some factor
 * faster than that. 1 usec / tick == 1 MHz, so if PCLK is 30 MHz, we'll have
 * to divide the hardware counter by 30 to get the values expected outside of
 * this file.
 */
static uint32_t clock_mul_factor;
static uint32_t clock_div_factor;
static uint32_t hw_rollover_count;

static inline uint32_t ticks_to_usecs(uint32_t ticks)
{
	return hw_rollover_count * clock_div_factor + ticks / clock_mul_factor;
}

static inline uint32_t usecs_to_ticks(uint32_t usecs)
{
	/* Really large counts will just be scheduled more than once */
	if (usecs >= clock_div_factor)
		return 0xffffffff;

	return usecs * clock_mul_factor;
}

static void update_prescaler(void)
{
	/*
	 * We want the timer to tick every microsecond, but we can only divide
	 * PCLK by 1, 16, or 256. We're targeting 30MHz, so we'll just let it
	 * run at 1:1.
	 */
	REG_WRITE_MLV(GR_TIMEHS_CONTROL(0, 1),
		      GC_TIMEHS_TIMER1CONTROL_PRE_MASK,
		      GC_TIMEHS_TIMER1CONTROL_PRE_LSB, 0);
	REG_WRITE_MLV(GR_TIMEHS_CONTROL(0, 2),
		      GC_TIMEHS_TIMER1CONTROL_PRE_MASK,
		      GC_TIMEHS_TIMER1CONTROL_PRE_LSB, 0);

	/*
	 * We're not yet doing anything to detect the current frequency, we're
	 * just hard-coding it. We're also assuming the clock rate is an
	 * integer multiple of MHz.
	 */
	clock_mul_factor = 30;			/* NOTE: prototype board */
	clock_div_factor = 0xffffffff / clock_mul_factor;
}
DECLARE_HOOK(HOOK_FREQ_CHANGE, update_prescaler, HOOK_PRIO_DEFAULT);

uint32_t __hw_clock_event_get(void)
{
	/* At what time will the next event fire? */
	uint32_t time_now_in_ticks;
	time_now_in_ticks = (0xffffffff - GR_TIMEHS_VALUE(0, 1));
	return ticks_to_usecs(time_now_in_ticks + GR_TIMEHS_VALUE(0, 2));
}

void __hw_clock_event_clear(void)
{
	/* one-shot, 32-bit, timer & interrupts disabled, 1:1 prescale */
	GR_TIMEHS_CONTROL(0, 2) = 0x3;
	/* Clear any pending interrupts */
	GR_TIMEHS_INTCLR(0, 2) = 0x1;
}

void __hw_clock_event_set(uint32_t deadline)
{
	uint32_t time_now_in_ticks;

	__hw_clock_event_clear();

	/* How long from the current time to the deadline? */
	time_now_in_ticks = (0xffffffff - GR_TIMEHS_VALUE(0, 1));
	GR_TIMEHS_LOAD(0, 2) = (deadline - time_now_in_ticks / clock_mul_factor)
				* clock_mul_factor;

	/* timer & interrupts enabled */
	GR_TIMEHS_CONTROL(0, 2) = 0xa3;
}

/*
 * Handle event matches. It's lower priority than the HW rollover irq, so it
 * will always be either before or after a rollover exception.
 */
void __hw_clock_event_irq(void)
{
	__hw_clock_event_clear();
	process_timers(0);
}
DECLARE_IRQ(GC_IRQNUM_TIMEHS0_TIMINT2, __hw_clock_event_irq, 2);

uint32_t __hw_clock_source_read(void)
{
	/*
	 * Return the current time in usecs. Since the counter counts down,
	 * we have to invert the value.
	 */
	return ticks_to_usecs(0xffffffff - GR_TIMEHS_VALUE(0, 1));
}

void __hw_clock_source_set(uint32_t ts)
{
	GR_TIMEHS_LOAD(0, 1) = 0xffffffff - usecs_to_ticks(ts);
}

/* This handles rollover in the HW timer */
void __hw_clock_source_irq(void)
{
	/* Clear the interrupt */
	GR_TIMEHS_INTCLR(0, 1) = 0x1;

	/* The one-tick-per-clock HW counter has rolled over. */
	hw_rollover_count++;
	/* Has the system's usec counter rolled over? */
	if (hw_rollover_count >= clock_mul_factor) {
		hw_rollover_count = 0;
		process_timers(1);
	} else {
		process_timers(0);
	}
}
DECLARE_IRQ(GC_IRQNUM_TIMEHS0_TIMINT1, __hw_clock_source_irq, 1);

int __hw_clock_source_init(uint32_t start_t)
{
	/* Set the reload and current value. */
	GR_TIMEHS_BGLOAD(0, 1) = 0xffffffff;
	GR_TIMEHS_LOAD(0, 1) = 0xffffffff;

	/* HW Timer enabled, periodic, interrupt enabled, 32-bit, wrapping */
	GR_TIMEHS_CONTROL(0, 1) = 0xe2;
	/* Event timer disabled */
	__hw_clock_event_clear();

	/* Account for the clock speed. */
	update_prescaler();

	/* Clear any pending interrupts */
	GR_TIMEHS_INTCLR(0, 1) = 0x1;

	/* Force the time to whatever we're told it is */
	__hw_clock_source_set(start_t);

	/* Here we go... */
	task_enable_irq(GC_IRQNUM_TIMEHS0_TIMINT1);
	task_enable_irq(GC_IRQNUM_TIMEHS0_TIMINT2);

	/* Return the Event timer IRQ number (NOT the HW timer IRQ) */
	return GC_IRQNUM_TIMEHS0_TIMINT2;
}