1 /*
2 * Copyright (c) 2014 Travis Geiselbrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8 #include <dev/timer/arm_cortex_a9.h>
9
10 #include <lk/debug.h>
11 #include <sys/types.h>
12 #include <lk/err.h>
13 #include <stdio.h>
14 #include <assert.h>
15 #include <lk/trace.h>
16 #include <lib/fixed_point.h>
17 #include <kernel/thread.h>
18 #include <kernel/spinlock.h>
19 #include <platform.h>
20 #include <platform/interrupts.h>
21 #include <platform/timer.h>
22 #include <lk/init.h>
23
24 /* driver for cortex-a9's private timer */
25 #define LOCAL_TRACE 0
26
27 /* interrupts for cortex-a9 timer and watchdog */
28 #define CPU_GLOB_TIMER_INT 27
29 #define CPU_PRIV_TIMER_INT 29
30 #define CPU_WATCHDOG_INT 30
31
32 /* offsets into the per cpu private scu base */
33 #define GLOBAL_TIMER_OFFSET (0x0200)
34 #define PRIV_TIMER_OFFSET (0x0600)
35
36 #define TIMREG(reg) (*REG32(scu_control_base + PRIV_TIMER_OFFSET + (reg)))
37
38 #define TIMER_LOAD (0x00)
39 #define TIMER_COUNTER (0x04)
40 #define TIMER_CONTROL (0x08)
41 #define TIMER_ISR (0x0c)
42 #define WDOG_LOAD (0x20)
43 #define WDOG_COUNTER (0x24)
44 #define WDOG_CONTROL (0x28)
45 #define WDOG_ISR (0x2c)
46
47 #define GTIMREG(reg) (*REG32(scu_control_base + GLOBAL_TIMER_OFFSET + (reg)))
48
49 #define GTIMER_COUNT_LO (0x00)
50 #define GTIMER_COUNT_HI (0x04)
51 #define GTIMER_CONTROL (0x08)
52 #define GTIMER_ISR (0x0c)
53 #define GTIMER_COMPARE_LO (0x10)
54 #define GTIMER_COMPARE_HI (0x14)
55 #define GTIMER_INCREMENT (0x18)
56
57 static platform_timer_callback t_callback;
58 static addr_t scu_control_base;
59 static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE;
60
61 static lk_time_t periodic_interval;
62 static lk_time_t oneshot_interval;
63 static uint32_t timer_freq;
64 static struct fp_32_64 timer_freq_msec_conversion;
65 static struct fp_32_64 timer_freq_usec_conversion_inverse;
66 static struct fp_32_64 timer_freq_msec_conversion_inverse;
67
68 static void arm_cortex_a9_timer_init_percpu(uint level);
69
get_global_val(void)70 static uint64_t get_global_val(void) {
71 uint32_t lo, hi;
72
73 retry:
74 hi = GTIMREG(GTIMER_COUNT_HI);
75 lo = GTIMREG(GTIMER_COUNT_LO);
76 if (GTIMREG(GTIMER_COUNT_HI) != hi)
77 goto retry;
78
79 return ((uint64_t)hi << 32 | lo);
80 }
81
current_time_hires(void)82 lk_bigtime_t current_time_hires(void) {
83 lk_bigtime_t time;
84
85 time = u64_mul_u64_fp32_64(get_global_val(), timer_freq_usec_conversion_inverse);
86
87 return time;
88 }
89
current_time(void)90 lk_time_t current_time(void) {
91 lk_time_t time;
92
93 time = u32_mul_u64_fp32_64(get_global_val(), timer_freq_msec_conversion_inverse);
94
95 return time;
96 }
97
platform_set_periodic_timer(platform_timer_callback callback,void * arg,lk_time_t interval)98 status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
99 LTRACEF("callback %p, arg %p, interval %u\n", callback, arg, interval);
100
101 uint64_t ticks = u64_mul_u64_fp32_64(interval, timer_freq_msec_conversion);
102 if (unlikely(ticks == 0))
103 ticks = 1;
104 if (unlikely(ticks > 0xffffffff))
105 ticks = 0xffffffff;
106
107 spin_lock_saved_state_t state;
108 spin_lock_irqsave(&lock, state);
109
110 t_callback = callback;
111
112 periodic_interval = interval;
113
114 // disable timer
115 TIMREG(TIMER_CONTROL) = 0;
116
117 TIMREG(TIMER_LOAD) = ticks;
118 TIMREG(TIMER_CONTROL) = (1<<2) | (1<<1) | (1<<0); // irq enable, autoreload, enable
119
120 spin_unlock_irqrestore(&lock, state);
121
122 return NO_ERROR;
123 }
124
platform_set_oneshot_timer(platform_timer_callback callback,void * arg,lk_time_t interval)125 status_t platform_set_oneshot_timer (platform_timer_callback callback, void *arg, lk_time_t interval) {
126 LTRACEF("callback %p, arg %p, timeout %u\n", callback, arg, interval);
127
128 uint64_t ticks = u64_mul_u64_fp32_64(interval, timer_freq_msec_conversion);
129 if (unlikely(ticks == 0))
130 ticks = 1;
131 if (unlikely(ticks > 0xffffffff))
132 ticks = 0xffffffff;
133
134 spin_lock_saved_state_t state;
135 spin_lock_irqsave(&lock, state);
136
137 t_callback = callback;
138 oneshot_interval = interval;
139
140 // disable timer
141 TIMREG(TIMER_CONTROL) = 0;
142
143 TIMREG(TIMER_LOAD) = ticks;
144 TIMREG(TIMER_CONTROL) = (1<<2) | (1<<0) | (1<<0); // irq enable, oneshot, enable
145
146 spin_unlock_irqrestore(&lock, state);
147
148 return NO_ERROR;
149 }
150
platform_stop_timer(void)151 void platform_stop_timer(void) {
152 LTRACE;
153
154 TIMREG(TIMER_CONTROL) = 0;
155 }
156
platform_tick(void * arg)157 static enum handler_return platform_tick(void *arg) {
158 LTRACE;
159
160 TIMREG(TIMER_ISR) = 1; // ack the irq
161
162 if (t_callback) {
163 return t_callback(arg, current_time());
164 } else {
165 return INT_NO_RESCHEDULE;
166 }
167 }
168
arm_cortex_a9_timer_init(addr_t _scu_control_base,uint32_t freq)169 void arm_cortex_a9_timer_init(addr_t _scu_control_base, uint32_t freq) {
170 scu_control_base = _scu_control_base;
171
172 arm_cortex_a9_timer_init_percpu(0);
173
174 /* save the timer frequency for later calculations */
175 timer_freq = freq;
176
177 /* precompute the conversion factor for global time to real time */
178 fp_32_64_div_32_32(&timer_freq_msec_conversion, timer_freq, 1000);
179 fp_32_64_div_32_32(&timer_freq_usec_conversion_inverse, 1000000, timer_freq);
180 fp_32_64_div_32_32(&timer_freq_msec_conversion_inverse, 1000, timer_freq);
181 }
182
arm_cortex_a9_timer_init_percpu(uint level)183 static void arm_cortex_a9_timer_init_percpu(uint level) {
184 /* disable timer */
185 TIMREG(TIMER_CONTROL) = 0;
186
187 /* kill the watchdog */
188 TIMREG(WDOG_CONTROL) = 0;
189
190 /* ack any irqs that may be pending */
191 TIMREG(TIMER_ISR) = 1;
192
193 /* register the platform tick on each cpu */
194 register_int_handler(CPU_PRIV_TIMER_INT, &platform_tick, NULL);
195 unmask_interrupt(CPU_PRIV_TIMER_INT);
196 }
197
198 /* secondary cpu initialize the timer just before the kernel starts with interrupts enabled */
199 LK_INIT_HOOK_FLAGS(arm_cortex_a9_timer_init_percpu,
200 arm_cortex_a9_timer_init_percpu,
201 LK_INIT_LEVEL_THREADING - 1, LK_INIT_FLAG_SECONDARY_CPUS);
202