/******************************************************************************
*
* Copyright (C) 2013-2018 Texas Instruments Incorporated - http://www.ti.com/
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*  Redistributions of source code must retain the above copyright
*  notice, this list of conditions and the following disclaimer.
*
*  Redistributions in binary form must reproduce the above copyright
*  notice, this list of conditions and the following disclaimer in the
*  documentation and/or other materials provided with the
*  distribution.
*
*  Neither the name of Texas Instruments Incorporated nor the names of
*  its contributors may be used to endorse or promote products derived
*  from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************/

#include <stdint.h>
#include <stdbool.h>
#include "driverlib.h"

#include "MSP432P4_FlashLibIf.h"

/* Number of erase repeats until timeout */
#define FLASH_MAX_REPEATS 5

/* Local prototypes */
void msp432_flash_init(void);
void msp432_flash_mass_erase(void);
void msp432_flash_sector_erase(void);
void msp432_flash_write(void);
void msp432_flash_continous_write(void);
void msp432_flash_exit(void);
void unlock_flash_sectors(void);
void unlock_all_flash_sectors(void);
void lock_all_flash_sectors(void);
void __cs_set_dco_frequency_range(uint32_t dco_freq);
static bool program_device(void *src, void *dest, uint32_t length);

struct backup_params {
	uint32_t BANK0_WAIT_RESTORE;
	uint32_t BANK1_WAIT_RESTORE;
	uint32_t CS_DC0_FREQ_RESTORE;
	uint8_t  VCORE_LEVEL_RESTORE;
	uint8_t  PCM_VCORE_LEVEL_RESTORE;
};

#define BACKUP_PARAMS      ((struct backup_params *) 0x20000180)
#define INFO_FLASH_START  __INFO_FLASH_A_TECH_START__
#define INFO_FLASH_MIDDLE __INFO_FLASH_A_TECH_MIDDLE__
#define BSL_FLASH_START   BSL_API_TABLE_ADDR

/* Main with trampoline */
int main(void)
{
	/* Halt watchdog */
	MAP_WDT_A_HOLD_TIMER();

	/* Disable interrupts */
	cpu_cpsid();

	while (1) {
		switch (FLASH_LOADER->FLASH_FUNCTION) {
			case FLASH_INIT:
				FLASH_LOADER->RETURN_CODE = FLASH_BUSY;
				msp432_flash_init();
				FLASH_LOADER->FLASH_FUNCTION = 0;
				break;
			case FLASH_MASS_ERASE:
				FLASH_LOADER->RETURN_CODE = FLASH_BUSY;
				msp432_flash_mass_erase();
				FLASH_LOADER->FLASH_FUNCTION = 0;
				break;
			case FLASH_SECTOR_ERASE:
				FLASH_LOADER->RETURN_CODE = FLASH_BUSY;
				msp432_flash_sector_erase();
				FLASH_LOADER->FLASH_FUNCTION = 0;
				break;
			case FLASH_PROGRAM:
				FLASH_LOADER->RETURN_CODE = FLASH_BUSY;
				msp432_flash_write();
				FLASH_LOADER->FLASH_FUNCTION = 0;
				break;
			case FLASH_CONTINUOUS_PROGRAM:
				FLASH_LOADER->RETURN_CODE = FLASH_BUSY;
				msp432_flash_continous_write();
				FLASH_LOADER->FLASH_FUNCTION = 0;
				break;
			case FLASH_EXIT:
				FLASH_LOADER->RETURN_CODE = FLASH_BUSY;
				msp432_flash_exit();
				FLASH_LOADER->FLASH_FUNCTION = 0;
				break;
			case FLASH_NO_COMMAND:
				break;
			default:
				FLASH_LOADER->RETURN_CODE = FLASH_WRONG_COMMAND;
				break;
		}
	}
}

