/** @file bkp.c
 * Module handling the Backup (BKP) domain functionalities.
 *
 * This module includes management of the Real Time Clock (RTC), Tamper
 * protection system and a limited space (20 bytes) for persistent data storage
 *
 * All features present in this module stay running in standby mode / when
 * no-longer powered, so long as VBAT is maintained
 */

//--includes--------------------------------------------------------------------

#include "bkp.h"
#include "bkp_regs.h"
#include "rcc.h"
#include "nvic.h"


//--local definitions-----------------------------------------------------------

static uint32_t compute_prescaler(uint32_t period_ms,
		enum BkpRtcClockSrc clock_src);
static void lsi_calib_callback(enum TimIRQSource src);


//--local variables-------------------------------------------------------------

static volatile struct BKP* bkp_regs = (struct BKP*)BKP_BASE_ADDRESS;
static volatile struct RCC* rcc_regs = (struct RCC*)RCC_BASE_ADDRESS;
static volatile struct RTC* rtc_regs = (struct RTC*)RTC_BASE_ADDRESS;

static BkpRtcCallback rtc_callback;


//--public functions------------------------------------------------------------

void bkp_configure_rtc(uint32_t period_ms, enum BkpRtcClockSrc clock_src,
		enum BkpRtcIrq irq_mask, BkpRtcCallback callback)
{
	rcc_enable(RCC_AHB_NONE, RCC_APB1_BKP, RCC_APB2_NONE);

	//start RTC
	rcc_regs->BDCR.RTCSEL = clock_src + 1;
	rcc_regs->BDCR.RTCEN = 1;

	uint32_t prescaler = compute_prescaler(period_ms, clock_src);

	//wait for registers to synchronize
	rtc_regs->CRL.RSF = 0;
	while (rtc_regs->CRL.RSF != 1) {}
	//wait for last operation to finish
	while (rtc_regs->CRL.RTOFF != 1) {}

	//enable core configuration
	rtc_regs->CRL.CNF = 1;

	//configure core registers
	rtc_regs->PRLH.PRL = prescaler >> 16;
	rtc_regs->PRLL.PRL = prescaler;

	//apply irq config
	rtc_regs->CRH.word |= irq_mask & 0x7;

	//disable/apply core configuration
	rtc_regs->CRL.CNF = 0;

	//wait for last operation to finish
	while (rtc_regs->CRL.RTOFF != 1) {}

	if (callback) {
		rtc_callback = callback;
		nvic_enable(NVIC_IRQ_RTC);
	}
}

void bkp_set_rtc_alam(uint32_t offset)
{
	uint32_t alarm = bkp_read_rtc() + offset;

	//wait for last operation to finish
	while (rtc_regs->CRL.RTOFF != 1) {}

	//enable core configuration
	rtc_regs->CRL.CNF = 1;

	//write alarm value
	rtc_regs->ALRH.RTC_ALR = alarm >> 16;
	rtc_regs->ALRL.RTC_ALR = alarm;

	//disable/apply core configuration
	rtc_regs->CRL.CNF = 0;

	//wait for last operation to finish
	while (rtc_regs->CRL.RTOFF != 1) {}
}

uint32_t bkp_read_rtc(void)
{
	//wait for core registers to be synchronized, immediate most of the time
	while (rtc_regs->CRL.RSF != 1) {}

	uint32_t time = rtc_regs->CNTH.RTC_CNT << 16;
	time |= rtc_regs->CNTL.RTC_CNT << 0;

	return time;
}

uint32_t bkp_read_rtc_div(void)
{
	//wait for core registers to be synchronized, immediate most of the time
	while (rtc_regs->CRL.RSF != 1) {}

	uint32_t div = rtc_regs->DIVH.RTC_DIV << 16;
	div |= rtc_regs->DIVL.RTC_DIV << 0;

	return div;
}

void bkp_reset(void)
{
	rcc_regs->BDCR.BDRST = 1;
	rcc_regs->BDCR.BDRST = 0;
}

void bkp_write_data(enum BkpData data_index, uint16_t data)
{
	bkp_regs->DR[data_index].D = data;
}

uint16_t bkp_read_data(enum BkpData data_index)
{
	return bkp_regs->DR[data_index].D;
}

void bkp_calibrate_lsi(enum TimPeriph timer)
{
	rcc_enable(RCC_AHB_NONE, RCC_APB1_BKP, RCC_APB2_NONE);

	//do not calibrate if already calibrated
	if (bkp_read_data(BKP_DATA_LSI_CALIB) != 0) {
		return;
	}

	//configure timer to count 1s exactly
	struct RccClocks clocks;
	rcc_get_clocks(&clocks);

	tim_set_prescaler(timer, (clocks.tim_freq / 10000)); //10000 to avoid
														 //prescaler overflow
	tim_set_auto_reload(timer, 10000);
	tim_configure_master(timer, TIM_CONFIG_ONE_SHOT
			| TIM_CONFIG_DIR_UP, TIM_MASTER_CONFIG_MODE_RESET,
			lsi_calib_callback);

	//configure rtc to tick every 2s so the div value isn't reset during the
	//timer's delay if the clock is too fast
	bkp_configure_rtc(2000, BKP_RTC_CLOCK_SRC_LSI, BKP_RTC_IRQ_NONE, nullptr);
	tim_start(timer);

	while (bkp_read_data(BKP_DATA_LSI_CALIB) == 0) {}

	//TODO reset timer
}

//--local functions-------------------------------------------------------------

/**
 * Computes the prescaler value based on the clock source and the required
 * period
 */
static uint32_t compute_prescaler(uint32_t period_ms,
		enum BkpRtcClockSrc clock_src)
{
	uint32_t prescaler;

	switch (clock_src) {
		case BKP_RTC_CLOCK_SRC_LSE:
			prescaler = 32768; //32.768kHz
			break;
		case BKP_RTC_CLOCK_SRC_LSI:
			prescaler = bkp_read_data(BKP_DATA_LSI_CALIB);
			if (prescaler == 0) {
				prescaler = 40000; //40khz
			}
			break;
		case BKP_RTC_CLOCK_SRC_HSE:
			prescaler = 62500; //8Mhz / 128
			break;
		default:
			return 0;
	}

	return (period_ms * prescaler) / 1000;
}

/**
 * LSI callibration callback. Measures the LSI deviation from its theorical
 * frequency using the RTC and stores the effective frequency for calibration
 * purposes
 */
static void lsi_calib_callback(enum TimIRQSource src)
{
	if (src == TIM_IRQ_SOURCE_UPDATE) {
		//div is decremented from 40kHz * programmed delay (in s)
		uint32_t lsi_freq = (40000 * 2) - bkp_read_rtc_div();
		bkp_write_data(BKP_DATA_LSI_CALIB, lsi_freq);
	}
}


//--ISRs------------------------------------------------------------------------

void hdr_rtc(void)
{
	nvic_clear_pending(NVIC_IRQ_RTC);

	//copy and clear and pass along src flags
	enum BkpRtcIrq src = rtc_regs->CRL.word & 0x7;
	rtc_regs->CRL.word &= ~(0x7);
	rtc_callback(src);
}

