Software APIs
dif_rv_timer.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
6
7#include <assert.h>
8#include <stddef.h>
9
13
14#include "rv_timer_regs.h" // Generated.
15
16static_assert(RV_TIMER_PARAM_N_HARTS > 0,
17 "RV Timer must support at least one hart.");
18static_assert(RV_TIMER_PARAM_N_TIMERS > 0,
19 "RV Timer must support at least one timer per hart.");
20
21/**
22 * The factor to multiply by to find the registers for the Nth hart.
23 *
24 * Given the hart N (zero-indexed), its timer registers are found at
25 * the memory address
26 * base_addr + ((N + 1) * kHartRegisterSpacing)
27 *
28 * The function `reg_for_hart()` can be used to compute the offset from
29 * `base` for the Nth hart for a particular repeated hardware register.
30 */
31static const ptrdiff_t kHartRegisterSpacing = 0x100;
32
33/**
34 * Returns the MMIO offset for the register `reg_offset`, for the zero-indexed
35 * `hart`.
36 */
37static ptrdiff_t reg_for_hart(uint32_t hart, ptrdiff_t reg_offset) {
38 return kHartRegisterSpacing * (ptrdiff_t)hart + reg_offset;
39}
40
41/**
42 * A naive implementation of the Euclidean algorithm by repeated remainder.
43 */
44static uint64_t euclidean_gcd(uint64_t a, uint64_t b) {
45 while (b != 0) {
46 uint64_t old_b = b;
47 OT_DISCARD(udiv64_slow(a, b, &b));
48 a = old_b;
49 }
50
51 return a;
52}
53
54dif_result_t dif_rv_timer_approximate_tick_params(
55 uint64_t clock_freq, uint64_t counter_freq,
57 if (out == NULL) {
58 return kDifBadArg;
59 }
60
61 // We have the following relation:
62 // counter_freq = clock_freq * (step / (prescale + 1))
63 // We can solve for the individual parts as
64 // prescale = clock_freq / gcd - 1
65 // step = counter_freq / gcd
66 uint64_t gcd = euclidean_gcd(clock_freq, counter_freq);
67
68 uint64_t prescale = udiv64_slow(clock_freq, gcd, NULL) - 1;
69 uint64_t step = udiv64_slow(counter_freq, gcd, NULL);
70
71 if (prescale > RV_TIMER_CFG0_PRESCALE_MASK ||
72 step > RV_TIMER_CFG0_STEP_MASK) {
73 return kDifBadArg;
74 }
75
76 out->prescale = (uint16_t)prescale;
77 out->tick_step = (uint8_t)step;
78
79 return kDifOk;
80}
81
82dif_result_t dif_rv_timer_set_tick_params(const dif_rv_timer_t *timer,
83 uint32_t hart_id,
85 if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
86 return kDifBadArg;
87 }
88
89 uint32_t config_value = 0;
90 config_value = bitfield_field32_write(
91 config_value, RV_TIMER_CFG0_PRESCALE_FIELD, params.prescale);
92 config_value = bitfield_field32_write(config_value, RV_TIMER_CFG0_STEP_FIELD,
93 params.tick_step);
94 mmio_region_write32(timer->base_addr,
95 reg_for_hart(hart_id, RV_TIMER_CFG0_REG_OFFSET),
96 config_value);
97
98 return kDifOk;
99}
100
101dif_result_t dif_rv_timer_counter_set_enabled(const dif_rv_timer_t *timer,
102 uint32_t hart_id,
103 dif_toggle_t state) {
104 if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
105 return kDifBadArg;
106 }
107
108 switch (state) {
110 mmio_region_nonatomic_set_bit32(timer->base_addr,
111 RV_TIMER_CTRL_REG_OFFSET, hart_id);
112 break;
114 mmio_region_nonatomic_clear_bit32(timer->base_addr,
115 RV_TIMER_CTRL_REG_OFFSET, hart_id);
116 break;
117 default:
118 return kDifBadArg;
119 }
120
121 return kDifOk;
122}
123
124dif_result_t dif_rv_timer_counter_read(const dif_rv_timer_t *timer,
125 uint32_t hart_id, uint64_t *out) {
126 if (timer == NULL || out == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
127 return kDifBadArg;
128 }
129
130 // We need to read a monotonically increasing, volatile uint64. To do so,
131 // we first read the upper half, then the lower half. Then, we check if the
132 // upper half reads the same value again. If it doesn't, it means that the
133 // lower half overflowed and we need to re-take the measurement.
134 while (true) {
135 uint32_t upper = mmio_region_read32(
136 timer->base_addr,
137 reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
138 uint32_t lower = mmio_region_read32(
139 timer->base_addr,
140 reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET));
141
142 uint32_t overflow_check = mmio_region_read32(
143 timer->base_addr,
144 reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
145
146 if (upper == overflow_check) {
147 *out = (((uint64_t)upper) << 32) | lower;
148 return kDifOk;
149 }
150 }
151}
152
153dif_result_t dif_rv_timer_counter_write(const dif_rv_timer_t *timer,
154 uint32_t hart_id, uint64_t count) {
155 if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
156 return kDifBadArg;
157 }
158
159 // Disable the counter.
160 uint32_t ctrl_reg =
161 mmio_region_read32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET);
162 uint32_t ctrl_reg_cleared = bitfield_bit32_write(ctrl_reg, hart_id, false);
163 mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET,
164 ctrl_reg_cleared);
165
166 // Write the new count.
167 uint32_t lower_count = (uint32_t)count;
168 uint32_t upper_count = count >> 32;
169 mmio_region_write32(timer->base_addr,
170 reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET),
171 lower_count);
172 mmio_region_write32(timer->base_addr,
173 reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET),
174 upper_count);
175
176 // Re-enable the counter (if it was previously enabled).
177 mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET, ctrl_reg);
178
179 return kDifOk;
180}
181
182dif_result_t dif_rv_timer_arm(const dif_rv_timer_t *timer, uint32_t hart_id,
183 uint32_t comp_id, uint64_t threshold) {
184 if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS ||
185 comp_id >= RV_TIMER_PARAM_N_TIMERS) {
186 return kDifBadArg;
187 }
188
189 uint32_t lower = (uint32_t)threshold;
190 uint32_t upper = threshold >> 32;
191
192 ptrdiff_t lower_reg =
193 reg_for_hart(hart_id, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) +
194 (ptrdiff_t)(sizeof(uint64_t) * comp_id);
195 ptrdiff_t upper_reg =
196 reg_for_hart(hart_id, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) +
197 (ptrdiff_t)(sizeof(uint64_t) * comp_id);
198
199 // First, set the upper register to the largest value possible without setting
200 // off the alarm; this way, we can set the lower register without setting
201 // off the alarm.
202 mmio_region_write32(timer->base_addr, upper_reg, UINT32_MAX);
203
204 // This can't set off the alarm because of the value we set above.
205 mmio_region_write32(timer->base_addr, lower_reg, lower);
206 // Finish writing the new value; this may set off an alarm immediately.
207 mmio_region_write32(timer->base_addr, upper_reg, upper);
208
209 return kDifOk;
210}
211
212dif_result_t dif_rv_timer_reset(const dif_rv_timer_t *timer) {
213 if (timer == NULL) {
214 return kDifBadArg;
215 }
216
217 // Disable all counters.
218 mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET, 0x0);
219
220 for (uint32_t hart_id = 0; hart_id < RV_TIMER_PARAM_N_HARTS; ++hart_id) {
221 // Clear and disable all interrupts.
222 ptrdiff_t irq_status =
223 reg_for_hart(hart_id, RV_TIMER_INTR_STATE0_REG_OFFSET);
224 ptrdiff_t irq_enable =
225 reg_for_hart(hart_id, RV_TIMER_INTR_ENABLE0_REG_OFFSET);
226 mmio_region_write32(timer->base_addr, irq_enable, 0x0);
227 mmio_region_write32(timer->base_addr, irq_status, UINT32_MAX);
228
229 // Reset all comparators to their default all-ones state.
230 for (uint32_t comp_id = 0; comp_id < RV_TIMER_PARAM_N_TIMERS; ++comp_id) {
231 dif_result_t error =
232 dif_rv_timer_arm(timer, hart_id, comp_id, UINT64_MAX);
233 if (error != kDifOk) {
234 return error;
235 }
236 }
237
238 // Reset the counter to zero.
239 mmio_region_write32(
240 timer->base_addr,
241 reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
242 mmio_region_write32(
243 timer->base_addr,
244 reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
245 }
246
247 return kDifOk;
248}