/*
 * config.c
 *
 *  Created on: Sep 5, 2021
 *      Author: david
 */

#include "config.h"

/*
 * Variables
 */
/* Baudrates - STM32L0xx can do baudrates from 1200 to 115200
 * - default value has index 0 */
const uint32_t config_baudrates[] = {
			19200,	// 0
			4800,	// 1
			9600,	// 2
			14400,	// 3
			19200,	// 4
			28800,	// 5
			38400,	// 6
			57600,	// 7
			76800,	// 8
			115200	// 9
};
const uint8_t config_baudrates_length = 10;

/* Function to lock the EEPROM */
static int8_t eeprom_lock(void);
/* Function to unlock the EEPROM */
static int8_t eeprom_unlock(void);
/* Function to write one byte to the EEPROM */
/* IMPORTANT: EEPROM must be unlocked first */
static int8_t eeprom_program_byte(uint32_t addr, uint8_t ee_data);
/* Function to write two bytes to the EEPROM */
/* IMPORTANT: EEPROM must be unlocked first */
static int8_t eeprom_program_halfword(uint32_t addr, uint16_t ee_data);
/* Function to write four bytes to the EEPROM */
/* IMPORTANT: EEPROM must be unlocked first */
static int8_t eeprom_program_word(uint32_t addr, uint32_t ee_data);

int8_t config_read(config_t *config)
{
	config->modbus_addr = *(uint16_t *) (CONFIG_EEPROM_ADDR_MODBUS_ADDR);
	config->baudrate_index = *(uint16_t *) (CONFIG_EEPROM_ADDR_BAUDRATE_INDEX);
	config->led_on = *(uint16_t *) (CONFIG_EEPROM_ADDR_LED_ON);
	config->led_brightness = *(uint16_t *) (CONFIG_EEPROM_ADDR_LED_BRIGHTNESS);
	config->led_smooth = *(uint16_t *) (CONFIG_EEPROM_ADDR_LED_SMOOTH);
	config->led_co2_alert_limit1 = *(uint16_t *) (CONFIG_EEPROM_ADDR_LED_ALERT1);
	config->led_co2_alert_limit2 = *(uint16_t *) (CONFIG_EEPROM_ADDR_LED_ALERT2);
	config->scd4x_t_offset = *(int16_t *) (CONFIG_EEPROM_ADDR_SCD4x_T_OFFSET);

	/* Check if the EEPROM is initialized - do not check:
	 *  LED ON
	 *  LED SMOOTH
	 *  SCD4x T OFFSET
	 *  BAUDRATE INDEX
	 *  those can be 0 */
	if ((config->modbus_addr 			== EEPROM_EMPTY_BYTE) ||
		(config->led_co2_alert_limit1 	== EEPROM_EMPTY_BYTE) ||
		(config->led_co2_alert_limit2 	== EEPROM_EMPTY_BYTE) ||
		(config->led_brightness 		== EEPROM_EMPTY_BYTE))
	{
		return CONFIG_ERROR;
	}
	return CONFIG_OK;
}

int8_t config_write(config_t *config)
{
	/* Unlock the EEPROM */
	if (eeprom_unlock() != EEPROM_OK)
	{
		return EEPROM_UNLOCK_ERROR;
	}
	/* Reset the ERASE and DATA bits in the FLASH_PECR register to disable any residual erase */
	FLASH->PECR = FLASH->PECR & ~(FLASH_PECR_ERASE | FLASH_PECR_DATA);

	/* Write MODBUS ADDRESS */
	if (eeprom_program_halfword(CONFIG_EEPROM_ADDR_MODBUS_ADDR, config->modbus_addr)  != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}
	/* Write BAUDRATE */
	if (eeprom_program_halfword(CONFIG_EEPROM_ADDR_BAUDRATE_INDEX, config->baudrate_index)  != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}

	/* Write LED ON */
	if (eeprom_program_byte(CONFIG_EEPROM_ADDR_LED_ON, config->led_on)  != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}

	/* Write LED BRIGHTNESS */
	if (eeprom_program_halfword(CONFIG_EEPROM_ADDR_LED_BRIGHTNESS, config->led_brightness)  != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}

	/* Write LED SMOOTH */
	if (eeprom_program_byte(CONFIG_EEPROM_ADDR_LED_SMOOTH, config->led_smooth)  != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}

	/* Write LED CO2 ALERT LIMIT 1 */
	if (eeprom_program_halfword(CONFIG_EEPROM_ADDR_LED_ALERT1, config->led_co2_alert_limit1) != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}

	/* Write LED CO2 ALERT LIMIT 2 */
	if (eeprom_program_halfword(CONFIG_EEPROM_ADDR_LED_ALERT2, config->led_co2_alert_limit2) != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}

	/* Write LED SCD4x TEMPERATURE OFFSET */
	if (eeprom_program_halfword(CONFIG_EEPROM_ADDR_SCD4x_T_OFFSET, config->scd4x_t_offset) != EEPROM_OK)
	{
		return EEPROM_WRITE_ERROR;
	}

	/* Lock EEPROM*/
	if (eeprom_lock() != EEPROM_OK)
	{
		return EEPROM_LOCK_ERROR;
	}
	return CONFIG_OK;
}

