148 lines
5.3 KiB
C
148 lines
5.3 KiB
C
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2020 Joey Castillo
|
|
*
|
|
* 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 "watch_private.h"
|
|
#include "adc.h"
|
|
#include "tcc.h"
|
|
#include "tc.h"
|
|
#include "usb.h"
|
|
#include "system.h"
|
|
|
|
void _watch_init(void) {
|
|
// set frequency to 4 MHz
|
|
set_cpu_frequency(4000000);
|
|
|
|
// disable debugger hot-plugging
|
|
HAL_GPIO_SWCLK_pmuxdis();
|
|
HAL_GPIO_SWCLK_off();
|
|
|
|
// RAM should be back-biased in STANDBY
|
|
PM->STDBYCFG.bit.BBIASHS = 1;
|
|
|
|
// Use switching regulator for lower power consumption.
|
|
SUPC->VREG.bit.SEL = 1;
|
|
|
|
// per Microchip datasheet clarification DS80000782,
|
|
// work around silicon erratum 1.7.2, which causes the microcontroller to lock up on leaving standby:
|
|
// request that the voltage regulator run in standby, and also that it switch to PL0.
|
|
SUPC->VREG.bit.RUNSTDBY = 1;
|
|
SUPC->VREG.bit.STDBYPL0 = 1;
|
|
while(!SUPC->STATUS.bit.VREGRDY); // wait for voltage regulator to become ready
|
|
|
|
// check the battery voltage...
|
|
watch_enable_adc();
|
|
uint16_t battery_voltage = watch_get_vcc_voltage();
|
|
watch_disable_adc();
|
|
// ...because we can enable the more efficient low power regulator if the system voltage is > 2.5V
|
|
// still, enable LPEFF only if the battery voltage is comfortably above this threshold.
|
|
if (battery_voltage >= 2700) {
|
|
SUPC->VREG.bit.LPEFF = 1;
|
|
} else {
|
|
SUPC->VREG.bit.LPEFF = 0;
|
|
}
|
|
|
|
// set up the brownout detector (low battery warning)
|
|
NVIC_DisableIRQ(SYSTEM_IRQn);
|
|
NVIC_ClearPendingIRQ(SYSTEM_IRQn);
|
|
NVIC_EnableIRQ(SYSTEM_IRQn);
|
|
SUPC->BOD33.bit.ENABLE = 0; // BOD33 must be disabled to change its configuration
|
|
SUPC->BOD33.bit.VMON = 0; // Monitor VDD in active and standby mode
|
|
SUPC->BOD33.bit.ACTCFG = 1; // Enable sampling mode when active
|
|
SUPC->BOD33.bit.RUNSTDBY = 1; // Enable sampling mode in standby
|
|
SUPC->BOD33.bit.STDBYCFG = 1; // Run in standby
|
|
SUPC->BOD33.bit.RUNBKUP = 0; // Don't run in backup mode
|
|
SUPC->BOD33.bit.PSEL = 0x9; // Check battery level every second (we'll change this before entering sleep)
|
|
SUPC->BOD33.bit.LEVEL = 34; // Detect brownout at 2.6V (1.445V + level * 34mV)
|
|
SUPC->BOD33.bit.ACTION = 0x2; // Generate an interrupt when BOD33 is triggered
|
|
SUPC->BOD33.bit.HYST = 0; // Disable hysteresis
|
|
while(!SUPC->STATUS.bit.B33SRDY); // wait for BOD33 to sync
|
|
|
|
// Enable interrupt on BOD33 detect
|
|
SUPC->INTENSET.bit.BOD33DET = 1;
|
|
SUPC->BOD33.bit.ENABLE = 1;
|
|
|
|
// External wake depends on RTC; calendar is a required module.
|
|
_watch_rtc_init();
|
|
|
|
// set up state
|
|
btn_alarm_callback = NULL;
|
|
a2_callback = NULL;
|
|
a4_callback = NULL;
|
|
}
|
|
|
|
static inline void _watch_wait_for_entropy() {
|
|
while (!TRNG->INTFLAG.bit.DATARDY);
|
|
}
|
|
|
|
void watch_disable_TRNG(void) {
|
|
// per Microchip datasheet clarification DS80000782,
|
|
// silicon erratum 1.16.1 indicates that the TRNG may leave internal components powered after being disabled.
|
|
// the workaround is to disable the TRNG by clearing the control register, twice.
|
|
TRNG->CTRLA.bit.ENABLE = 0;
|
|
TRNG->CTRLA.bit.ENABLE = 0;
|
|
}
|
|
|
|
// this function is called by arc4random to get entropy for random number generation.
|
|
int getentropy(void *buf, size_t buflen);
|
|
|
|
// let's use the SAM L22's true random number generator to seed the PRNG!
|
|
int getentropy(void *buf, size_t buflen) {
|
|
MCLK->APBCMASK.bit.TRNG_ = 1;
|
|
TRNG->CTRLA.bit.ENABLE = 1;
|
|
|
|
size_t i = 0;
|
|
while(i < buflen / 4) {
|
|
_watch_wait_for_entropy();
|
|
((uint32_t *)buf)[i++] = TRNG->DATA.reg;
|
|
}
|
|
|
|
// but what if they asked for an awkward number of bytes?
|
|
if (buflen % 4) {
|
|
// all good: let's fill in one, two or three bytes at the end of the buffer.
|
|
_watch_wait_for_entropy();
|
|
uint32_t last_little_bit = TRNG->DATA.reg;
|
|
for(size_t j = 0; j <= (buflen % 4); j++) {
|
|
((uint8_t *)buf)[i * 4 + j] = (last_little_bit >> (j * 8)) & 0xFF;
|
|
}
|
|
}
|
|
|
|
watch_disable_TRNG();
|
|
MCLK->APBCMASK.bit.TRNG_ = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void _watch_enable_usb(void) {
|
|
set_cpu_frequency(8000000);
|
|
usb_init();
|
|
usb_enable();
|
|
}
|
|
|
|
void watch_reset_to_bootloader(void) {
|
|
volatile uint32_t *dbl_tap_ptr = ((volatile uint32_t *)(HSRAM_ADDR + HSRAM_SIZE - 4));
|
|
*dbl_tap_ptr = 0xf01669ef; // from the UF2 bootloaer: uf2.h line 255
|
|
NVIC_SystemReset();
|
|
}
|