1 #ifndef _HARDWARE_RISCV_PLATFORM_TIMER_
2 #define _HARDWARE_RISCV_PLATFORM_TIMER_
3
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7
8 #include "pico.h"
9 #include "hardware/structs/sio.h"
10
11 /** \file hardware/riscv_platform_timer.h
12 * \defgroup hardware_riscv_platform_timer hardware_riscv_platform_timer
13 *
14 * \brief Accessors for standard RISC-V platform timer (mtime/mtimecmp), available on
15 * Raspberry Pi microcontrollers with RISC-V processors
16 *
17 * Note this header can be used by Arm as well as RISC-V processors, as the
18 * timer is a memory-mapped peripheral external to the processors. The name
19 * refers to this timer being a standard RISC-V peripheral.
20 *
21 */
22
23 /*! \brief Enable or disable the RISC-V platform timer
24 * \ingroup hardware_riscv_platform_timer
25 *
26 * This enables and disables the counting of the RISC-V platform timer. It
27 * does not enable or disable the interrupts, which are asserted
28 * unconditionally when a given core's mtimecmp/mtimecmph registers are
29 * greater than the current 64-bit value of the mtime/mtimeh registers.
30 *
31 * \param enabled Pass true to enable, false to disable
32 */
riscv_timer_set_enabled(bool enabled)33 static inline void riscv_timer_set_enabled(bool enabled) {
34 if (enabled) {
35 // Note atomic rwtype is not supported on SIO
36 sio_hw->mtime_ctrl |= SIO_MTIME_CTRL_EN_BITS;
37 } else {
38 sio_hw->mtime_ctrl &= ~SIO_MTIME_CTRL_EN_BITS;
39 }
40 }
41
42 /*! \brief Configure the RISC-V platform timer to run at full system clock speed
43 * \ingroup hardware_riscv_platform_timer
44 *
45 * \param fullspeed Pass true to increment at system clock speed, false to
46 * increment at the frequency defined by the system tick generator
47 * (the `ticks` block)
48 */
riscv_timer_set_fullspeed(bool fullspeed)49 static inline void riscv_timer_set_fullspeed(bool fullspeed) {
50 if (fullspeed) {
51 sio_hw->mtime_ctrl |= SIO_MTIME_CTRL_FULLSPEED_BITS;
52 } else {
53 sio_hw->mtime_ctrl &= ~SIO_MTIME_CTRL_FULLSPEED_BITS;
54 }
55 }
56
57 /*! \brief Read the RISC-V platform timer
58 * \ingroup hardware_riscv_platform_timer
59 *
60 * \return Current 64-bit mtime value
61 */
riscv_timer_get_mtime(void)62 static inline uint64_t riscv_timer_get_mtime(void) {
63 // Read procedure from RISC-V ISA manual to avoid being off by 2**32 on
64 // low half rollover -- note this loop generally executes only once, and
65 // should never execute more than twice:
66 uint32_t h0, l, h1;
67 do {
68 h0 = sio_hw->mtimeh;
69 l = sio_hw->mtime;
70 h1 = sio_hw->mtimeh;
71 } while (h0 != h1);
72 return l | (uint64_t)h1 << 32;
73 }
74
75 /*! \brief Update the RISC-V platform timer
76 * \ingroup hardware_riscv_platform_timer
77 *
78 * This function should only be called when the timer is disabled via
79 * riscv_timer_set_enabled(). Note also that unlike the mtimecmp comparison
80 * values, mtime is *not* core-local, so updates on one core will be visible
81 * to the other core.
82 *
83 * \param mtime New value to set the RISC-V platform timer to
84 */
riscv_timer_set_mtime(uint64_t mtime)85 static inline void riscv_timer_set_mtime(uint64_t mtime) {
86 // This ought really only be done when the timer is stopped, but we can
87 // make things a bit safer by clearing the low half of the counter, then
88 // writing high half, then low half. This protects against the low half
89 // rolling over, and largely avoids getting an intermediate value that is
90 // higher than either the original or new value, if the timer is running.
91 //
92 // Note that on RP2350, mtime is shared between the two cores!(mtimcemp is
93 // core-local however.)
94 sio_hw->mtime = 0;
95 sio_hw->mtimeh = mtime >> 32;
96 sio_hw->mtime = mtime & 0xffffffffu;
97 }
98
99 /*! \brief Get the current RISC-V platform timer mtimecmp value for this core
100 * \ingroup hardware_riscv_platform_timer
101 *
102 * Get the current mtimecmp value for the calling core. This function is
103 * interrupt-safe as long as timer interrupts only increase the value of
104 * mtimecmp. Otherwise, it must be called with timer interrupts disabled.
105 *
106 * \return Current value of mtimecmp
107 */
riscv_timer_get_mtimecmp(void)108 static inline uint64_t riscv_timer_get_mtimecmp(void) {
109 // Use the same procedure as reading mtime, which should be safe assuming
110 // mtimecmp increases monotonically with successive interrupts.
111 uint32_t h0, l, h1;
112 do {
113 h0 = sio_hw->mtimecmph;
114 l = sio_hw->mtimecmp;
115 h1 = sio_hw->mtimecmph;
116 } while (h0 != h1);
117 return l | (uint64_t)h1 << 32;
118 }
119
120 /*! \brief Set a new RISC-V platform timer interrupt comparison value (mtimecmp) for this core
121 * \ingroup hardware_riscv_platform_timer
122 *
123 * This function updates the mtimecmp value for the current core. The calling
124 * core's RISC-V platform timer interrupt is asserted whenever the 64-bit
125 * mtime value (stored in 32-bit mtime/mtimeh registers) is greater than or
126 * equal to this core's current mtime/mtimecmph value.
127 *
128 * \param mtime New value to set the RISC-V platform timer to
129 */
riscv_timer_set_mtimecmp(uint64_t mtimecmp)130 static inline void riscv_timer_set_mtimecmp(uint64_t mtimecmp) {
131 // Use write procedure from RISC-V ISA manual to avoid causing a spurious
132 // interrupt when updating the two halves of mtimecmp.
133 // No lower than original:
134 sio_hw->mtimecmp = -1u;
135 // No lower than original, no lower than new (assuming new >= original):
136 sio_hw->mtimecmph = mtimecmp >> 32;
137 // Equal to new:
138 sio_hw->mtimecmp = mtimecmp & 0xffffffffu;
139 }
140
141 #ifdef __cplusplus
142 }
143 #endif
144
145 #endif
146