/*
 * Copyright 2018 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_debug_console.h"
#include "board.h"
#include "fsl_iap.h"
#include "fsl_iap_ffr.h"
#include "fsl_common.h"
#include "pin_mux.h"
#include "fsl_crc.h"

#include "ack_lpc55s16_ota.h"
////////////////////////////////////////////////////////////////////////////////
// Definitions
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Prototypes
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Variables
////////////////////////////////////////////////////////////////////////////////
static flash_config_t flashInstance;
////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

static void InitCrc32(CRC_Type *base, uint32_t seed)
{
    crc_config_t config;

    config.polynomial    = kCRC_Polynomial_CRC_32;
    config.reverseIn     = true;
    config.complementIn  = false;
    config.reverseOut    = true;
    config.complementOut = true;
    config.seed          = seed;

    CRC_Init(base, &config);
}

// Determines whether there's an unapplied OTA image in the Staging Partition.
bool CheckStatusPartitionForUnappliedImage(void)
{
    volatile ACKLpc55s16OtaStatusPartition_t* pStatusPartition
        = (ACKLpc55s16OtaStatusPartition_t*)ACK_LPC55S16_OTA_STATUS_PARTITION_START;
    
    uint32_t status;
    
    status = FLASH_VerifyErase(&flashInstance, ACK_LPC55S16_OTA_STATUS_PARTITION_START, ACK_LPC55S16_OTA_STATUS_PARTITION_SIZE);
    if(status == kStatus_Success)
    {
        return false;
    }

    if ((pStatusPartition->Signature1 != ACK_LPC55S16_OTA_STATUS_PARTITION_SIGNATURE1)
        || (pStatusPartition->Signature2 != ACK_LPC55S16_OTA_STATUS_PARTITION_SIGNATURE2)
        || pStatusPartition->Pad[0]
        || pStatusPartition->Pad[1]
        || pStatusPartition->Pad[2]
        || (pStatusPartition->State != ACK_LPC55S16_OTA_STATUS_PARTITION_STATE_UNAPPLIED_IMAGE)
        || (pStatusPartition->ImageSize == 0)
        || (pStatusPartition->ImageSize > ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE)
        || (pStatusPartition->ImageSize > ACK_LPC55S16_OTA_PRIMARY_PARTITION_SIZE)
        || (pStatusPartition->ImageStartAddress != ACK_LPC55S16_OTA_PRIMARY_PARTITION_START))
    {
        // Signature or pad bytes not valid, or they are valid and the state doesn't indicate
        // the presence of an unapplied OTA image. Or the image size is 0 or too large, or the
        // image is intended for a different address than where the Primary Partition is.
        return false;
    }

    // Check CRC32 of data bytes.
    InitCrc32(CRC_ENGINE, 0xFFFFFFFFU);
    CRC_WriteData(CRC_ENGINE, (const uint8_t*)ACK_LPC55S16_OTA_STAGING_PARTITION_START, pStatusPartition->ImageSize);
    uint32_t crc = CRC_Get32bitResult(CRC_ENGINE);

    if (crc != pStatusPartition->Crc32)
    {
        return false;
    }

    return true;
}

// Programs flash representing the Primary Partition with the contents of the Staging Partition.
bool ApplyOtaImage(void)
{
    uint32_t status;

    volatile ACKLpc55s16OtaStatusPartition_t* pStatusPartition 
        = (ACKLpc55s16OtaStatusPartition_t*)ACK_LPC55S16_OTA_STATUS_PARTITION_START;

    uint32_t size = ALIGN_UP(pStatusPartition->ImageSize,512);
    
    status = FLASH_Erase(&flashInstance, ACK_LPC55S16_OTA_PRIMARY_PARTITION_START, ACK_LPC55S16_OTA_PRIMARY_PARTITION_SIZE, kFLASH_ApiEraseKey);
    if(status != kStatus_Success)
    {
        return false;
    }
    
    status = FLASH_VerifyErase(&flashInstance, ACK_LPC55S16_OTA_PRIMARY_PARTITION_START, size);
    if(status != kStatus_Success)
    {
        return false;
    }
    
    status = FLASH_Program(&flashInstance, ACK_LPC55S16_OTA_PRIMARY_PARTITION_START, (uint8_t *)ACK_LPC55S16_OTA_STAGING_PARTITION_START, size);

    return status == kStatus_Success;
    return true;
}

bool EraseStatusPartitionToIndicateNoUnappliedImage(void)
{
    uint32_t status;
    
    status = FLASH_Erase(&flashInstance, ACK_LPC55S16_OTA_STATUS_PARTITION_START, ACK_LPC55S16_OTA_STATUS_PARTITION_SIZE, kFLASH_ApiEraseKey);
    if(status != kStatus_Success)
    {
        return false;
    }
    
    status = FLASH_VerifyErase(&flashInstance, ACK_LPC55S16_OTA_STATUS_PARTITION_START, ACK_LPC55S16_OTA_STATUS_PARTITION_SIZE);

    return status == kStatus_Success;
}

// deinit/reinitialize the hardware.
void UninitializeHardware(void)
{
    CRC_Deinit(CRC_ENGINE);
}

void JumpToApplication(uint32_t addr)
{
    static uint32_t sp, pc;
    uint32_t *vectorTable = (uint32_t*)addr;
    sp = vectorTable[0];
    pc = vectorTable[1];
    
    typedef void(*app_entry_t)(void);

    static app_entry_t s_application = 0;

    s_application = (app_entry_t)pc;

    // Change MSP and PSP
    __set_MSP(sp);
    __set_PSP(sp);
    
    SCB->VTOR = addr;
    
    // Jump to application
    s_application();

    // Should never reach here.
    __NOP();
}
// This routine does not return.
void Loader(void)
{
    // Check status partition to see whether there's an unapplied OTA image.
    if (CheckStatusPartitionForUnappliedImage())
    {
        if (ApplyOtaImage())
        {
            EraseStatusPartitionToIndicateNoUnappliedImage();
        }
    }

    // Now that we've applied an OTA image (or not) above, prepare to execute the application
    // on the Primary Partition.
    UninitializeHardware();

    // Does not return.
    JumpToApplication(ACK_LPC55S16_OTA_PRIMARY_PARTITION_START);
}

int main()
{
    BOARD_BootClockFROHF96M();
    
    FLASH_Init(&flashInstance);
    
    InitCrc32(CRC_ENGINE, 0xFFFFFFFFU);

    Loader();

    while (1)
    {
    }
}


////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////
