/*
 * backup_registers.cpp
 *
 *  Created on: Oct 19, 2023
 *      Author: mateusz
 */

#include "backup_registers.h"

#include "variant.h"

#ifdef STM32L471xx
#define REGISTER 					RTC->BKP0R
#define REGISTER_LAST_SLEEP			RTC->BKP1R
#define REGISTER_LAST_WKUP			RTC->BKP2R
#define REGISTER_COUNTERS			RTC->BKP4R
#define REGISTER_LAST_SLTIM			RTC->BKP6R
#define REGISTER_PACKET_COUNTERS	RTC->BKP7R
#define REGISTER_RESET_CHECK_FAIL	RTC->BKP8R
#define REGISTER_ASSERT				RTC->BKP9R
#endif

#define BACKUP_REG_INHIBIT_PWR_SWITCH_PERIODIC_H 	1u
#define BACKUP_REG_ALL_PWRSAVE_STATES_BITMASK 		(0xFFu << 2)


// backup registers (ParaTNC)
// 0 ->
// 2 -> boot and hard fault count
// 3 -> controller configuration status
// 4 -> telemetry counter
// 5 ->
// 6 -> weather counters


// backup registers (ParaMETEO)
// 0 -> powersave status
// 1 -> last sleep rtc time
// 2 -> last wakeup rtc time
// 3 -> controller configuration status
// 4 -> wakeup events MSB, sleep events LSB
// 5 -> monitor
// 6 -> last sleep time
// 7 -> weather and telemetry timers & counters
// 8 -> counters of resets caused by validation checks failures
// 9 -> assert register

// 7th register map
// xxxxyyAA - telemetry frames counter
// xxxxyAyy - value of packet_tx_meteo_counter limited to 15
// xxxxAyyy - value of packet_tx_meteo_gsm_counter limited to 15
// xxAAyyyy - value of packet_tx_beacon_counter
// Axxxyyyy - checksum

// 8th register map
// xxxxyyAA - resets caused by 'aprsis_check_connection_attempt_alive()'
// xxxxAAyy - resets caused by 'rte_wx_check_weather_measurements()'
// xxAAyyyy - resets caused by value of 'rte_wx_dallas_degraded_counter'
// AAxxyyyy - resets caused by 'system_is_rtc_ok()'

static void backup_reg_unclock(void) {
	// enable access to backup domain
	PWR->CR1 |= PWR_CR1_DBP;
}

static void backup_reg_lock(void) {
	PWR->CR1 &= (0xFFFFFFFFu ^ PWR_CR1_DBP);
}

/**
 * Calculates checksum from register content
 * @param reg
 * @return
 */
inline static uint8_t backup_reg_calculate_checksum(uint32_t reg) {

	uint8_t out = 0u;

	uint32_t temp = 0u;

	temp += (reg & 0xFu);
	temp += ((reg & 0xF0u) >> 4);
	temp += ((reg & 0xF00u) >> 8);
	temp += ((reg & 0xF000u) >> 12);
	temp += ((reg & 0xF0000u) >> 16);
	temp += ((reg & 0xF00000u) >> 20);
	temp += ((reg & 0xF000000u) >> 24);

	temp = ~temp;

	out = (temp & 0xFu);

	return out;
}

inline static uint8_t backup_reg_get_checksum(uint32_t reg) {

	uint8_t out = 0u;

	out = ((reg & 0xF0000000u) >> 28);

	return out;
}

inline static void backup_reg_set_checksum(volatile uint32_t * reg, const uint8_t checksum) {

	if (variant_validate_is_within_ram((void*)reg) != 0) {
		// clear existing checksum
		(*reg) &= (0xFFFFFFFF ^ 0xF0000000);

		// store new checksum
		(*reg) |= ((checksum & 0xF) << 28);
	}
}

/**
 * Get the value of a register keeping a status which configuration
 * sector is valid, and which is loaded
 * @return
 */
uint32_t backup_reg_get_configuration(void) {

	uint32_t out = 0;

#ifdef PARATNC
	out = BKP->DR3;
#endif

#ifdef PARAMETEO
	out = RTC->BKP3R;

#endif

	return out;
}

/**
 * Set a register, which contains an information which configuration sector
 * is valid, and which one has been loaded. It is mostly used to reset
 * the register back to zero.
 * @param value
 */
void  backup_reg_set_configuration(uint32_t value) {
	backup_reg_unclock();

#ifdef PARATNC
	BKP->DR3 = value;
#endif

#ifdef PARAMETEO
	RTC->BKP3R = value;
#endif

	backup_reg_lock();

}