static int8_t eeprom_lock(void)
{
	uint32_t tick_start = SysTick->VAL;
  	while ((FLASH->SR & FLASH_SR_BSY) != 0) /* Wait for FLASH to be free */
  	{
 	   /* Timeout test */
 	   /* The maximum writing time is 3.94ms (half-word) */
  		uint32_t tick_last = SysTick->VAL;
  		uint32_t tick_diff;
  		if (tick_start <= tick_last)
  		{
  			tick_diff = tick_last - tick_start;
  		} else
  		{
  			tick_diff = (0xFFFFFFFF - tick_last) + tick_start;
  		}

 		/* If the time difference is more than 5ms */
  		if (tick_diff >= (uint32_t)((uint32_t)SYSTICK_FREQ_HZ*(uint32_t)EEPROM_TIMEOUT_MAX_MS_INV))
  		{
  			return EEPROM_LOCK_ERROR;
  		}
  	}

  	FLASH->PECR = FLASH->PECR & ~(FLASH_PECR_ERRIE | FLASH_PECR_EOPIE); /* disable flash interrupts */
  	FLASH->PECR = FLASH->PECR | FLASH_PECR_PELOCK; /* Lock memory with PELOCK */

  	return EEPROM_OK;
}

static int8_t eeprom_unlock(void)
{
	uint32_t tick_start = SysTick->VAL;
   while ((FLASH->SR & FLASH_SR_BSY) != 0) /* Wait for FLASH to be free */
   {
	   /* Timeout test */
	   /* The maximum writing time is 3.94ms (half-word) */
 		uint32_t tick_last = SysTick->VAL;
 		uint32_t tick_diff;
 		if (tick_start <= tick_last)
 		{
 			tick_diff = tick_last - tick_start;
 		} else
 		{
 			tick_diff = (0xFFFFFFFF - tick_last) + tick_start;
 		}

 		/* If the time difference is more than 5ms */
  		if (tick_diff >= (uint32_t)((uint32_t)SYSTICK_FREQ_HZ*(uint32_t)EEPROM_TIMEOUT_MAX_MS_INV))
 		{
 			return EEPROM_UNLOCK_ERROR;
 		}
   }
   if ((FLASH->PECR & FLASH_PECR_PELOCK) != 0) /* If PELOCK is locked */
   {
	   /* Unlock PELOCK */
      FLASH->PEKEYR = FLASH_PEKEY1; /* PEKEY1 */
      FLASH->PEKEYR = FLASH_PEKEY2; /* PEKEY2 */
   }
   FLASH->PECR = FLASH->PECR | (FLASH_PECR_ERRIE | FLASH_PECR_EOPIE); /* enable flash interrupts */
   return EEPROM_OK;
}

static int8_t eeprom_program_byte(uint32_t addr, uint8_t ee_data)
{
	if ((EEPROM_ADDR_START <= addr) && (addr <= EEPROM_ADDR_END - 1))
	{
		*(uint8_t *)(addr) = ee_data; /* write data to EEPROM */
		if (*(uint8_t *)(addr) != ee_data)
		{
			return EEPROM_WRITE_ERROR;
		}
		return EEPROM_OK;
	} else
	{
		return EEPROM_ADDR_ERROR;
	}
}
static int8_t eeprom_program_halfword(uint32_t addr, uint16_t ee_data)
{
	if ((EEPROM_ADDR_START <= addr) && (addr <= EEPROM_ADDR_END - 2))
	{
		*(uint16_t *)(addr) = ee_data; /* write data to EEPROM */
		if (*(uint16_t *)(addr) != ee_data)
		{
			return EEPROM_WRITE_ERROR;
		}
		return EEPROM_OK;
	} else
	{
		return EEPROM_ADDR_ERROR;
	}
}

static int8_t eeprom_program_word(uint32_t addr, uint32_t ee_data)
{
	if ((EEPROM_ADDR_START <= addr) && (addr <= EEPROM_ADDR_END - 4))
	{
		*(uint32_t *)(addr) = ee_data; /* write data to EEPROM */
		if (*(uint32_t *)(addr) != ee_data)
		{
			return EEPROM_WRITE_ERROR;
		}
		return EEPROM_OK;
	} else
	{
		return EEPROM_ADDR_ERROR;
	}
}
