/**
 *
 * @file bl_avr_bootloader.c
 *
 * @ingroup generic_bootloader_8bit
 *
 * @brief This source file provides the implementation of the APIs for the 8-bit Bootloader library
 *
 * @version BOOTLOADER Driver Version 3.0.0
 */

${disclaimer}

#include <stdlib.h>
#include "../../${DELAY_HEADER_PATH}"
#include <stdbool.h>
#include <string.h>
#include "../bl_bootload.h"
#include "../bl_communication_interface.h"

typedef void(*app_t)(void);

static void BL_RunBootloader(void);
static bool BL_BootloadRequired(void);
static void BL_CheckDeviceReset(void);

static uint8_t  BL_GetVersionData(void);
<#if (SUPPORT_FLASH_READ == "Enabled")>
static uint8_t  BL_ReadFlash(void);
</#if>
static uint8_t  BL_WriteFlash(void);
static uint8_t  BL_EraseFlash(void);
<#if (SUPPORT_EE_DATA_READ == "Enabled")>
static uint8_t  BL_ReadEEData(void);
</#if>
<#if (SUPPORT_EE_DATA_WRITE == "Enabled")>
static uint8_t  BL_WriteEEData(void);
</#if>
static uint8_t  BL_ReadConfig(void);
static uint8_t  BL_WriteAppStatus(void);
static uint8_t  BL_CalcChecksum(void);
static uint8_t  BL_Reset(void);
static uint8_t  BL_ProcessBootBuffer (void);

static bool resetPending = false;
static frame_t frame;


void BL_Initialize(void)
{
	resetPending = false;   

	/* Initialize system for AVR GCC support, expects r1 = 0 */
	asm("clr r1");   
        
<#if (BOOTLOADER_RUNNING_IO == "Enabled")>
    BL_INDICATOR_OFF();
</#if>   
	if ((BL_BootloadRequired() == true))        
	{
        DELAY_milliseconds(200);            
<#if (BOOTLOADER_RUNNING_IO == "Enabled")>
        BL_INDICATOR_ON();
</#if>
        BL_RunBootloader();     // generic comms layer
	}
             
	app_t app = (app_t)(START_OF_APP / sizeof(app_t));
	app();	
}


static bool BL_BootloadRequired(void)
{
<#if IO_PIN_ENTRY == "Enabled">
    /* NOTE: This function can be customized by the user to enter the bootloader 
     * as required. The entry point can be reading a push button status, a  
     * command from a peripheral, or any other method selected by the user.  
     * 
     * Currently the bootloader checks an IO pin to force entry into bootloader. */  

	// #info  "You may need to add additional delay here between enabling weak pullups and testing the pin."
	for (uint8_t i = 0; i != 0xFF; i++)
	{
		asm("nop");
	}
		
	if (IO_PIN_ENTRY_GetInputValue() == IO_PIN_ENTRY_RUN_BL)
	{
            return (true);                
	}
</#if>
	if (BL_bootVerify() == false)
	{
            return (true);                
	}
    return false;
}


// *****************************************************************************
static uint8_t  BL_ProcessBootBuffer(void)
{
	uint8_t   len;

	// ***********************************************
	// Test the command field and sub-command.
	// ***********************************************
    switch (frame.command)
    {
	    case    READ_VERSION:
			len = BL_GetVersionData();
	    break;
<#if (SUPPORT_FLASH_READ == "Enabled")>
	    case    READ_FLASH:
			len =  BL_ReadFlash();
	    break;
</#if>
	    case    WRITE_FLASH:
			len = BL_WriteFlash();
	    break;
	    case    ERASE_FLASH:
			len = BL_EraseFlash();
	    break;
<#if (SUPPORT_EE_DATA_READ == "Enabled")>
	    case    READ_EE_DATA:
			len = BL_ReadEEData();
	    break;
</#if>
<#if (SUPPORT_EE_DATA_WRITE == "Enabled")>
	    case    WRITE_EE_DATA:
			len = BL_WriteEEData();
	    break;
</#if>
	    case    READ_CONFIG:
			len = BL_ReadConfig();
	    break;
		case WRITE_STATUS:
			len = BL_WriteAppStatus ();
		break;
	    case    CALC_CHECKSUM:
			// not supported yet
			frame.data[0] = ERROR_INVALID_COMMAND;
			len = BL_HEADER + 1;
	    break;
	    case    RESET_DEVICE:
			frame.data[0] = COMMAND_PROCESSED_SUCCESSFULLY;
			len = BL_Reset();
	    break;
	    default:
			frame.data[0] = ERROR_INVALID_COMMAND;
			len = BL_HEADER + 1;
		break;
    }
    return (len);
}

