blob: 91bd1994c1dfe7e79942ec61dee9a5d49bb4ed65 [file] [log] [blame]
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "hardware/irq.h"
#include "hardware/rtc.h"
#include "hardware/resets.h"
#include "hardware/clocks.h"
// Set this when setting an alarm
static rtc_callback_t _callback = NULL;
static bool _alarm_repeats = false;
bool rtc_running(void) {
return (rtc_hw->ctrl & RTC_CTRL_RTC_ACTIVE_BITS);
}
void rtc_init(void) {
// Get clk_rtc freq and make sure it is running
uint rtc_freq = clock_get_hz(clk_rtc);
assert(rtc_freq != 0);
// Take rtc out of reset now that we know clk_rtc is running
reset_block(RESETS_RESET_RTC_BITS);
unreset_block_wait(RESETS_RESET_RTC_BITS);
// Set up the 1 second divider.
// If rtc_freq is 400 then clkdiv_m1 should be 399
rtc_freq -= 1;
// Check the freq is not too big to divide
assert(rtc_freq <= RTC_CLKDIV_M1_BITS);
// Write divide value
rtc_hw->clkdiv_m1 = rtc_freq;
}
static bool valid_datetime(datetime_t *t) {
// Valid ranges taken from RTC doc. Note when setting an RTC alarm
// these values are allowed to be -1 to say "don't match this value"
if (!(t->year >= 0 && t->year <= 4095)) return false;
if (!(t->month >= 1 && t->month <= 12)) return false;
if (!(t->day >= 1 && t->day <= 31)) return false;
if (!(t->dotw >= 0 && t->dotw <= 6)) return false;
if (!(t->hour >= 0 && t->hour <= 23)) return false;
if (!(t->min >= 0 && t->min <= 59)) return false;
if (!(t->sec >= 0 && t->sec <= 59)) return false;
return true;
}
bool rtc_set_datetime(datetime_t *t) {
if (!valid_datetime(t)) {
return false;
}
// Disable RTC
rtc_hw->ctrl = 0;
// Wait while it is still active
while (rtc_running()) {
tight_loop_contents();
}
// Write to setup registers
rtc_hw->setup_0 = (t->year << RTC_SETUP_0_YEAR_LSB ) |
(t->month << RTC_SETUP_0_MONTH_LSB) |
(t->day << RTC_SETUP_0_DAY_LSB);
rtc_hw->setup_1 = (t->dotw << RTC_SETUP_1_DOTW_LSB) |
(t->hour << RTC_SETUP_1_HOUR_LSB) |
(t->min << RTC_SETUP_1_MIN_LSB) |
(t->sec << RTC_SETUP_1_SEC_LSB);
// Load setup values into rtc clock domain
rtc_hw->ctrl = RTC_CTRL_LOAD_BITS;
// Enable RTC and wait for it to be running
rtc_hw->ctrl = RTC_CTRL_RTC_ENABLE_BITS;
while (!rtc_running()) {
tight_loop_contents();
}
return true;
}
bool rtc_get_datetime(datetime_t *t) {
// Make sure RTC is running
if (!rtc_running()) {
return false;
}
// Note: RTC_0 should be read before RTC_1
t->dotw = (rtc_hw->rtc_0 & RTC_RTC_0_DOTW_BITS ) >> RTC_RTC_0_DOTW_LSB;
t->hour = (rtc_hw->rtc_0 & RTC_RTC_0_HOUR_BITS ) >> RTC_RTC_0_HOUR_LSB;
t->min = (rtc_hw->rtc_0 & RTC_RTC_0_MIN_BITS ) >> RTC_RTC_0_MIN_LSB;
t->sec = (rtc_hw->rtc_0 & RTC_RTC_0_SEC_BITS ) >> RTC_RTC_0_SEC_LSB;
t->year = (rtc_hw->rtc_1 & RTC_RTC_1_YEAR_BITS ) >> RTC_RTC_1_YEAR_LSB;
t->month = (rtc_hw->rtc_1 & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB;
t->day = (rtc_hw->rtc_1 & RTC_RTC_1_DAY_BITS ) >> RTC_RTC_1_DAY_LSB;
return true;
}
static void rtc_enable_alarm(void) {
// Set matching and wait for it to be enabled
hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS);
while(!(rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS)) {
tight_loop_contents();
}
}
static void rtc_irq_handler(void) {
// Always disable the alarm to clear the current IRQ.
// Even if it is a repeatable alarm, we don't want it to keep firing.
// If it matches on a second it can keep firing for that second.
rtc_disable_alarm();
if (_alarm_repeats) {
// If it is a repeatable alarm, re enable the alarm.
rtc_enable_alarm();
}
// Call user callback function
if (_callback) {
_callback();
}
}
static bool rtc_alarm_repeats(datetime_t *t) {
// If any value is set to -1 then we don't match on that value
// hence the alarm will eventually repeat
if (t->year == -1) return true;
if (t->month == -1) return true;
if (t->day == -1) return true;
if (t->dotw == -1) return true;
if (t->hour == -1) return true;
if (t->min == -1) return true;
if (t->sec == -1) return true;
return false;
}
void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback) {
rtc_disable_alarm();
// Only add to setup if it isn't -1
rtc_hw->irq_setup_0 = ((t->year == -1) ? 0 : (t->year << RTC_IRQ_SETUP_0_YEAR_LSB )) |
((t->month == -1) ? 0 : (t->month << RTC_IRQ_SETUP_0_MONTH_LSB)) |
((t->day == -1) ? 0 : (t->day << RTC_IRQ_SETUP_0_DAY_LSB ));
rtc_hw->irq_setup_1 = ((t->dotw == -1) ? 0 : (t->dotw << RTC_IRQ_SETUP_1_DOTW_LSB)) |
((t->hour == -1) ? 0 : (t->hour << RTC_IRQ_SETUP_1_HOUR_LSB)) |
((t->min == -1) ? 0 : (t->min << RTC_IRQ_SETUP_1_MIN_LSB )) |
((t->sec == -1) ? 0 : (t->sec << RTC_IRQ_SETUP_1_SEC_LSB ));
// Set the match enable bits for things we care about
if (t->year != -1) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_YEAR_ENA_BITS);
if (t->month != -1) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MONTH_ENA_BITS);
if (t->day != -1) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_DAY_ENA_BITS);
if (t->dotw != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_DOTW_ENA_BITS);
if (t->hour != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_HOUR_ENA_BITS);
if (t->min != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_MIN_ENA_BITS);
if (t->sec != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_SEC_ENA_BITS);
// Does it repeat? I.e. do we not match on any of the bits
_alarm_repeats = rtc_alarm_repeats(t);
// Store function pointer we can call later
_callback = user_callback;
irq_set_exclusive_handler(RTC_IRQ, rtc_irq_handler);
// Enable the IRQ at the peri
rtc_hw->inte = RTC_INTE_RTC_BITS;
// Enable the IRQ at the proc
irq_set_enabled(RTC_IRQ, true);
rtc_enable_alarm();
}
void rtc_disable_alarm(void) {
// Disable matching and wait for it to stop being active
hw_clear_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS);
while(rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS) {
tight_loop_contents();
}
}