/* Initialize flash */
void msp432_flash_init(void)
{
	bool success = false;

	/* Point to vector table in RAM */
	SCB->VTOR = (uint32_t)0x01000000;

	/* backup system parameters */
	BACKUP_PARAMS->BANK0_WAIT_RESTORE =
		MAP_FLASH_CTL_A_GET_WAIT_STATE(FLASH_A_BANK0);
	BACKUP_PARAMS->BANK1_WAIT_RESTORE =
		MAP_FLASH_CTL_A_GET_WAIT_STATE(FLASH_A_BANK1);
	BACKUP_PARAMS->VCORE_LEVEL_RESTORE = MAP_PCM_GET_CORE_VOLTAGE_LEVEL();
	BACKUP_PARAMS->PCM_VCORE_LEVEL_RESTORE = MAP_PCM_GET_POWER_STATE();
	BACKUP_PARAMS->CS_DC0_FREQ_RESTORE = CS->CTL0 & CS_CTL0_DCORSEL_MASK;

	/* set parameters for flashing */
	success = MAP_PCM_SET_POWER_STATE(PCM_AM_LDO_VCORE0);

	/* Set Flash wait states to 2 */
	MAP_FLASH_CTL_A_SET_WAIT_STATE(FLASH_A_BANK0, 2);
	MAP_FLASH_CTL_A_SET_WAIT_STATE(FLASH_A_BANK1, 2);

	/* Set CPU speed to 24MHz */
	__cs_set_dco_frequency_range(CS_DCO_FREQUENCY_24);

	if (!success) {
		/* Indicate failed power switch */
		FLASH_LOADER->RETURN_CODE = FLASH_POWER_ERROR;
	} else
		FLASH_LOADER->RETURN_CODE = FLASH_SUCCESS;
}

/* Erase entire flash */
void msp432_flash_mass_erase(void)
{
	bool success = false;

	/* Allow flash writes */
	unlock_flash_sectors();

	/* Allow some mass erase repeats before timeout with error */
	int erase_repeats = FLASH_MAX_REPEATS;
	while (!success && (erase_repeats > 0)) {
		/* Mass erase with post-verify */
		success = ROM_FLASH_CTL_A_PERFORM_MASS_ERASE();
		erase_repeats--;
	}

	if (erase_repeats == 0)
		FLASH_LOADER->RETURN_CODE = FLASH_VERIFY_ERROR;
	else
		FLASH_LOADER->RETURN_CODE = FLASH_SUCCESS;

	/* Block flash writes */
	lock_all_flash_sectors();
}

/* Erase one flash sector */
void msp432_flash_sector_erase(void)
{
	bool success = false;

	/* Allow flash writes */
	unlock_all_flash_sectors();

	/* Allow some sector erase repeats before timeout with error */
	int erase_repeats = FLASH_MAX_REPEATS;
	while (!success && (erase_repeats > 0)) {
		/* Sector erase with post-verify */
		success = MAP_FLASH_CTL_A_ERASE_SECTOR(FLASH_LOADER->DST_ADDRESS);
		erase_repeats--;
	}

	if (erase_repeats == 0)
		FLASH_LOADER->RETURN_CODE = FLASH_ERROR;
	else
		FLASH_LOADER->RETURN_CODE = FLASH_SUCCESS;

	/* Block flash writes */
	lock_all_flash_sectors();
}

/* Write data to flash with the help of DriverLib */
void msp432_flash_write(void)
{
	bool success = false;

	/* Allow flash writes */
	unlock_all_flash_sectors();

	while (!(FLASH_LOADER->BUFFER1_STATUS_REGISTER & BUFFER_DATA_READY))
		;

	FLASH_LOADER->BUFFER1_STATUS_REGISTER |= BUFFER_ACTIVE;

	/* Program memory */
	success = program_device((uint32_t *)RAM_LOADER_BUFFER1,
		(void *)FLASH_LOADER->DST_ADDRESS, FLASH_LOADER->SRC_LENGTH);

	FLASH_LOADER->BUFFER1_STATUS_REGISTER &=
		~(BUFFER_ACTIVE | BUFFER_DATA_READY);

	/* Block flash writes */
	lock_all_flash_sectors();

	if (!success)
		FLASH_LOADER->RETURN_CODE = FLASH_ERROR;
	else
		FLASH_LOADER->RETURN_CODE = FLASH_SUCCESS;
}