static void BL_RunBootloader(void)
{
	uint16_t  msg_length = 0;
	uint16_t index = 0;
	uint8_t  ch;

    while (1)
    {	
		BL_CheckDeviceReset();

		BL_CommunicationModuleInit();  

		index = 0;  //Point to the buffer
		msg_length = BL_HEADER;  // message has 9 bytes of overhead (Synch + Opcode + Length + Address)	
		memset(frame.buffer,0,sizeof(frame));	// Clear Buffer
		
		while(index < msg_length)
		{		
			BL_CommunicationModuleRead(&ch,1);
            frame.buffer[index] = ch;			

			index++;
			if (index == 5)
		    {
				if ((frame.command == WRITE_FLASH)
					|| (frame.command == WRITE_EE_DATA)
					|| (frame.command == WRITE_STATUS))
				{
					msg_length += frame.data_length;
				}
			}
		}

		msg_length = BL_ProcessBootBuffer ();
		
		if (msg_length>0)
		{
			BL_CommunicationModuleWrite(frame.buffer, msg_length);

			while(BL_CommunicationModuleIsReady() != true);
		}            
	}
}

static void BL_CheckDeviceReset(void)
{
	
	if (resetPending)
	{
<#if (VERIFY == "STATUS_BYTE_VERIFICATION")>
<#if (NVM_ACCESS_TYPE == "BYTE_ACCESS")>
		nvm_status_t status = NVM_OK;
		if(FLASH_Read(STATUS_ADDRESS) == 0xFF)
		{
			status = FLASH_Write(STATUS_ADDRESS, (uint16_t)APPLICATION_VALID);
		}
</#if>
<#if (NVM_ACCESS_TYPE == "ROW_ACCESS")>
		flash_address_t flashStartPageAddress;
		uint16_t flashAddressOffset;
		flash_data_t flashWriteData[PROGMEM_PAGE_SIZE];

		// Read out the page that holds the status byte
		if(FLASH_Read(STATUS_ADDRESS) == 0xFF){
			//Get the starting address of the page containing given address
			flashStartPageAddress = FLASH_ErasePageAddressGet(STATUS_ADDRESS);

			//Read entire row
			for (flashAddressOffset = 0; flashAddressOffset < PROGMEM_PAGE_SIZE; flashAddressOffset++)
			{
				flashWriteData[flashAddressOffset] = FLASH_Read(flashStartPageAddress + flashAddressOffset);
			}

			//Get offset from starting address of the page
			flashAddressOffset = FLASH_ErasePageOffsetGet(STATUS_ADDRESS);

			// Set the status value
			flashWriteData[flashAddressOffset] = 0x55;

			//Write data to Flash row
			FLASH_RowWrite(flashStartPageAddress, flashWriteData);
		}
</#if>
</#if>
	    DELAY_milliseconds(3);
            ccp_write_io((void *)&RSTCTRL.SWRR, ${RESET_BIT_U});
	}
}

// ***********************************************
// Commands
// ***********************************************