/**
 * Set certain bit in the register containing current configuration state.
 * This is used to store a state of all configuration sectors.
 * @param value
 */
void backup_reg_set_bits_configuration(uint32_t value) {
#ifdef PARATNC
	BKP->DR3 |= value;
#endif

#ifdef PARAMETEO
	// enable access to backup domain
	backup_reg_unclock();

	RTC->BKP3R |= value;

	backup_reg_lock();

#endif
}

/**
 * Clears certain bit in the register containing current configuration state.
 * This is used to keep information that CRC checksum of a sector is no longer
 * valid.
 * @param value
 */
void backup_reg_clear_bits_configuration(uint32_t value) {
	// enable access to backup domain
	backup_reg_unclock();

#ifdef PARATNC
	BKP->DR3 &= (0xFFFF ^ value);
#endif

#ifdef PARAMETEO
	RTC->BKP3R &= (0xFFFFFFFFu ^ value);
#endif

	backup_reg_lock();

}

/**
 * Resets all powersave mode flags.
 */
void backup_reg_reset_all_powersave_states(void) {

	backup_reg_unclock();

#ifdef PARAMETEO
	REGISTER &= 0xFFFFFFFFu ^ BACKUP_REG_ALL_PWRSAVE_STATES_BITMASK;
#endif

	backup_reg_lock();

}

/**
 * Checks if controller is currently in given powersave mode using
 * information from dedicated backup register
 * @param state
 * @return one if controller is in given powersave state
 */
int backup_reg_is_in_powersave_state(uint32_t state) {
	int out = 0;
#ifdef PARAMETEO

	if ((REGISTER & BACKUP_REG_ALL_PWRSAVE_STATES_BITMASK) == state) {
		out = 1;
	}
#endif
	return out;
}

/**
 * Set that controller is currently in given powersave state, it should be
 * used along with \link #backup_reg_reset_all_powersave_states
 * @param state
 */
void backup_reg_set_powersave_state(uint32_t state) {
#ifdef PARAMETEO
	backup_reg_unclock();

	REGISTER |= state;

	backup_reg_lock();
#endif
}

/**
 * Returns current powersave state
 * @return
 */
uint16_t backup_reg_get_powersave_state(void) {
	int out = 0;

#ifdef PARAMETEO
	out = (uint16_t)(REGISTER & BACKUP_REG_ALL_PWRSAVE_STATES_BITMASK);
#endif

	return out;
}

/**
 * Return counter value with current number of wakeup events
 * @return
 */
uint32_t backup_reg_get_wakeup_counter(void) {

	uint32_t out = 0;

#ifdef PARAMETEO
	out = (uint32_t)((REGISTER_COUNTERS & 0xFFFF0000u) >> 16);
#endif

	return out;
}

/**
 * Set current value of wakeup events
 * @param in
 */
void backup_reg_set_wakeup_counter(uint32_t in) {
	backup_reg_unclock();

#ifdef PARAMETEO
	REGISTER_COUNTERS = (REGISTER_COUNTERS & 0x0000FFFFu) | ((in & 0xFFFFu) << 16);
#endif

	backup_reg_lock();
}

/**
 * Return counter value with current number of sleep events
 * @return
 */
uint32_t backup_reg_get_sleep_counter(void) {
	uint32_t out = 0;

#ifdef PARAMETEO
	out = (uint16_t)(REGISTER_COUNTERS & 0xFFFFu);
#endif

	return out;
}

/**
 * Set current value of sleep events
 * @param in
 */
void backup_reg_set_sleep_counter(uint32_t in) {
	backup_reg_unclock();

#ifdef PARAMETEO
	REGISTER_COUNTERS = (REGISTER_COUNTERS & 0xFFFF0000u) | (uint16_t)(in & 0xFFFFu);
#endif

	backup_reg_lock();
}


/**
 * Returns a timestamp of last sleep event
 */
uint32_t backup_reg_get_last_sleep_timestamp(void) {

	uint32_t out = 0;

#ifdef PARAMETEO
	out = REGISTER_LAST_SLEEP;
#endif

	return out;
}

/**
 * Stores a timestamp of a sleep event from current RTC time
 */
void backup_reg_set_last_sleep_timestamp(void) {

#ifdef PARAMETEO
	backup_reg_unclock();

	REGISTER_LAST_SLEEP = RTC->TR;

	backup_reg_lock();
#endif

}

/**
 * Disables an inhibition of VBATT_S switching, when controller is in
 * powersave mode in which weather sensors shall be kept powered down.
 */
void backup_reg_reset_inhibit_periodic_pwr_switch(void) {
	backup_reg_unclock();

#ifdef PARAMETEO
	REGISTER &= 0xFFFFFFFFu ^ BACKUP_REG_INHIBIT_PWR_SWITCH_PERIODIC_H;
#endif

	backup_reg_lock();
}

