From e45fdf15afc2803b67d7a0bff15922315c1e11e7 Mon Sep 17 00:00:00 2001
From: Joey Castillo <jose.castillo@gmail.com>
Date: Sun, 8 Aug 2021 15:02:38 -0400
Subject: work on RTC tamper interrupt and external wake

---
 watch-library/config/hpl_rtc_config.h    |  8 +--
 watch-library/hal/include/hpl_calendar.h |  9 ++--
 watch-library/hpl/rtc/hpl_rtc.c          | 11 +++--
 watch-library/watch/watch.c              | 84 +++++++++++++++++++++++---------
 watch-library/watch/watch.h              | 29 +++++++++--
 5 files changed, 101 insertions(+), 40 deletions(-)

(limited to 'watch-library')

diff --git a/watch-library/config/hpl_rtc_config.h b/watch-library/config/hpl_rtc_config.h
index 582a1c23..2c478111 100644
--- a/watch-library/config/hpl_rtc_config.h
+++ b/watch-library/config/hpl_rtc_config.h
@@ -48,7 +48,7 @@
 // <e> RTC Tamper Input 0 settings
 // <id> tamper_input_0_settings
 #ifndef CONF_TAMPER_INPUT_0_SETTINGS
-#define CONF_TAMPER_INPUT_0_SETTINGS 0
+#define CONF_TAMPER_INPUT_0_SETTINGS 1
 #endif
 
 // <q> Tamper Level Settings
@@ -66,7 +66,7 @@
 // <i> These bits define the RTC Tamper Input Action to be performed
 // <id> rtc_tamper_input_action_0
 #ifndef CONF_RTC_TAMPER_INACT_0
-#define CONF_RTC_TAMPER_INACT_0 0
+#define CONF_RTC_TAMPER_INACT_0 1
 #endif
 
 // <q> Debounce Enable for Tamper Input
@@ -81,7 +81,7 @@
 // <e> RTC Tamper Input 1 settings
 // <id> tamper_input_1_settings
 #ifndef CONF_TAMPER_INPUT_1_SETTINGS
-#define CONF_TAMPER_INPUT_1_SETTINGS 0
+#define CONF_TAMPER_INPUT_1_SETTINGS 1
 #endif
 
 // <q> Tamper Level Settings
@@ -99,7 +99,7 @@
 // <i> These bits define the RTC Tamper Input Action to be performed
 // <id> rtc_tamper_input_action_1
 #ifndef CONF_RTC_TAMPER_INACT_1
-#define CONF_RTC_TAMPER_INACT_1 0
+#define CONF_RTC_TAMPER_INACT_1 1
 #endif
 
 // <q> Debounce Enable for Tamper Input
diff --git a/watch-library/hal/include/hpl_calendar.h b/watch-library/hal/include/hpl_calendar.h
index 87b1a5a8..f94249b9 100644
--- a/watch-library/hal/include/hpl_calendar.h
+++ b/watch-library/hal/include/hpl_calendar.h
@@ -77,7 +77,8 @@ enum calendar_alarm_mode { ONESHOT = 1, REPEAT };
 /**
  * \brief Prototype of callback on alarm match
  */
-typedef void (*calendar_drv_cb_t)(struct calendar_dev *const dev);
+typedef void (*calendar_drv_cb_t)();
+typedef void (*calendar_drv_extwake_cb_t)(uint8_t reason);
 
 /**
  * \brief Structure of Calendar instance
@@ -88,8 +89,8 @@ struct calendar_dev {
 	/** Alarm match callback */
 	calendar_drv_cb_t callback_alarm;
 	/** Tamper callback */
-	calendar_drv_cb_t callback_tamper;
-	/** Tamper callback */
+	calendar_drv_extwake_cb_t callback_tamper;
+	/** Tick callback */
 	calendar_drv_cb_t callback_tick;
 	/** IRQ struct */
 	struct _irq_descriptor irq;