// **************************************************************************************
// Get Bootloader Version Information
//        Cmd     Length----------------   Address---------------
// In:   [<0x00> <0x00><0x00><0x00><0x00> <0x00><0x00><0x00><0x00>]
// OUT:  [<0x00> <0x00><0x00><0x00><0x00> <0x00><0x00><0x00><0x00> <VERL><VERH>]
// ***********************************************
static uint8_t  BL_GetVersionData(void)
{
	uint8_t dataIndex = 0;
	
	volatile uint32_t memSize = PROGMEM_SIZE;
	volatile uint16_t pageSize = PROGMEM_PAGE_SIZE;
	volatile uint32_t partSize = 0;
	
	partSize = (memSize/((uint32_t)pageSize));

	frame.data[dataIndex++] = MINOR_VERSION;
	frame.data[dataIndex++] = MAJOR_VERSION;
	frame.data[dataIndex++] = BL_HEADER;
	
<#if (BOOT_BLOCK_SIZE_U == "512bytes")>
	frame.data[dataIndex++] = FUSE.BOOTSIZE;
	frame.data[dataIndex++] = FUSE.CODESIZE;
</#if> 
<#if (BOOT_BLOCK_SIZE_U == "256bytes")>
	frame.data[dataIndex++] = FUSE.BOOTEND;
	frame.data[dataIndex++] = FUSE.APPEND;
</#if>

	frame.data[dataIndex++] =  (uint8_t)(partSize & 0xFF);
	frame.data[dataIndex++] =  (uint8_t)((partSize >> 8) & 0xFF);
	frame.data[dataIndex++] =  (uint8_t)((partSize >> 16) & 0xFF);
	frame.data[dataIndex++] =  (uint8_t)((partSize >> 24) & 0xFF);

	frame.data[dataIndex++] = SIGROW.DEVICEID0;
	frame.data[dataIndex++] = SIGROW.DEVICEID1;
	frame.data[dataIndex++] = SIGROW.DEVICEID2;
	frame.data[dataIndex++] = (uint8_t)(PROGMEM_PAGE_SIZE & 0xFF);
	frame.data[dataIndex++] = (uint8_t)((PROGMEM_PAGE_SIZE >> 8) & 0xFF);
	frame.data[dataIndex++] = SIGROW.SERNUM0;
	frame.data[dataIndex++] = SIGROW.SERNUM1;
	frame.data[dataIndex++] = SIGROW.SERNUM2;
	frame.data[dataIndex++] = SIGROW.SERNUM3;
	frame.data[dataIndex++] = SIGROW.SERNUM4;
	frame.data[dataIndex++] = SIGROW.SERNUM5;
	frame.data[dataIndex++] = SIGROW.SERNUM6;
	frame.data[dataIndex++] = SIGROW.SERNUM7;
	frame.data[dataIndex++] = SIGROW.SERNUM8;
	frame.data[dataIndex++] = SIGROW.SERNUM9;
	
	return  (BL_HEADER + dataIndex);   // total length to send back 9 byte header + 22 byte payload
}

<#if (SUPPORT_FLASH_READ == "Enabled")>
// *********************************************************************************************************************
// Request Flash Read over range using Bootloader
// [<CMD><Data Length(Low, High) Bytes to Read><unused><unused><Address(Low,High,Upper,Extended)to Start Reading From>]
// IN: [<0x01><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE>...]
// OUT: [<0x01><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE><DATA>...]
// *********************************************************************************************************************
static uint8_t BL_ReadFlash(void)
{
<#if (AVR_PAGE == "Expanded")>
	flash_address_t adr = ( ( (flash_address_t)frame.address_E << 24)
					| ( (flash_address_t)frame.address_U << 16)
					| ( (flash_address_t)frame.address_H << 8)
					| frame.address_L);
</#if> 
<#if (AVR_PAGE == "Classic")>
	flash_address_t adr = ( ( (flash_address_t)frame.address_H << 8) 
					| frame.address_L);
</#if>
	uint16_t dataIndex;
	
	for (dataIndex = 0; dataIndex < frame.data_length; dataIndex++)
	{
		frame.data[dataIndex] = FLASH_Read(adr);
		++adr;
	}
	return (frame.data_length + BL_HEADER);
}
</#if>

// ********************************************************************************************************************
// Request Flash Write over range, with supplied data using Bootloader
// [<CMD><Data Length(Low, High) Bytes to Write><unused><unused><Address(Low,High,Upper,Extended)to Start Writing To>]
// IN: [<0x02><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE><DATA>...]
// OUT: [<0x02><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE><STATUS_RESPONSE>...]
// ********************************************************************************************************************
static uint8_t BL_WriteFlash(void)
{
    nvm_status_t errorStatus = NVM_OK;
	uint8_t dataIndex = 0;
<#if (AVR_PAGE == "Expanded")>

	flash_address_t adr = ( ( (flash_address_t)frame.address_E << 24) 
					| ( (flash_address_t)frame.address_U << 16) 
					| ( (flash_address_t)frame.address_H << 8) 
					| frame.address_L);
</#if> 
<#if (AVR_PAGE == "Classic")>
	flash_address_t adr = ( ( (flash_address_t)frame.address_H << 8) 
					| frame.address_L);
</#if>
	
	if (adr < START_OF_APP)
	{
		frame.data[dataIndex++] = ERROR_ADDRESS_OUT_OF_RANGE;
		return (BL_HEADER + dataIndex);
	}
	
	errorStatus = FLASH_RowWrite(adr, frame.data);

	frame.data[dataIndex++] = errorStatus == NVM_OK ? COMMAND_PROCESSED_SUCCESSFULLY : COMMAND_PROCESSING_ERROR;

	return (BL_HEADER + dataIndex);
}

