|
| 1 | +//go:build rp2040 |
| 2 | + |
| 3 | +// Implementation based on code located here: |
| 4 | +// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c |
| 5 | + |
| 6 | +package machine |
| 7 | + |
| 8 | +import ( |
| 9 | + "device/rp" |
| 10 | + "errors" |
| 11 | + "runtime/interrupt" |
| 12 | + "unsafe" |
| 13 | +) |
| 14 | + |
| 15 | +type rtcType rp.RTC_Type |
| 16 | + |
| 17 | +type rtcTime struct { |
| 18 | + Year int16 |
| 19 | + Month int8 |
| 20 | + Day int8 |
| 21 | + Dotw int8 |
| 22 | + Hour int8 |
| 23 | + Min int8 |
| 24 | + Sec int8 |
| 25 | +} |
| 26 | + |
| 27 | +var RTC = (*rtcType)(unsafe.Pointer(rp.RTC)) |
| 28 | + |
| 29 | +const ( |
| 30 | + second = 1 |
| 31 | + minute = 60 * second |
| 32 | + hour = 60 * minute |
| 33 | + day = 24 * hour |
| 34 | +) |
| 35 | + |
| 36 | +var ( |
| 37 | + rtcAlarmRepeats bool |
| 38 | + rtcCallback func() |
| 39 | + rtcEpoch = rtcTime{ |
| 40 | + Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0, |
| 41 | + } |
| 42 | +) |
| 43 | + |
| 44 | +var ( |
| 45 | + ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second") |
| 46 | + ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day") |
| 47 | +) |
| 48 | + |
| 49 | +// SetInterrupt configures delayed and optionally recurring interrupt by real time clock. |
| 50 | +// |
| 51 | +// Delay is specified in whole seconds, allowed range depends on platform. |
| 52 | +// Zero delay disables previously configured interrupt, if any. |
| 53 | +// |
| 54 | +// RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted. |
| 55 | +func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error { |
| 56 | + |
| 57 | + // Verify delay range |
| 58 | + if delay > day { |
| 59 | + return ErrRtcDelayTooLarge |
| 60 | + } |
| 61 | + |
| 62 | + // De-configure delayed interrupt if delay is zero |
| 63 | + if delay == 0 { |
| 64 | + rtc.disableInterruptMatch() |
| 65 | + return nil |
| 66 | + } |
| 67 | + |
| 68 | + // Configure delayed interrupt |
| 69 | + rtc.setDivider() |
| 70 | + |
| 71 | + rtcAlarmRepeats = repeat |
| 72 | + rtcCallback = callback |
| 73 | + |
| 74 | + err := rtc.setTime(rtcEpoch) |
| 75 | + if err != nil { |
| 76 | + return err |
| 77 | + } |
| 78 | + rtc.setAlarm(toAlarmTime(delay), callback) |
| 79 | + |
| 80 | + return nil |
| 81 | +} |
| 82 | + |
| 83 | +func toAlarmTime(delay uint32) rtcTime { |
| 84 | + result := rtcEpoch |
| 85 | + remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early |
| 86 | + if remainder >= hour { |
| 87 | + result.Hour = int8(remainder / hour) |
| 88 | + remainder %= hour |
| 89 | + } |
| 90 | + if remainder >= minute { |
| 91 | + result.Min = int8(remainder / minute) |
| 92 | + remainder %= minute |
| 93 | + } |
| 94 | + result.Sec = int8(remainder) |
| 95 | + return result |
| 96 | +} |
| 97 | + |
| 98 | +func (rtc *rtcType) setDivider() { |
| 99 | + // Get clk_rtc freq and make sure it is running |
| 100 | + rtcFreq := configuredFreq[clkRTC] |
| 101 | + if rtcFreq == 0 { |
| 102 | + panic("can not set RTC divider, clock is not running") |
| 103 | + } |
| 104 | + |
| 105 | + // Take rtc out of reset now that we know clk_rtc is running |
| 106 | + resetBlock(rp.RESETS_RESET_RTC) |
| 107 | + unresetBlockWait(rp.RESETS_RESET_RTC) |
| 108 | + |
| 109 | + // Set up the 1 second divider. |
| 110 | + // If rtc_freq is 400 then clkdiv_m1 should be 399 |
| 111 | + rtcFreq -= 1 |
| 112 | + |
| 113 | + // Check the freq is not too big to divide |
| 114 | + if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk { |
| 115 | + panic("can not set RTC divider, clock frequency is too big to divide") |
| 116 | + } |
| 117 | + |
| 118 | + // Write divide value |
| 119 | + rtc.CLKDIV_M1.Set(rtcFreq) |
| 120 | +} |
| 121 | + |
| 122 | +// setTime configures RTC with supplied time, initialises and activates it. |
| 123 | +func (rtc *rtcType) setTime(t rtcTime) error { |
| 124 | + |
| 125 | + // Disable RTC and wait while it is still running |
| 126 | + rtc.CTRL.Set(0) |
| 127 | + for rtc.isActive() { |
| 128 | + } |
| 129 | + |
| 130 | + rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) | |
| 131 | + (uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) | |
| 132 | + (uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)) |
| 133 | + |
| 134 | + rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) | |
| 135 | + (uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) | |
| 136 | + (uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) | |
| 137 | + (uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)) |
| 138 | + |
| 139 | + // Load setup values into RTC clock domain |
| 140 | + rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD) |
| 141 | + |
| 142 | + // Enable RTC and wait for it to be running |
| 143 | + rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE) |
| 144 | + for !rtc.isActive() { |
| 145 | + } |
| 146 | + |
| 147 | + return nil |
| 148 | +} |
| 149 | + |
| 150 | +func (rtc *rtcType) isActive() bool { |
| 151 | + return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE) |
| 152 | +} |
| 153 | + |
| 154 | +// setAlarm configures alarm in RTC and arms it. |
| 155 | +// The callback is executed in the context of an interrupt handler, |
| 156 | +// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc. |
| 157 | +func (rtc *rtcType) setAlarm(t rtcTime, callback func()) { |
| 158 | + |
| 159 | + rtc.disableInterruptMatch() |
| 160 | + |
| 161 | + // Clear all match enable bits |
| 162 | + rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA | rp.RTC_IRQ_SETUP_0_MONTH_ENA | rp.RTC_IRQ_SETUP_0_DAY_ENA) |
| 163 | + rtc.IRQ_SETUP_1.ClearBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA | rp.RTC_IRQ_SETUP_1_HOUR_ENA | rp.RTC_IRQ_SETUP_1_MIN_ENA | rp.RTC_IRQ_SETUP_1_SEC_ENA) |
| 164 | + |
| 165 | + // Only add to setup if it isn't -1 and set the match enable bits for things we care about |
| 166 | + if t.Year >= 0 { |
| 167 | + rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) |
| 168 | + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA) |
| 169 | + } |
| 170 | + |
| 171 | + if t.Month >= 0 { |
| 172 | + rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) |
| 173 | + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA) |
| 174 | + } |
| 175 | + |
| 176 | + if t.Day >= 0 { |
| 177 | + rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos) |
| 178 | + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA) |
| 179 | + } |
| 180 | + |
| 181 | + if t.Dotw >= 0 { |
| 182 | + rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) |
| 183 | + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA) |
| 184 | + } |
| 185 | + |
| 186 | + if t.Hour >= 0 { |
| 187 | + rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) |
| 188 | + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA) |
| 189 | + } |
| 190 | + |
| 191 | + if t.Min >= 0 { |
| 192 | + rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) |
| 193 | + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA) |
| 194 | + } |
| 195 | + |
| 196 | + if t.Sec >= 0 { |
| 197 | + rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos) |
| 198 | + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA) |
| 199 | + } |
| 200 | + |
| 201 | + // Enable the IRQ at the proc |
| 202 | + interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable() |
| 203 | + |
| 204 | + // Enable the IRQ at the peri |
| 205 | + rtc.INTE.Set(rp.RTC_INTE_RTC) |
| 206 | + |
| 207 | + rtc.enableInterruptMatch() |
| 208 | +} |
| 209 | + |
| 210 | +func (rtc *rtcType) enableInterruptMatch() { |
| 211 | + // Set matching and wait for it to be enabled |
| 212 | + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) |
| 213 | + for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { |
| 214 | + } |
| 215 | +} |
| 216 | + |
| 217 | +func (rtc *rtcType) disableInterruptMatch() { |
| 218 | + // Disable matching and wait for it to stop being active |
| 219 | + rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) |
| 220 | + for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { |
| 221 | + } |
| 222 | +} |
| 223 | + |
| 224 | +func rtcHandleInterrupt(itr interrupt.Interrupt) { |
| 225 | + // Always disable the alarm to clear the current IRQ. |
| 226 | + // Even if it is a repeatable alarm, we don't want it to keep firing. |
| 227 | + // If it matches on a second it can keep firing for that second. |
| 228 | + RTC.disableInterruptMatch() |
| 229 | + |
| 230 | + // Call user callback function |
| 231 | + if rtcCallback != nil { |
| 232 | + rtcCallback() |
| 233 | + } |
| 234 | + |
| 235 | + if rtcAlarmRepeats { |
| 236 | + // If it is a repeatable alarm, reset time and re-enable the alarm. |
| 237 | + RTC.setTime(rtcEpoch) |
| 238 | + RTC.enableInterruptMatch() |
| 239 | + } |
| 240 | +} |
0 commit comments