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();
}