// *********************************************************************************************************************
// Request Flash Memory Erase over range using Bootloader
// Erases data_length rows from program memory
// [<CMD><Data Length(Low, High) Bytes to Erase><unused><unused><Address(Low,High,Upper,Extended)to Start Erasing From>]
// IN: [<0x03><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE>...]
// OUT: [<0x03><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE><STATUS_RESPONSE>...]
// *********************************************************************************************************************
static uint8_t BL_EraseFlash(void)
{
    nvm_status_t errorStatus = NVM_OK;
	uint8_t dataIndex = 0;
	flash_address_t adr = 0; 

<#if (ERASE_CONTROL == "FullErase")>
	adr = START_OF_APP;
	
	while (adr < FLASHEND)
	{
		errorStatus = FLASH_PageErase(adr);
	
        if (errorStatus != NVM_OK)
        {
            break;
        }
		adr += PROGMEM_PAGE_SIZE;	
    }
    
	frame.data[dataIndex++] = errorStatus == NVM_OK ? COMMAND_SUCCESS : COMMAND_PROCESSING_ERROR;
	
</#if>
<#if (ERASE_CONTROL == "BlockBased")>
<#if (AVR_PAGE == "Expanded")>
	adr = ( ( ( (flash_address_t)frame.address_E << 24)
			| ( (flash_address_t)frame.address_U << 16)
			| ( (flash_address_t)frame.address_H << 8)
			| frame.address_L) );
</#if> 
<#if (AVR_PAGE == "Classic")>
	adr = ( ( (flash_address_t)frame.address_H << 8) 
			| frame.address_L);
</#if> 

	if ((adr  < START_OF_APP) || (adr > FLASHEND) )
	{
		frame.data[dataIndex++] = ERROR_ADDRESS_OUT_OF_RANGE;
		return (BL_HEADER + dataIndex);
	}
	
<#if (AVR_PAGE == "Expanded")>
	for (uint32_t i = 0; i < frame.data_length; i ++)
</#if>
<#if (AVR_PAGE == "Classic")>
	for (uint16_t i = 0; i < frame.data_length; i ++)
</#if>
	{
		errorStatus = FLASH_PageErase(adr);
	
        if (errorStatus != NVM_OK)
        {
            break;
        }
		adr += PROGMEM_PAGE_SIZE;
	}
	frame.data[dataIndex++]  = errorStatus == NVM_OK ? COMMAND_PROCESSED_SUCCESSFULLY : COMMAND_PROCESSING_ERROR;
</#if>
	return (BL_HEADER + dataIndex);
}

<#if (SUPPORT_EE_DATA_READ == "Enabled")>
// *********************************************************************************************************************
// Request Reading EEPROM data over range using Bootloader
// [<CMD><Data Length(Low, High) Bytes to Read><unused><unused><Address(Low,High,Upper,Extended)to Start Reading From>]
// IN: [<0x04><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE>...]
// OUT: [<0x04><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE><DATA>...]
// *********************************************************************************************************************
static uint8_t BL_ReadEEData(void)
{
	uint8_t dataIndex;
	eeprom_address_t adr;

<#if (EEPROM_SIZE_U == "Expanded")>
	adr = ( ( (eeprom_address_t)frame.address_H << 8) 
			| frame.address_L);
</#if> 
<#if (EEPROM_SIZE_U == "Classic")>
	adr = frame.address_L;
</#if> 
    
	for (dataIndex = 0; dataIndex < frame.data_length; dataIndex++)
	{
		frame.data[dataIndex] = EEPROM_Read(adr);
		++adr;
	}

	return (frame.data_length + BL_HEADER);
}
</#if>

