github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/machine/machine_rp2040_rtc.go (about)

     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  }