@@ -260,7 +261,7 @@ int32_t _prescaler_register_callback(struct calendar_dev *const dev, calendar_dr
  *
  * \return ERR_NONE on success, or an error code on failure.
  */
-int32_t _extwake_register_callback(struct calendar_dev *const dev, calendar_drv_cb_t callback);
+int32_t _extwake_register_callback(struct calendar_dev *const dev, calendar_drv_extwake_cb_t callback);
 
 /**
  * \brief Find tamper is detected on specified pin
diff --git a/watch-library/hpl/rtc/hpl_rtc.c b/watch-library/hpl/rtc/hpl_rtc.c
index 0d119da1..437449ce 100644
--- a/watch-library/hpl/rtc/hpl_rtc.c
+++ b/watch-library/hpl/rtc/hpl_rtc.c
@@ -327,8 +327,7 @@ int32_t _prescaler_register_callback(struct calendar_dev *const dev, calendar_dr
 	return ERR_NONE;
 }
 
-// TODO: refactor this so it doesn't take a callback (it will never get called anyway)
-int32_t _extwake_register_callback(struct calendar_dev *const dev, calendar_drv_cb_t callback)
+int32_t _extwake_register_callback(struct calendar_dev *const dev, calendar_drv_extwake_cb_t callback)
 {
 	ASSERT(dev && dev->hw);
 
@@ -389,16 +388,20 @@ static void _rtc_interrupt_handler(struct calendar_dev *dev)
 	uint16_t interrupt_enabled = hri_rtcmode0_read_INTEN_reg(dev->hw);
 
 	if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_ALARM0) {
-		dev->callback_alarm(dev);
+		dev->callback_alarm();
 
 		/* Clear interrupt flag */
 		hri_rtcmode0_clear_interrupt_CMP0_bit(dev->hw);
 	} else if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_PER7) {
-		dev->callback_tick(dev);
+		dev->callback_tick();
 
 		/* Clear interrupt flag */
 		hri_rtcmode0_clear_interrupt_PER7_bit(dev->hw);
 	} else if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_TAMPER) {
+		uint8_t reason = hri_rtc_get_TAMPID_reg(dev->hw, 0x1F);
+		dev->callback_tamper(reason);
+		hri_rtc_write_TAMPID_reg(dev->hw, reason);
+
 		/* Clear interrupt flag */
 		hri_rtcmode0_clear_interrupt_TAMPER_bit(dev->hw);
 	}
diff --git a/watch-library/watch/watch.c b/watch-library/watch/watch.c
index 4c891d65..170c4747 100644
--- a/watch-library/watch/watch.c
+++ b/watch-library/watch/watch.c
@@ -1,6 +1,16 @@
 #include "watch.h"
 #include <stdlib.h>
 
+//////////////////////////////////////////////////////////////////////////////////////////
+// User callbacks and other definitions
+
+ext_irq_cb_t btn_alarm_callback;
+ext_irq_cb_t a2_callback;
+ext_irq_cb_t d1_callback;
+
+static void extwake_callback(uint8_t reason);
+
+
 //////////////////////////////////////////////////////////////////////////////////////////
 // Initialization
 
@@ -15,7 +25,14 @@ void _watch_init() {
 
     // Not sure if this belongs in every app -- is there a power impact?
     delay_driver_init();
+
+    // set up state
+    btn_alarm_callback = NULL;
+    a2_callback = NULL;
+    d1_callback = NULL;
 }
+
+
 //////////////////////////////////////////////////////////////////////////////////////////
 // Segmented Display
 
@@ -215,8 +232,16 @@ void watch_enable_buttons() {
     EXTERNAL_IRQ_0_init();
 }
 