<#if (SUPPORT_EE_DATA_WRITE == "Enabled")>
// ********************************************************************************************************************
// Request EEPROM Writing over range, with supplied data using Bootloader
// [<CMD><Data Length(Low, High) Bytes to Write><unused><unused><Address(Low,High,Upper,Extended)to Start Writing To>]
// IN: [<0x05><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE><DATA>...]
// OUT: [<0x05><DataLengthL><DataLengthH><unused><unused><ADDRL><ADDRH><ADDRU><ADDRE><STATUS_RESPONSE>...]
// ********************************************************************************************************************
static uint8_t  BL_WriteEEData(void)
{
	uint8_t dataIndex = 0;
	eeprom_address_t adr;
	
<#if (EEPROM_SIZE_U == "Expanded")>
	adr = ( ( (eeprom_address_t)frame.address_H << 8) 
			| frame.address_L);
</#if>
<#if (EEPROM_SIZE_U == "Classic")>
	adr = frame.address_L;
</#if> 	
	for(dataIndex = 0; dataIndex < frame.data_length; dataIndex++)
	{
		EEPROM_Write(adr, frame.data[dataIndex]);
		++adr;
	}
	
	dataIndex = 0;
	frame.data[dataIndex++] = COMMAND_PROCESSED_SUCCESSFULLY;

	return (BL_HEADER + dataIndex);
}
</#if>

// **************************************************************************************
// Request Read device FUSE setting using bootloader
// In:	[<0x06><DataLengthL><unused> <unused><unused> <ADDRL><ADDRH><ADDRU><unused>...]
// OUT:	[9 byte header + data ]
// **************************************************************************************
static uint8_t BL_ReadConfig(void)
{
    uint8_t dataIndex = 0;
   
    frame.data[dataIndex++] = FUSE.WDTCFG;
    frame.data[dataIndex++] = FUSE.BODCFG;
    frame.data[dataIndex++] = FUSE.OSCCFG;
	

	frame.data[dataIndex++] = FUSE.reserved_1[0];    
<#if (FUSE == "Reserved_One_Byte")>
    frame.data[dataIndex++] = FUSE.TCD0CFG;
</#if>     
<#if (FUSE == "Reserved_Two_Byte")>
    frame.data[dataIndex++] = FUSE.reserved_1[1];	
</#if>

    frame.data[dataIndex++] = FUSE.SYSCFG0;
    frame.data[dataIndex++] = FUSE.SYSCFG1;
	
<#if (BOOT_BLOCK_SIZE_U == "512bytes")>
    frame.data[dataIndex++] = FUSE.CODESIZE;
    frame.data[dataIndex++] = FUSE.BOOTSIZE;
</#if>
<#if (BOOT_BLOCK_SIZE_U == "256bytes")>
    frame.data[dataIndex++] = FUSE.APPEND;
    frame.data[dataIndex++] = FUSE.BOOTEND;
</#if> 

	return (BL_HEADER + dataIndex);           // 9 byte header + data 
}

// **************************************************************************************
// Request Wrtie device FUSE setting using bootloader
// In:	[<0x07><DataLengthL><unused> <unused><unused> <ADDRL><ADDRH><ADDRU><unused>...]
// OUT:	[9 byte header + Status]
// **************************************************************************************
static uint8_t BL_WriteAppStatus (void)
{
	uint8_t dataIndex = 0;

	EEPROM_Write(E2END, frame.data[dataIndex]);
	
	frame.data[dataIndex++] = COMMAND_PROCESSED_SUCCESSFULLY;
	
	return (BL_HEADER + dataIndex);           // 9 byte header + Status
}

// **************************************************************************************
// Calculate Checksum
// In:	[<0x08><DataLengthL><DataLengthH> <unused><unused> <ADDRL><ADDRH><ADDRU><unused>...]
// OUT:	[9 byte header + CRC_STATUS]
// **************************************************************************************
static uint8_t BL_CalcChecksum(void)
{
	// not implementated yet
}

// **************************************************************************************
// Reset
// In:   [<0x09> <0x00><0x00> <0x00><0x00> <0x00><0x00><0x00><0x00>]
// OUT:	[9 byte header] + [ChecksumL + ChecksumH]
// **************************************************************************************
static uint8_t BL_Reset(void)
{
	uint8_t dataIndex = 1; 
	
	resetPending = true;
	return (BL_HEADER + dataIndex);
}