/**
 * Enables an inhibition of VBATT_S switching, when controller is in
 * powersave mode in which weather sensors shall be kept powered down.
 */
void backup_reg_inhibit_periodic_pwr_switch(void) {
	backup_reg_unclock();

#ifdef PARAMETEO
	REGISTER |= BACKUP_REG_INHIBIT_PWR_SWITCH_PERIODIC_H;
#endif

	backup_reg_lock();

}

/**
 * Returns if VBATT_S switching inhibition is currently enabled
 * @return
 */
uint32_t backup_reg_is_periodic_pwr_switch_inhibited(void) {

	int out = 0;
#ifdef PARAMETEO

	if ((REGISTER & BACKUP_REG_INHIBIT_PWR_SWITCH_PERIODIC_H) != 0) {
		out = 1u;
	}
#endif
	return out;
}

/**
 * gets last wakeup timestamp
 * @return
 */
uint32_t backup_reg_get_last_wakeup_timestamp(void) {

	uint32_t out = 0;
#ifdef PARAMETEO
	out = REGISTER_LAST_WKUP;
#endif
	return out;
}

/**
 * Stores a timestamp of a wakeup event from current RTC time
 */
void backup_reg_set_last_wakeup_timestamp(void) {

#ifdef PARAMETEO
	backup_reg_unclock();

	REGISTER_LAST_WKUP = RTC->TR;

	backup_reg_lock();
#endif
}

/**
 *
 * @return
 */
uint32_t backup_reg_get_last_sleep_duration(void) {
	uint32_t out = 0;

#ifdef PARAMETEO
	out = REGISTER_LAST_SLTIM;
#endif

	return out;
}

/**
 *
 * @param in
 */
void backup_reg_set_last_sleep_duration(uint32_t in) {
#ifdef PARAMETEO
	backup_reg_unclock();

	REGISTER_LAST_SLTIM = in;

	backup_reg_lock();
#endif
}

/**
 * Set register containing packet counters, used when a configuration
 * is reset to default
 */
void backup_reg_reset_counters(void) {
#ifdef PARAMETEO
	backup_reg_unclock();

	REGISTER_PACKET_COUNTERS = 0u;

	backup_reg_lock();
#endif
}

/**
 * Gets last telemetry frames counter stored in backup registers
 * @return last telemetry counter or zero if backup registers contains crap
 */
uint8_t backup_reg_get_telemetry(void) {

	uint8_t out = 0u;

#ifdef PARAMETEO

	uint32_t reg_value = REGISTER_PACKET_COUNTERS;

	// calculate checksum from register value
	uint8_t calculated_checksum = backup_reg_calculate_checksum(reg_value);

	uint8_t checksum_from_reg = backup_reg_get_checksum(reg_value);

	// check if checksum is ok
	if (calculated_checksum == checksum_from_reg) {
		out = (uint8_t)(reg_value & 0xFFu);
	}
	else {
		; // return zero if checksum is wrong
	}

#endif

	return out;
}

/**
 *
 */
void backup_reg_set_telemetry(uint16_t in) {
	backup_reg_unclock();

	if (in > 255) {
		in = 0;
	}

	const uint8_t narrowed_in = (uint8_t)(in & 0xFFu);

#ifdef PARAMETEO

	// get current value
	uint32_t reg_value = REGISTER_PACKET_COUNTERS;

	// clear current telemetry counter using the bitmask
	reg_value &= (0xFFFFFFFFu ^ 0xFFu);

	// store updated value
	reg_value |= narrowed_in;

	// recalculate checksum
	const uint8_t new_checksum = backup_reg_calculate_checksum(reg_value);

	// store new checksum
	backup_reg_set_checksum(&reg_value, new_checksum);

	REGISTER_PACKET_COUNTERS = reg_value;
#endif

	backup_reg_lock();

}

/**
 *
 * @param beacon_counter
 * @param meteo_counter
 * @param meteo_gsm_counter
 */
void backup_reg_get_packet_counters(uint8_t * beacon_counter, uint8_t * meteo_counter, uint8_t * meteo_gsm_counter) {
#ifdef PARAMETEO
	uint32_t reg_value = REGISTER_PACKET_COUNTERS;

	// calculate checksum from register value
	uint8_t calculated_checksum = backup_reg_calculate_checksum(reg_value);

	uint8_t checksum_from_reg = backup_reg_get_checksum(reg_value);

	// check if checksum is ok
	if (calculated_checksum == checksum_from_reg) {
		*meteo_counter 		= ((reg_value & 0x00000F00u) >> 8);
		*meteo_gsm_counter 	= ((reg_value & 0x0000F000u) >> 12);
		*beacon_counter 	= ((reg_value & 0x00FF0000u) >> 16);
	}
	else {
		// if it is not ok revert to default values
		*beacon_counter = 0u;
		*meteo_counter = 2u;
		*meteo_gsm_counter = 0u;

		// and save it back into backup register
		backup_reg_set_packet_counters(*beacon_counter, *meteo_counter, *meteo_gsm_counter);
	}
#endif
}