-void watch_register_button_callback(const uint32_t pin, ext_irq_cb_t callback) {
-    ext_irq_register(pin, callback);
+void watch_register_button_callback(const uint8_t pin, ext_irq_cb_t callback) {
+    if (pin == BTN_ALARM) {
+        gpio_set_pin_direction(BTN_ALARM, GPIO_DIRECTION_IN);
+        gpio_set_pin_pull_mode(BTN_ALARM, GPIO_PULL_DOWN);
+        gpio_set_pin_function(BTN_ALARM, PINMUX_PA02G_RTC_IN2);
+        btn_alarm_callback = callback;
+        _extwake_register_callback(&CALENDAR_0.device, extwake_callback);
+    } else {
+        ext_irq_register(pin, callback);
+    }
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -310,15 +335,8 @@ void watch_get_date_time(struct calendar_date_time *date_time) {
     calendar_get_date_time(&CALENDAR_0, date_time);
 }
 
-static ext_irq_cb_t tick_user_callback;
-
-static void tick_callback(struct calendar_dev *const dev) {
-    tick_user_callback();
-}
-
 void watch_register_tick_callback(ext_irq_cb_t callback) {
-    tick_user_callback = callback;
-    _prescaler_register_callback(&CALENDAR_0.device, &tick_callback);
+    _prescaler_register_callback(&CALENDAR_0.device, callback);
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -448,6 +466,32 @@ uint32_t watch_i2c_read32(int16_t addr, uint8_t reg) {
 //////////////////////////////////////////////////////////////////////////////////////////
 // Deep Sleep
 
+static void extwake_callback(uint8_t reason) {
+    if (reason & RTC_TAMPID_TAMPID2) {
+        if (btn_alarm_callback != NULL) btn_alarm_callback();
+    } else if (reason & RTC_TAMPID_TAMPID1) {
+        if (a2_callback != NULL) a2_callback();
+    } else if (reason & RTC_TAMPID_TAMPID0) {
+        if (d1_callback != NULL) d1_callback();
+    }
+}
+
+void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback) {
+    uint32_t pinmux;
+    if (pin == D1) {
+        d1_callback = callback;
+        pinmux = PINMUX_PB00G_RTC_IN0;
+    } else if (pin == A2) {
+        a2_callback = callback;
+        pinmux = PINMUX_PB02G_RTC_IN1;
+    } else {
+        return;
+    }
+    gpio_set_pin_direction(pin, GPIO_DIRECTION_IN);
+    gpio_set_pin_function(pin, pinmux);
+    _extwake_register_callback(&CALENDAR_0.device, extwake_callback);
+}
+
 void watch_store_backup_data(uint32_t data, uint8_t reg) {
     if (reg < 8) {
         RTC->MODE0.BKUP[reg].reg = data;
@@ -462,14 +506,14 @@ uint32_t watch_get_backup_data(uint8_t reg) {
     return 0;
 }
 
-static void extwake_callback(struct calendar_dev *const dev) {
-    // this will never get called since we are basically waking from reset
-}
-
 void watch_enter_deep_sleep() {
-    // enable and configure the external wake interrupt
-    _extwake_register_callback(&CALENDAR_0.device, &extwake_callback);
-    _tamper_enable_debounce_asynchronous(&CALENDAR_0.device);
+    // enable and configure the external wake interrupt, if not already set up.
+    if (btn_alarm_callback == NULL && a2_callback == NULL && d1_callback == NULL) {
+        gpio_set_pin_direction(BTN_ALARM, GPIO_DIRECTION_IN);
+        gpio_set_pin_pull_mode(BTN_ALARM, GPIO_PULL_DOWN);
+        gpio_set_pin_function(BTN_ALARM, PINMUX_PA02G_RTC_IN2);
+        _extwake_register_callback(&CALENDAR_0.device, extwake_callback);
+    }
 
     // disable SLCD
     slcd_sync_deinit(&SEGMENT_LCD_0);
@@ -477,12 +521,6 @@ void watch_enter_deep_sleep() {
 
     // TODO: disable other peripherals
 
-    // disable EIC interrupt on ALARM pin (if any) and enable RTC interrupt.
-    ext_irq_disable(BTN_ALARM);
-    gpio_set_pin_direction(BTN_ALARM, GPIO_DIRECTION_IN);
-    gpio_set_pin_pull_mode(BTN_ALARM, GPIO_PULL_DOWN);
-    gpio_set_pin_function(BTN_ALARM, PINMUX_PA02G_RTC_IN2);
-
     // go into backup sleep mode
     sleep(5);
 }
diff --git a/watch-library/watch/watch.h b/watch-library/watch/watch.h
index 8cb949f6..29881292 100644
--- a/watch-library/watch/watch.h
+++ b/watch-library/watch/watch.h
@@ -256,16 +256,18 @@ void watch_enable_analog(const uint8_t pin);
   * @brief This section covers functions related to the three buttons: Light, Mode and Alarm.
   */
 /// @{
-/** @brief Enables the external interrupt controller.
+/** @brief Enables the external interrupt controller for use with the buttons.
+  * @note The BTN_ALARM button runs off of an interrupt in the the RTC controller, not the EIC. If your
+  *       application ONLY makes use of the alarm button, you do not need to call this method; you can
+  *       save ~5µA by leaving the EIC disabled and only registering a callback for BTN_ALARM.
   */
 void watch_enable_buttons();
 
 /** @brief Configures an external interrupt
   * @param pin One of pins BTN_LIGHT, BTN_MODE or BTN_ALARM.
   * @param callback The function you wish to have called when the button is pressed.
-  * @todo Make the alarm interrupt use the RTC tamper interrupt instead of the EIC.
   */
-void watch_register_button_callback(const uint32_t pin, ext_irq_cb_t callback);
+void watch_register_button_callback(const uint8_t pin, ext_irq_cb_t callback);
 /// @}
 
 
@@ -391,7 +393,18 @@ uint32_t watch_i2c_read32(int16_t addr, uint8_t reg);
   *        deepest sleep mode available on the SAM L22
   */
 /// @{
-/** @brief Stores 32 bits of data in the RTC's backup register, which retains its data in deep sleep.
+/** @brief Registers a callback on one of the RTC's external wake pins, which can wake the device
+  * from deep sleep mode.
+  * @param pin Either pin A2 or pin D1, the two external wake pins on the nine-pin connector.
+  * @param callback The callback to be called if this pin triggers outside of deep sleep mode.
+  * @note When in normal or STANDBY mode, this will function much like a standard external interrupt
+  *       situation: these pins will wake from standby, and your callback will be called. However,
+  *       if the device enters deep sleep and one of these pins wakes the device, your callback
+  *       WILL NOT be called.
+  */
+void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback);
+
+/** @brief Stores data in one of the RTC's backup registers, which retain their data in deep sleep.
   * @param data An unsigned 32 bit integer with the data you wish to store.
   * @param reg A register from 0-7.
   */
@@ -413,7 +426,13 @@ uint32_t watch_get_backup_data(uint8_t reg);
   *       in ACTIVE, IDLE or STANDBY modes, but it *will not be called* when waking from BACKUP.
   *       Waking from backup is effectively like waking from reset, except that your @ref
   *       app_wake_from_deep_sleep function will be called.
-  * @warning still kind of glitchy!
+  * @warning In initial testing, it seems like the ALARM_BTN pin (PA02 RTC/IN2) cannot wake the device
+             from deep sleep mode. There is an errata note (Reference: 15010, linked) that says that
+             due to a silicon bug, PB01 cannot be used as RTC/IN2. It seems though that this bug may
+             also affect PA02. As a result — and I'm very bummed about this — you cannot use deep sleep
+             mode unless you set up an external wake interrupt using a device on the nine-pin connector
+             (i.e. an accelerometer with an interrupt pin). Otherwise your only option for waking will
+             be to unscrew the watch case and press the reset button on the back of the board.
   */
 void watch_enter_deep_sleep();
 /// @}
-- 
cgit v1.2.3