/* Write data to flash with the help of DriverLib with auto-increment */
void msp432_flash_continous_write(void)
{
	bool buffer1_in_use = false;
	bool buffer2_in_use = false;
	uint32_t *src_address = NULL;
	bool success = false;

	uint32_t bytes_to_write = FLASH_LOADER->SRC_LENGTH;
	uint32_t write_package = 0;
	uint32_t start_addr = FLASH_LOADER->DST_ADDRESS;

	while (bytes_to_write > 0) {
		if (bytes_to_write > SRC_LENGTH_MAX) {
			write_package = SRC_LENGTH_MAX;
			bytes_to_write -= write_package;
		} else {
			write_package = bytes_to_write;
			bytes_to_write -= write_package;
		}
		unlock_all_flash_sectors();
		while (!(FLASH_LOADER->BUFFER1_STATUS_REGISTER & BUFFER_DATA_READY) &&
			!(FLASH_LOADER->BUFFER2_STATUS_REGISTER & BUFFER_DATA_READY))
			;

		if (FLASH_LOADER->BUFFER1_STATUS_REGISTER & BUFFER_DATA_READY) {
			FLASH_LOADER->BUFFER1_STATUS_REGISTER |= BUFFER_ACTIVE;
			src_address = (uint32_t *) RAM_LOADER_BUFFER1;
			buffer1_in_use = true;
		} else if (FLASH_LOADER->BUFFER2_STATUS_REGISTER & BUFFER_DATA_READY) {
			FLASH_LOADER->BUFFER2_STATUS_REGISTER |= BUFFER_ACTIVE;
			src_address = (uint32_t *) RAM_LOADER_BUFFER2;
			buffer2_in_use = true;
		}
		if (buffer1_in_use || buffer2_in_use) {
			success = program_device(src_address, (void *) start_addr, write_package);
			start_addr += write_package;
		}
		if (buffer1_in_use) {
			FLASH_LOADER->BUFFER1_STATUS_REGISTER &= ~(BUFFER_ACTIVE | BUFFER_DATA_READY);
			buffer1_in_use = false;
		} else if (buffer2_in_use) {
			FLASH_LOADER->BUFFER2_STATUS_REGISTER &= ~(BUFFER_ACTIVE | BUFFER_DATA_READY);
			buffer2_in_use = false;
		}
		/* Block flash writes */
		lock_all_flash_sectors();

		if (!success) {
			FLASH_LOADER->RETURN_CODE = FLASH_ERROR;
			break;
		}
	}
	if (success)
		FLASH_LOADER->RETURN_CODE = FLASH_SUCCESS;
}

/* Unlock Main/Info Flash sectors */
void unlock_flash_sectors(void)
{
	if (FLASH_LOADER->ERASE_PARAM & ERASE_MAIN)
		MAP_FLASH_CTL_A_UNPROTECT_MEMORY(FLASH_BASE, FLASH_BASE +
			MAP_SYS_CTL_A_GET_FLASH_SIZE() - 1);

	if (FLASH_LOADER->ERASE_PARAM & ERASE_INFO) {
		MAP_FLASH_CTL_A_UNPROTECT_MEMORY(INFO_FLASH_START, TLV_BASE - 1);
		if (FLASH_LOADER->UNLOCK_BSL == UNLOCK_BSL_KEY)
			MAP_FLASH_CTL_A_UNPROTECT_MEMORY(BSL_FLASH_START,
				INFO_FLASH_MIDDLE - 1);
		MAP_FLASH_CTL_A_UNPROTECT_MEMORY(INFO_FLASH_MIDDLE, INFO_FLASH_MIDDLE +
			MAP_SYS_CTL_A_GET_INFO_FLASH_SIZE() - 1);
	}
}

