339 lines
12 KiB
C
339 lines
12 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2021 Lucian Copeland for Adafruit Industries
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "py/runtime.h"
|
|
#include "samd/external_interrupts.h"
|
|
#include "eic_handler.h"
|
|
#include "atmel_start_pins.h"
|
|
#include "hal/include/hal_gpio.h"
|
|
// #include <stdio.h>
|
|
|
|
#include "shared-bindings/alarm/pin/PinAlarm.h"
|
|
#include "shared-bindings/microcontroller/__init__.h"
|
|
#include "shared-bindings/microcontroller/Pin.h"
|
|
#include "common-hal/alarm/__init__.h"
|
|
|
|
// This variable stores whether a PinAlarm woke in light sleep or fake deep sleep
|
|
// It CANNOT detect if the program woke from deep sleep.
|
|
STATIC volatile bool woke_up = false;
|
|
STATIC alarm_pin_pinalarm_obj_t *trig_alarm = NULL;
|
|
|
|
// Tamper Pins for deep sleep: IN0:PB00; IN1:PB02; IN2:PA02; IN3:PC00; IN4:PC01;
|
|
// wakeup from deep sleep seems to be triggered when these pins go from Hi/Lo to Hi-Z state.
|
|
typedef struct {
|
|
int n;
|
|
const mcu_pin_obj_t *pin;
|
|
} samd_tamper_pin_t;
|
|
|
|
static samd_tamper_pin_t TAMPER_PINS[] = {
|
|
#if defined(PIN_PB00) && !defined(IGNORE_PIN_PB00)
|
|
{ 0, &pin_PB00 },
|
|
#endif
|
|
#if defined(PIN_PB02) && !defined(IGNORE_PIN_PB02)
|
|
{ 1, &pin_PB02 },
|
|
#endif
|
|
#if defined(PIN_PA02) && !defined(IGNORE_PIN_PA02)
|
|
{ 2, &pin_PA02 },
|
|
#endif
|
|
#if defined(PIN_PC00) && !defined(IGNORE_PIN_PC00)
|
|
{ 3, &pin_PC00 },
|
|
#endif
|
|
#if defined(PIN_PC01) && !defined(IGNORE_PIN_PC01)
|
|
{ 4, &pin_PC01 },
|
|
#endif
|
|
{ -1, NULL }
|
|
};
|
|
|
|
void pin_alarm_callback(uint8_t num) {
|
|
// called back with EIC/RTC interrupt
|
|
if (!SAMD_ALARM_FLAG_PIN_CHK) {
|
|
return;
|
|
}
|
|
woke_up = true;
|
|
// SAMD_ALARM_FLAG_PIN_CLR;
|
|
if (RTC->MODE0.INTFLAG.reg & RTC_MODE0_INTFLAG_TAMPER) { // fake deep sleep
|
|
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_TAMPER;
|
|
trig_alarm = NULL;
|
|
} else { // light sleep
|
|
// Turn off sensing for the channel, or level detection will
|
|
// be tirggered again.
|
|
// If we clear EIC->INTENCLR flag here, cleaning up in deint
|
|
// may fail because MCLK to EIC is stopped earlier by
|
|
// turn_off_eic_channel.
|
|
configure_eic_channel(num, EIC_CONFIG_SENSE0_NONE_Val);
|
|
trig_alarm = get_eic_channel_data(num);
|
|
}
|
|
}
|
|
|
|
void common_hal_alarm_pin_pinalarm_construct(alarm_pin_pinalarm_obj_t *self, const mcu_pin_obj_t *pin, bool value, bool edge, bool pull) {
|
|
self->pin = pin;
|
|
self->value = value;
|
|
self->edge = edge;
|
|
self->pull = pull;
|
|
}
|
|
|
|
const mcu_pin_obj_t *common_hal_alarm_pin_pinalarm_get_pin(alarm_pin_pinalarm_obj_t *self) {
|
|
return self->pin;
|
|
}
|
|
|
|
bool common_hal_alarm_pin_pinalarm_get_value(alarm_pin_pinalarm_obj_t *self) {
|
|
return self->value;
|
|
}
|
|
|
|
bool common_hal_alarm_pin_pinalarm_get_edge(alarm_pin_pinalarm_obj_t *self) {
|
|
return self->edge;
|
|
}
|
|
|
|
bool common_hal_alarm_pin_pinalarm_get_pull(alarm_pin_pinalarm_obj_t *self) {
|
|
return self->pull;
|
|
}
|
|
|
|
bool alarm_pin_pinalarm_woke_this_cycle(void) {
|
|
return woke_up;
|
|
}
|
|
|
|
mp_obj_t alarm_pin_pinalarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms) {
|
|
for (size_t i = 0; i < n_alarms; i++) {
|
|
if (!mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) {
|
|
continue;
|
|
}
|
|
alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]);
|
|
if (alarm == trig_alarm) {
|
|
return alarms[i];
|
|
}
|
|
}
|
|
return mp_const_none;
|
|
}
|
|
|
|
mp_obj_t alarm_pin_pinalarm_create_wakeup_alarm(uint32_t TAMPID) {
|
|
// Create tamper alarm that caused wakeup from deep sleep
|
|
|
|
for (samd_tamper_pin_t *t = TAMPER_PINS; t->n >= 0; t++) {
|
|
if (TAMPID & (1 << t->n)) {
|
|
alarm_pin_pinalarm_obj_t *alarm = m_new_obj(alarm_pin_pinalarm_obj_t);
|
|
alarm->base.type = &alarm_pin_pinalarm_type;
|
|
alarm->pin = t->pin;
|
|
return alarm;
|
|
}
|
|
}
|
|
return mp_const_none;
|
|
}
|
|
|
|
void alarm_pin_pinalarm_reset(void) {
|
|
SAMD_ALARM_FLAG_PIN_CLR;
|
|
woke_up = false;
|
|
// Disable TAMPER interrupt
|
|
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_TAMPER;
|
|
if (RTC->MODE0.TAMPCTRL.reg != 0) {
|
|
common_hal_mcu_disable_interrupts();
|
|
RTC->MODE0.CTRLA.bit.ENABLE = 0; // Disable the RTC
|
|
while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization
|
|
}
|
|
RTC->MODE0.TAMPCTRL.reg = 0; // reset everything
|
|
RTC->MODE0.CTRLA.bit.ENABLE = 1; // Enable the RTC
|
|
while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization
|
|
}
|
|
common_hal_mcu_enable_interrupts();
|
|
}
|
|
}
|
|
|
|
#define PINALARM_NOERR 0
|
|
#define PINALARM_ERR_NOT_FREE -1
|
|
#define PINALARM_ERR_NOEXTINT -2
|
|
#define PINALARM_ERR_NOCHANNEL -3
|
|
#define PINALARM_ERR_NOTTAMPER -4
|
|
|
|
// Because PinAlarm does not have deinit, pins are furnished just
|
|
// before the light sleep here, and deinited after wake-up.
|
|
static void pinalarm_set_alarms_light(size_t n_alarms, const mp_obj_t *alarms) {
|
|
int err = PINALARM_NOERR;
|
|
size_t i;
|
|
const mcu_pin_obj_t *pin;
|
|
|
|
for (i = 0; i < n_alarms; i++) {
|
|
if (!mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) {
|
|
continue;
|
|
}
|
|
alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]);
|
|
pin = alarm->pin;
|
|
|
|
if (!pin_number_is_free(pin->number)) {
|
|
err = PINALARM_ERR_NOT_FREE;
|
|
break;
|
|
}
|
|
if (!pin->has_extint) {
|
|
err = PINALARM_ERR_NOEXTINT;
|
|
break;
|
|
}
|
|
if (eic_get_enable()) {
|
|
if (!eic_channel_free(pin->extint_channel)) {
|
|
err = PINALARM_ERR_NOCHANNEL;
|
|
break;
|
|
}
|
|
} else {
|
|
turn_on_external_interrupt_controller();
|
|
// It is automatically turned-off in turn_off_eic_channel.
|
|
}
|
|
|
|
SAMD_ALARM_FLAG_PIN_SET;
|
|
gpio_set_pin_function(pin->number, GPIO_PIN_FUNCTION_A);
|
|
uint32_t sense;
|
|
if (alarm->value) {
|
|
sense = alarm->edge ? EIC_CONFIG_SENSE0_RISE_Val : EIC_CONFIG_SENSE0_HIGH_Val;
|
|
if (alarm->pull) {
|
|
// detect rising edge means pull down
|
|
gpio_set_pin_pull_mode(pin->number, GPIO_PULL_DOWN);
|
|
}
|
|
} else {
|
|
sense = alarm->edge ? EIC_CONFIG_SENSE0_FALL_Val : EIC_CONFIG_SENSE0_LOW_Val;
|
|
if (alarm->pull) {
|
|
// detect falling edge means pull up
|
|
gpio_set_pin_pull_mode(pin->number, GPIO_PULL_UP);
|
|
}
|
|
}
|
|
|
|
set_eic_channel_data(pin->extint_channel, (void *)alarm);
|
|
claim_pin(pin);
|
|
set_eic_handler(pin->extint_channel, EIC_HANDLER_ALARM);
|
|
turn_on_eic_channel(pin->extint_channel, sense);
|
|
}
|
|
|
|
if (err == PINALARM_NOERR) {
|
|
return;
|
|
}
|
|
|
|
SAMD_ALARM_FLAG_PIN_CLR;
|
|
alarm_pin_pinalarm_deinit_alarms(i, alarms);
|
|
|
|
switch (err) {
|
|
case PINALARM_ERR_NOT_FREE:
|
|
assert_pin_free(pin);
|
|
// raise ValueError here
|
|
MP_FALLTHROUGH
|
|
case PINALARM_ERR_NOEXTINT:
|
|
raise_ValueError_invalid_pin();
|
|
case PINALARM_ERR_NOCHANNEL:
|
|
mp_raise_RuntimeError(translate("A hardware interrupt channel is already in use"));
|
|
default:
|
|
mp_raise_RuntimeError(translate("Unknown reason."));
|
|
}
|
|
}
|
|
|
|
static void pinalarm_set_alarms_deep(size_t n_alarms, const mp_obj_t *alarms) {
|
|
// In case of deep sleep, pin_number_is_free is not checked.
|
|
// Errata says that falling edge should be detected for the tamper pins.
|
|
samd_tamper_pin_t *t;
|
|
uint32_t tampctrl = 0;
|
|
|
|
for (size_t i = 0; i < n_alarms; i++) {
|
|
if (!mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) {
|
|
continue;
|
|
}
|
|
alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]);
|
|
const mcu_pin_obj_t *pin = alarm->pin;
|
|
|
|
for (t = TAMPER_PINS; t->n >= 0; t++) {
|
|
if (pin == t->pin) {
|
|
break;
|
|
}
|
|
}
|
|
if (t->n < 0) {
|
|
raise_ValueError_invalid_pin();
|
|
}
|
|
|
|
// It is strange, but to my experiment, interrupt during sleep
|
|
// is triggered on transition from Hi/Lo to Hi-Z state, and
|
|
// not on switching from Hi/Lo to Lo/Hi state, regardless of
|
|
// TAMLVL value. If tested on non-sleep condition, TAMPID
|
|
// reacts either on falling or rising edge, according to
|
|
// TAMLVL value as doumented.
|
|
tampctrl |= (((1UL << t->n) << 24) | // DEBNCn
|
|
((alarm->value << t->n) << 16) | // TAMLVLn
|
|
(1UL << t->n * 2)); // INnACT
|
|
|
|
// Tamper pins under the control of RTC are kept in Hi-Z
|
|
// state, and pull mode is not effective.
|
|
#if 0
|
|
if (alarm->pull) {
|
|
if (alarm->value) {
|
|
gpio_set_pin_pull_mode(pin->number, GPIO_PULL_DOWN);
|
|
} else {
|
|
gpio_set_pin_pull_mode(pin->number, GPIO_PULL_UP);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
if (tampctrl == 0) {
|
|
return;
|
|
}
|
|
|
|
SAMD_ALARM_FLAG_PIN_SET;
|
|
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_TAMPER;
|
|
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_TAMPER;
|
|
common_hal_mcu_disable_interrupts();
|
|
RTC->MODE0.CTRLA.bit.ENABLE = 0; // Disable the RTC
|
|
while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization
|
|
}
|
|
RTC->MODE0.TAMPCTRL.reg = tampctrl;
|
|
RTC->MODE0.CTRLA.bit.ENABLE = 1; // Enable the RTC
|
|
while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization
|
|
}
|
|
common_hal_mcu_enable_interrupts();
|
|
}
|
|
|
|
void alarm_pin_pinalarm_deinit_alarms(size_t n_alarms, const mp_obj_t *alarms) {
|
|
for (size_t i = 0; i < n_alarms; i++) {
|
|
if (!mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) {
|
|
continue;
|
|
}
|
|
alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]);
|
|
const mcu_pin_obj_t *pin = alarm->pin;
|
|
|
|
set_eic_handler(pin->extint_channel, EIC_HANDLER_NO_INTERRUPT);
|
|
// Set sense = 0 or INTFLAG may be set on reset_pin_number.
|
|
configure_eic_channel(pin->extint_channel, EIC_CONFIG_SENSE0_NONE_Val);
|
|
turn_off_eic_channel(pin->extint_channel);
|
|
reset_pin_number(pin->number);
|
|
}
|
|
}
|
|
|
|
void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) {
|
|
trig_alarm = NULL;
|
|
if (deep_sleep) {
|
|
pinalarm_set_alarms_deep(n_alarms, alarms);
|
|
} else {
|
|
pinalarm_set_alarms_light(n_alarms, alarms);
|
|
}
|
|
}
|
|
|
|
void alarm_pin_pinalarm_prepare_for_deep_sleep(void) {
|
|
// TODO: This function is called after the VM reset, right before
|
|
// the program actually enters deep sleep. If the VM reset
|
|
// (see port_reset) resets any pins/EVSYS settings/RTC values
|
|
// needed by the pinalarms, this function is responsible for setting them
|
|
// back up again.
|
|
}
|