blob: ac88029151976e732eb7a3d583e835ad27925778 [file] [log] [blame]
Miguel Young de la Sota6146c672020-04-15 13:09:52 -04001// Copyright lowRISC contributors.
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5#include "sw/device/lib/dif/dif_rv_timer.h"
6
7#include <stddef.h>
8
9#include "sw/device/lib/base/bitfield.h"
10#include "rv_timer_regs.h" // Generated.
11
12/**
13 * The factor to multiply by to find the registers for the Nth hart.
14 *
15 * Given the hart N (zero-indexed), its timer registers are found at
16 * the memory address
17 * base_addr + ((N + 1) * kHartRegisterSpacing)
18 *
19 * The function `reg_for_hart()` can be used to compute the offset from
20 * `base` for the Nth hart for a particular repeated hardware register.
21 */
22static const ptrdiff_t kHartRegisterSpacing = 0x100;
23
24/**
25 * Returns the MMIO offset for the register `reg_offset`, for the zero-indexed
26 * `hart`.
27 */
28static ptrdiff_t reg_for_hart(uint32_t hart, ptrdiff_t reg_offset) {
29 return kHartRegisterSpacing * hart + reg_offset;
30}
31
32dif_rv_timer_result_t dif_rv_timer_init(mmio_region_t base_addr,
33 dif_rv_timer_config_t config,
34 dif_rv_timer_t *timer_out) {
35 if (timer_out == NULL || config.hart_count < 1 ||
36 config.comparator_count < 1) {
37 return kDifRvTimerBadArg;
38 }
39
40 timer_out->base_addr = base_addr;
41 timer_out->config = config;
42
43 return dif_rv_timer_reset(timer_out);
44}
45
46/**
47 * A naive implementation of the Euclidean algorithm by repeated remainder.
48 */
49static uint64_t euclidean_gcd(uint64_t a, uint64_t b) {
50 // TODO: The below 64-bit divide/remaider should be replaced with more
51 // space-efficient polyfills at some point.
52 while (b != 0) {
53 uint64_t old_b = b;
54 b = a % b;
55 a = old_b;
56 }
57
58 return a;
59}
60
61dif_rv_timer_approximate_tick_params_result_t
62dif_rv_timer_approximate_tick_params(uint64_t clock_freq, uint64_t counter_freq,
63 dif_rv_timer_tick_params_t *out) {
64 if (out == NULL) {
65 return kDifRvTimerApproximateTickParamsBadArg;
66 }
67
68 // We have the following relation:
69 // counter_freq = clock_freq * (step / (prescale + 1))
70 // We can solve for the individual parts as
71 // prescale = clock_freq / gcd - 1
72 // step = counter_freq / gcd
73 uint64_t gcd = euclidean_gcd(clock_freq, counter_freq);
74
75 uint64_t prescale = clock_freq / gcd - 1;
76 uint64_t step = counter_freq / gcd;
77
78 if (prescale > RV_TIMER_CFG0_PRESCALE_MASK ||
79 step > RV_TIMER_CFG0_STEP_MASK) {
80 return kDifRvTimerApproximateTickParamsUnrepresentable;
81 }
82
83 out->prescale = (uint16_t)prescale;
84 out->tick_step = (uint8_t)step;
85
86 return kDifRvTimerApproximateTickParamsOk;
87}
88
89dif_rv_timer_result_t dif_rv_timer_set_tick_params(
90 const dif_rv_timer_t *timer, uint32_t hart_id,
91 dif_rv_timer_tick_params_t params) {
92 if (timer == NULL || hart_id >= timer->config.hart_count) {
93 return kDifRvTimerBadArg;
94 }
95
96 uint32_t config_value = 0;
97 config_value = bitfield_set_field32(
98 config_value, (bitfield_field32_t){
99 .mask = RV_TIMER_CFG0_PRESCALE_MASK,
100 .index = RV_TIMER_CFG0_PRESCALE_OFFSET,
101 .value = params.prescale,
102 });
103 config_value =
104 bitfield_set_field32(config_value, (bitfield_field32_t){
105 .mask = RV_TIMER_CFG0_STEP_MASK,
106 .index = RV_TIMER_CFG0_STEP_OFFSET,
107 .value = params.tick_step,
108 });
109 mmio_region_write32(timer->base_addr,
110 reg_for_hart(hart_id, RV_TIMER_CFG0_REG_OFFSET),
111 config_value);
112
113 return kDifRvTimerOk;
114}
115
116dif_rv_timer_result_t dif_rv_timer_counter_set_enabled(
117 const dif_rv_timer_t *timer, uint32_t hart_id,
118 dif_rv_timer_enabled_t state) {
119 if (timer == NULL || hart_id >= timer->config.hart_count) {
120 return kDifRvTimerBadArg;
121 }
122
123 switch (state) {
124 case kDifRvTimerEnabled:
125 mmio_region_nonatomic_set_bit32(timer->base_addr,
126 RV_TIMER_CTRL_REG_OFFSET, hart_id);
127 break;
128 case kDifRvTimerDisabled:
129 mmio_region_nonatomic_clear_bit32(timer->base_addr,
130 RV_TIMER_CTRL_REG_OFFSET, hart_id);
131 break;
132 default:
133 return kDifRvTimerBadArg;
134 }
135
136 return kDifRvTimerOk;
137}
138
139dif_rv_timer_result_t dif_rv_timer_counter_read(const dif_rv_timer_t *timer,
140 uint32_t hart_id,
141 uint64_t *out) {
142 if (timer == NULL || out == NULL || hart_id >= timer->config.hart_count) {
143 return kDifRvTimerBadArg;
144 }
145
146 // We need to read a monotonically increasing, volatile uint64. To do so,
147 // we first read the upper half, then the lower half. Then, we check if the
148 // upper half reads the same value again. If it doesn't, it means that the
149 // lower half overflowed and we need to re-take the measurement.
150 while (true) {
151 uint32_t upper = mmio_region_read32(
152 timer->base_addr,
153 reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
154 uint32_t lower = mmio_region_read32(
155 timer->base_addr,
156 reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET));
157
158 uint32_t overflow_check = mmio_region_read32(
159 timer->base_addr,
160 reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
161
162 if (upper == overflow_check) {
163 *out = (((uint64_t)upper) << 32) | lower;
164 return kDifRvTimerOk;
165 }
166 }
167}
168
169dif_rv_timer_result_t dif_rv_timer_arm(const dif_rv_timer_t *timer,
170 uint32_t hart_id, uint32_t comp_id,
171 uint64_t threshold) {
172 if (timer == NULL || hart_id >= timer->config.hart_count ||
173 comp_id >= timer->config.comparator_count) {
174 return kDifRvTimerBadArg;
175 }
176
177 uint32_t lower = threshold;
178 uint32_t upper = threshold >> 32;
179
180 ptrdiff_t lower_reg =
181 reg_for_hart(hart_id, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) +
182 (sizeof(uint64_t) * comp_id);
183 ptrdiff_t upper_reg =
184 reg_for_hart(hart_id, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) +
185 (sizeof(uint64_t) * comp_id);
186
187 // First, set the upper register to the largest value possible without setting
188 // off the alarm; this way, we can set the lower register without setting
189 // off the alarm.
190 mmio_region_write32(timer->base_addr, upper_reg, UINT32_MAX);
191
192 // This can't set off the alarm because of the value we set above.
193 mmio_region_write32(timer->base_addr, lower_reg, lower);
194 // Finish writing the new value; this may set off an alarm immediately.
195 mmio_region_write32(timer->base_addr, upper_reg, upper);
196
197 return kDifRvTimerOk;
198}
199
200/**
201 * The number of comparators that the IP instantiation this library is compiled
202 * against has. In other words, this tells us how far the statically known IRQ
203 * register constants are from the start of the comparators, which influences
204 * the computation in `irq_reg_for_hart()`.
205 */
206static const ptrdiff_t kComparatorsInReggenHeader =
207 (RV_TIMER_INTR_ENABLE0_REG_OFFSET - RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) /
208 sizeof(uint64_t);
209
210/**
211 * Computes the appropriate register for a particular interrupt register, given
212 * a number of comparators.
213 *
214 * Currently, all IRQ registers are placed after the comparator registers,
215 * so the register offsets given by HW are not useable. The offsets need to be
216 * compensated for by a factor of `kComparatorsInReggenHeader` double-words..
217 *
218 * We also do not handle the case when comparator_count > 32, which would cause
219 * multiple interrupt registers to be generated.
220 */
221static ptrdiff_t irq_reg_for_hart(uint32_t hart_id, uint32_t comparators,
222 ptrdiff_t reg_offset) {
223 // Note that it is completely valid for this value to be negative: if this
224 // library is built for hardware with a large number of compartors, this value
225 // is necessarially negative when used with hardware with a small number of
226 // comparators.
227 ptrdiff_t extra_comparator_offset =
228 sizeof(uint64_t) * (comparators - kComparatorsInReggenHeader);
229 return reg_for_hart(hart_id, reg_offset) + extra_comparator_offset;
230}
231
232dif_rv_timer_result_t dif_rv_timer_irq_enable(const dif_rv_timer_t *timer,
233 uint32_t hart_id,
234 uint32_t comp_id,
235 dif_rv_timer_enabled_t state) {
236 if (timer == NULL || hart_id >= timer->config.hart_count ||
237 comp_id >= timer->config.comparator_count) {
238 return kDifRvTimerBadArg;
239 }
240
241 ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
242 RV_TIMER_INTR_ENABLE0_REG_OFFSET);
243
244 switch (state) {
245 case kDifRvTimerEnabled:
246 mmio_region_nonatomic_set_bit32(timer->base_addr, reg, comp_id);
247 break;
248 case kDifRvTimerDisabled:
249 mmio_region_nonatomic_clear_bit32(timer->base_addr, reg, comp_id);
250 break;
251 default:
252 return kDifRvTimerBadArg;
253 }
254
255 return kDifRvTimerOk;
256}
257
258dif_rv_timer_result_t dif_rv_timer_irq_get(const dif_rv_timer_t *timer,
259 uint32_t hart_id, uint32_t comp_id,
260 bool *flag_out) {
261 if (timer == NULL || flag_out == NULL ||
262 hart_id >= timer->config.hart_count ||
263 comp_id >= timer->config.comparator_count) {
264 return kDifRvTimerBadArg;
265 }
266
267 ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
268 RV_TIMER_INTR_STATE0_REG_OFFSET);
269
270 *flag_out = mmio_region_get_bit32(timer->base_addr, reg, comp_id);
271
272 return kDifRvTimerOk;
273}
274
275dif_rv_timer_result_t dif_rv_timer_irq_clear(const dif_rv_timer_t *timer,
276 uint32_t hart_id,
277 uint32_t comp_id) {
278 if (timer == NULL || hart_id >= timer->config.hart_count ||
279 comp_id >= timer->config.comparator_count) {
280 return kDifRvTimerBadArg;
281 }
282
283 ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
284 RV_TIMER_INTR_STATE0_REG_OFFSET);
285
286 mmio_region_write32(timer->base_addr, reg, 1 << comp_id);
287
288 return kDifRvTimerOk;
289}
290
291dif_rv_timer_result_t dif_rv_timer_irq_disable(const dif_rv_timer_t *timer,
292 uint32_t hart_id,
293 uint32_t *state) {
294 if (timer == NULL || hart_id >= timer->config.hart_count) {
295 return kDifRvTimerBadArg;
296 }
297
298 ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
299 RV_TIMER_INTR_ENABLE0_REG_OFFSET);
300
301 if (state != NULL) {
302 *state = mmio_region_read32(timer->base_addr, reg);
303 }
304
305 mmio_region_write32(timer->base_addr, reg, 0);
306
307 return kDifRvTimerOk;
308}
309
310dif_rv_timer_result_t dif_rv_timer_irq_restore(const dif_rv_timer_t *timer,
311 uint32_t hart_id,
312 uint32_t state) {
313 if (timer == NULL || hart_id >= timer->config.hart_count) {
314 return kDifRvTimerBadArg;
315 }
316
317 ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
318 RV_TIMER_INTR_ENABLE0_REG_OFFSET);
319
320 mmio_region_write32(timer->base_addr, reg, state);
321
322 return kDifRvTimerOk;
323}
324
325dif_rv_timer_result_t dif_rv_timer_irq_force(const dif_rv_timer_t *timer,
326 uint32_t hart_id,
327 uint32_t comp_id) {
328 if (timer == NULL || hart_id >= timer->config.hart_count ||
329 comp_id >= timer->config.comparator_count) {
330 return kDifRvTimerBadArg;
331 }
332
333 ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
334 RV_TIMER_INTR_TEST0_REG_OFFSET);
335
336 mmio_region_write32(timer->base_addr, reg, 1 << comp_id);
337
338 return kDifRvTimerOk;
339}
340
341dif_rv_timer_result_t dif_rv_timer_reset(const dif_rv_timer_t *timer) {
342 if (timer == NULL) {
343 return kDifRvTimerBadArg;
344 }
345
346 // Disable all counters.
347 mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET, 0x0);
348
349 for (uint32_t hart_id = 0; hart_id < timer->config.hart_count; ++hart_id) {
350 // Clear and disable all interrupts.
351 ptrdiff_t irq_status =
352 irq_reg_for_hart(hart_id, timer->config.comparator_count,
353 RV_TIMER_INTR_STATE0_REG_OFFSET);
354 ptrdiff_t irq_enable =
355 irq_reg_for_hart(hart_id, timer->config.comparator_count,
356 RV_TIMER_INTR_ENABLE0_REG_OFFSET);
357 mmio_region_write32(timer->base_addr, irq_enable, 0x0);
358 mmio_region_write32(timer->base_addr, irq_status, UINT32_MAX);
359
360 // Reset all comparators to their default all-ones state.
361 for (uint32_t comp_id = 0; comp_id < timer->config.comparator_count;
362 ++comp_id) {
363 dif_rv_timer_result_t error =
364 dif_rv_timer_arm(timer, hart_id, comp_id, UINT64_MAX);
365 if (error != kDifRvTimerOk) {
366 return error;
367 }
368 }
369
370 // Reset the counter to zero.
371 mmio_region_write32(
372 timer->base_addr,
373 reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
374 mmio_region_write32(
375 timer->base_addr,
376 reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
377 }
378
379 return kDifRvTimerOk;
380}