/* Unlock All Flash sectors */
void unlock_all_flash_sectors(void)
{
	MAP_FLASH_CTL_A_UNPROTECT_MEMORY(FLASH_BASE, FLASH_BASE +
		MAP_SYS_CTL_A_GET_FLASH_SIZE() - 1);
	MAP_FLASH_CTL_A_UNPROTECT_MEMORY(INFO_FLASH_START, TLV_BASE - 1);
	if (FLASH_LOADER->UNLOCK_BSL == UNLOCK_BSL_KEY)
		MAP_FLASH_CTL_A_UNPROTECT_MEMORY(BSL_FLASH_START,
			INFO_FLASH_MIDDLE - 1);
	MAP_FLASH_CTL_A_UNPROTECT_MEMORY(INFO_FLASH_MIDDLE,  INFO_FLASH_MIDDLE +
		MAP_SYS_CTL_A_GET_INFO_FLASH_SIZE() - 1);
}

/* Lock all Flash sectors */
void lock_all_flash_sectors(void)
{
	MAP_FLASH_CTL_A_PROTECT_MEMORY(FLASH_BASE, FLASH_BASE +
		MAP_SYS_CTL_A_GET_FLASH_SIZE() - 1);
	MAP_FLASH_CTL_A_PROTECT_MEMORY(INFO_FLASH_START, INFO_FLASH_START +
		MAP_SYS_CTL_A_GET_INFO_FLASH_SIZE() - 1);
}

/* Force DCO frequency range */
void __cs_set_dco_frequency_range(uint32_t dco_freq)
{
	/* Unlocking the CS Module */
	CS->KEY = CS_KEY_VAL;

	/* Resetting Tuning Parameters and Setting the frequency */
	CS->CTL0 = (CS->CTL0 & ~CS_CTL0_DCORSEL_MASK) | dco_freq;

	/* Locking the CS Module */
	CS->KEY = 0;
}

/* Exit flash programming */
void msp432_flash_exit(void)
{
	bool success = false;

	/* Restore modified registers, in reverse order */
	__cs_set_dco_frequency_range(CS_DCO_FREQUENCY_3);

	MAP_FLASH_CTL_A_SET_WAIT_STATE(FLASH_A_BANK0,
		BACKUP_PARAMS->BANK0_WAIT_RESTORE);
	MAP_FLASH_CTL_A_SET_WAIT_STATE(FLASH_A_BANK1,
		BACKUP_PARAMS->BANK1_WAIT_RESTORE);

	success = MAP_PCM_SET_POWER_STATE(BACKUP_PARAMS->PCM_VCORE_LEVEL_RESTORE);

	success &= MAP_PCM_SET_CORE_VOLTAGE_LEVEL(
		BACKUP_PARAMS->VCORE_LEVEL_RESTORE);

	__cs_set_dco_frequency_range(BACKUP_PARAMS->CS_DC0_FREQ_RESTORE);

	/* Point to vector table in Flash */
	SCB->VTOR = (uint32_t)0x00000000;

	if (!success)
		FLASH_LOADER->RETURN_CODE = FLASH_ERROR;
	else
		FLASH_LOADER->RETURN_CODE = FLASH_SUCCESS;
}

static bool program_device(void *src, void *dest, uint32_t length)
{
	uint32_t dst_address = (uint32_t)dest;

	/* Flash main memory first, then information memory */
	if ((dst_address < INFO_FLASH_START) && ((dst_address + length) >
		INFO_FLASH_START)) {
		uint32_t block_length = INFO_FLASH_START - dst_address;
		uint32_t src_address = (uint32_t)src;
		/* Main memory block */
		bool success = MAP_FLASH_CTL_A_PROGRAM_MEMORY(src, dest, block_length);

		src_address = src_address + block_length;
		block_length = length - block_length;
		/* Information memory block */
		success &= MAP_FLASH_CTL_A_PROGRAM_MEMORY((void *)src_address,
			(void *)INFO_FLASH_START, block_length);
		return success;
	} else
		return MAP_FLASH_CTL_A_PROGRAM_MEMORY(src, dest, length);
}