/**
 *
 * @param beacon_counter
 * @param meteo_counter
 * @param meteo_gsm_counter
 */
void backup_reg_set_packet_counters(uint8_t beacon_counter, uint8_t meteo_counter, uint8_t meteo_gsm_counter) {
#ifdef PARAMETEO
	volatile uint32_t reg_value = REGISTER_PACKET_COUNTERS;

	// clear existing content
	reg_value &= (0xFFFFFFFFu ^ 0x00FFFF00u);

	// check if meteo_counter doesn't overflow
	if (meteo_counter > 15) {
		meteo_counter = 15;
	}

	if (meteo_gsm_counter > 15) {
		meteo_gsm_counter = 15;
	}

	// put new values
	reg_value |= ((beacon_counter << 16) | (meteo_gsm_counter << 12) | (meteo_counter << 8));

	// calculate new checksum
	const uint8_t new_checksum = backup_reg_calculate_checksum(reg_value);

	// put new checksum value
	backup_reg_set_checksum(&reg_value, new_checksum);

	backup_reg_unclock();

	REGISTER_PACKET_COUNTERS = reg_value;

	backup_reg_lock();

#endif
}

void backup_reg_increment_aprsis_check_reset(void) {
#ifdef PARAMETEO
	// REGISTER_RESET_CHECK_FAIL
	volatile uint32_t reg_value = REGISTER_RESET_CHECK_FAIL;

	// get existing value
	uint8_t counter = (uint8_t)(reg_value & 0xFFU);

	// increment it
	counter++;

	// clear existing value from register
	reg_value &= 0xFFFFFF00U;

	// add incremented counter value
	reg_value |= counter;

	backup_reg_unclock();

	REGISTER_RESET_CHECK_FAIL = reg_value;

	backup_reg_lock();
#endif
}

void backup_reg_increment_weather_measurements_check_reset(void) {
#ifdef PARAMETEO
	// REGISTER_RESET_CHECK_FAIL
	volatile uint32_t reg_value = REGISTER_RESET_CHECK_FAIL;

	// get existing value
	uint8_t counter = (uint8_t)((reg_value & 0xFF00U) >> 8U);

	// increment it
	counter++;

	// clear existing value from register
	reg_value &= 0xFFFF00FFU;

	// add incremented counter value
	reg_value |= ((uint32_t)counter << 8U);

	backup_reg_unclock();

	REGISTER_RESET_CHECK_FAIL = reg_value;

	backup_reg_lock();
#endif
}

void backup_reg_increment_dallas_degraded_reset(void) {
#ifdef PARAMETEO
	// REGISTER_RESET_CHECK_FAIL
	volatile uint32_t reg_value = REGISTER_RESET_CHECK_FAIL;

	// get existing value
	uint8_t counter = (uint8_t)((reg_value & 0xFF0000U) >> 16U);

	// increment it
	counter++;

	// clear existing value from register
	reg_value &= 0xFF00FFFFU;

	// add incremented counter value
	reg_value |= ((uint32_t)counter << 16U);

	backup_reg_unclock();

	REGISTER_RESET_CHECK_FAIL = reg_value;

	backup_reg_lock();
#endif
}

void backup_reg_increment_is_rtc_ok_check_reset(void) {
#ifdef PARAMETEO
	// REGISTER_RESET_CHECK_FAIL
	volatile uint32_t reg_value = REGISTER_RESET_CHECK_FAIL;

	// get existing value
	uint8_t counter = (uint8_t)((reg_value & 0xFF000000U) >> 24U);

	// increment it
	counter++;

	// clear existing value from register
	reg_value &= 0x00FFFFFFU;

	// add incremented counter value
	reg_value |= ((uint32_t)counter << 24U);

	backup_reg_unclock();

	REGISTER_RESET_CHECK_FAIL = reg_value;

	backup_reg_lock();
#endif
}

void backup_assert(uint32_t assert) {
#ifdef PARAMETEO
	backup_reg_unclock();

	REGISTER_ASSERT |= assert;

	backup_reg_lock();

	NVIC_SystemReset();
#endif
}
