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