/*
 * Copyright (C) 2014 INRIA
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup lpc2387
 * @{
 */

/**
 * @file
 * @brief       lpc2387 in-application programming driver (for flashrom)
 *
 * @author      Oliver Hahm <oliver.hahm@inria.fr>
 *
 */

#include <stdint.h>
#include <includes.h>
#include "iolpc2368.h"

#define INVALID_ADDRESS 0xFF

/*
* Command codes for IAP
*/
#define PREPARE_SECTOR_FOR_WRITE_OPERATION      50
#define COPY_RAM_TO_FLASH   51
#define ERASE_SECTOR        52
#define BLANK_CHECK_SECTOR  53
#define READ_PART_ID        54
#define READ_BOOT_CODE_REV  55
#define COMPARE             56
#define REINVOKE_ISP        57

/*
* IAP status codes
*/
typedef enum {
    CMD_SUCCESS = 0,
    INVALID_COMMAND,
    SRC_ADDR_ERROR,
    DST_ADDR_ERROR,
    SRC_ADDR_NOT_MAPPED,
    DST_ADDR_NOT_MAPPED,
    COUNT_ERROR,
    INVALID_SECTOR,
    SECTOR_NOT_BLANK,
    SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION,
    COMPARE_ERROR,
    BUSY,
} __e_iap_status;

static unsigned int iap_command[5];         // contains parameters for IAP command
static unsigned int iap_result[2];          // contains results
typedef void (*IAP)(unsigned int[], unsigned int[]);    // typedefinition for IAP entry function
IAP IAP_Entry;

/* some function prototypes */
static uint32_t blank_check_sector(uint32_t tmp_sect1, uint32_t tmp_sect2);
static uint32_t prepare_sectors(uint32_t tmp_sect1, uint32_t tmp_sect2);
static uint32_t erase_sectors(uint32_t tmp_sect1, uint32_t tmp_sect2);
static uint32_t copy_ram_to_flash(uint32_t tmp_adr_dst, uint32_t tmp_adr_src, uint32_t tmp_size);
static uint32_t compare(uint32_t tmp_adr_dst, uint32_t tmp_adr_src, uint32_t tmp_size);
static uint32_t iap(uint32_t code, uint32_t p1, uint32_t p2, uint32_t p3, uint32_t p4);
static uint8_t iap_get_sector(uint32_t addr);

/******************************************************************************
 * P U B L I C   F U N C T I O N S
 *****************************************************************************/
uint8_t flashrom_write(uint8_t *dst, const uint8_t *src, size_t size)
{
    #if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr = 0;
    #endif

    char err;
    uint8_t sec;

    sec = iap_get_sector((uint32_t) dst);

    if (sec == INVALID_ADDRESS) {
        return 0;
    }

    /* check sector */
    if (blank_check_sector(sec, sec) == SECTOR_NOT_BLANK) {
    }

    /* prepare sector */
    err = prepare_sectors(sec, sec);
    if (err) {
        return 0;
    }

    /*  write flash */
    OS_ENTER_CRITICAL();
    err = copy_ram_to_flash((uint32_t) dst, (uint32_t) src, size);
    OS_EXIT_CRITICAL();

    if (err) {
        /* set interrupts back and return */
        return 0;
    }
    /* check result */
    err = compare((uint32_t) dst, (uint32_t) src, size);

    if (err) {
        return 0;
    }

    return 1;
}


uint8_t flashrom_erase(uint8_t *addr)
{
    #if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr = 0;
    #endif
    uint8_t sec = iap_get_sector((uint32_t) addr);

    if (sec == INVALID_ADDRESS) {
        return 0;
    }

    /* check sector */
    if (!blank_check_sector(sec, sec)) {
        return 1;
    }

    /* prepare sector */
    if (prepare_sectors(sec, sec)) {
        return 0;
    }

    OS_ENTER_CRITICAL();

    /* erase sector */
    if (erase_sectors(sec, sec)) {
        OS_EXIT_CRITICAL();
        return 0;
    }

    OS_EXIT_CRITICAL();

    /* check again */
    if (blank_check_sector(sec, sec)) {
        return 0;
    }

    return 1;
}


/******************************************************************************
 * PRIVATE FUNCTIONS
 *****************************************************************************/

static uint32_t iap(uint32_t code, uint32_t p1, uint32_t p2, uint32_t p3, uint32_t p4)
{
    iap_command[0] = code;      // set command code
    iap_command[1] = p1;        // set 1st param
    iap_command[2] = p2;        // set 2nd param
    iap_command[3] = p3;        // set 3rd param
    iap_command[4] = p4;        // set 4th param

    ((void (*)())0x7ffffff1)(iap_command, iap_result);      // IAP entry point
    return *iap_result;
}

/******************************************************************************
 * Function:    blank_check_sector
 *
 * Description: This command is used to blank check a sector or multiple sectors
 *              of on-chip Flash memory. To blank check a single sector use the
 *              same "Start" and "End" sector numbers.
 *              Command: 53
 *              Param0: Start Sector Number
 *              Param1: End Sector Number (should be greater than equal to the start
 *                      sector number)
 *
 * Parameters:  long tmp_sect1:     Param0
 *              long tmp_sect2:     Param1
 *
 * Return:      Code    CMD_SUCCESS |
 *                      BUSY |
 *                      SECTOR_NOT_BLANK |
 *                      INVALID_SECTOR
 *              Result0: Offset of the first non blank word location if the status code is SECTOR_NOT_BLANK.
 *              Result1: Contents of non blank wird location.
 *****************************************************************************/
uint32_t blank_check_sector(uint32_t tmp_sect1, uint32_t tmp_sect2)
{
    return iap(BLANK_CHECK_SECTOR, tmp_sect1, tmp_sect2, 0 , 0);
}


/******************************************************************************
 * Function:    copy_ram_to_flash
 *
 * Description: This command is used to programm the flash memory. the affected should be
 *              prepared first by calling "Prepare Sector for Write Operation" command. the
 *              affected sectors are automatically protected again once the copy command is
 *              successfully executed. the boot sector cannot be written by this command.
 *              Command: 51
 *              Param0: (DST) Destination Flash adress where data bytes are to be written.
 *                      This address should be a 512 byte boundary.
 *              Param1: (SRC) Source RAM adress from which data byre are to be read.
 *              Param2: Number of bytes to be written. Should be 512 | 1024 | 4096 | 8192.
 *              Param3: System Clock Frequency (CCLK) in KHz.
 *
 * Parameters:  long tmp_adr_dst:   Param0
 *              long tmp_adr_src:   Param1
 *              long tmp_size:      Param2
 *
 * Return:      Code    CMD_SUCCESS |
 *                      SRC_ADDR_ERROR (Address not on word boundary) |
 *                      DST_ADDR_ERROR (Address not on correct boundary) |
 *                      SRC_ADDR_NOT_MAPPED |
 *                      DST_ADDR_NOT_MAPPED |
 *                      COUNT_ERROR (Byte count is not 512 | 1024 | 4096 | 8192) |
 *                      SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION |
 *                      BUSY
 *****************************************************************************/
uint32_t copy_ram_to_flash(uint32_t tmp_adr_dst, uint32_t tmp_adr_src, uint32_t tmp_size)
{
    return iap(COPY_RAM_TO_FLASH, tmp_adr_dst, tmp_adr_src, tmp_size, BSP_CPU_ClkFreq() / 1000);
}


/******************************************************************************
 * Function:    Prepare_Sector
 *
 * Description: This command must be executed before executing "Copy RAM to Flash" or "Erase Sector(s)"
 *              command. Successful execution of the "Copy RAM to Flash" or "Erase Sector(s)" command causes
 *              relevant sectors to be protected again. The boot sector can not be prepared by this command. To
 *              prepare a single sector use the same "Start" and "End" sector numbers..
 *              Command code: 50
 *              Param0: Start Sector Number
 *              Param1: End Sector Number: Should be greater than or equal to start sector number.
 *
 * Parameters:  long tmp_sect1:     Param0
 *              long tmp_sect2:     Param1
 *
 * Return:      Code    CMD_SUCCESS |
 *                      BUSY |
 *                      INVALID_SECTOR
 *****************************************************************************/
uint32_t prepare_sectors(uint32_t tmp_sect1, uint32_t tmp_sect2)
{
    return iap(PREPARE_SECTOR_FOR_WRITE_OPERATION, tmp_sect1, tmp_sect2, 0 , 0);
}


/******************************************************************************
 * Function:    erase_sectors
 *
 * Description: This command is used to erase a sector or multiple sectors of on-chip Flash memory. The boot
 *              sector can not be erased by this command. To erase a single sector use the same "Start" and "End"
 *              sector numbers.
 *              Command code: 52
 *              Param0: Start Sector Number
 *              Param1: End Sector Number: Should be greater than or equal to start sector number.
 *              Param2: System Clock Frequency (CCLK) in KHz.
 *
 * Parameters:  long tmp_sect1:     Param0
 *              long tmp_sect2:     Param1
 *
 * Return:      Code    CMD_SUCCESS |
 *                      BUSY |
 *                      SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION |
 *                      INVALID_SECTOR
 *****************************************************************************/
uint32_t erase_sectors(uint32_t tmp_sect1, uint32_t tmp_sect2)
{
    return iap(ERASE_SECTOR, tmp_sect1, tmp_sect2, BSP_CPU_ClkFreq() / 1000, 0);
}


/******************************************************************************
 * Function:    compare
 *
 * Description: This command is used to compare the memory contents at two locations. compare result may not
 *              be correct when source or destination address contains any of the first 64 bytes starting
 *              from address zero. First 64 bytes can be re-mapped to RAM.
 *              Command Code: 56
 *              Param0(DST): Starting Flash or RAM address from where data bytes are to be
 *                              address should be a word boundary.
 *              Param1(SRC): Starting Flash or RAM address from where data bytes are to be
 *                              address should be a word boundary.
 *              Param2: Number of bytes to be compared. Count should be in multiple of 4.
 *
 * Parameters:  long tmp_adr_dst
 *              long tmp_adr_src
 *              long tmp_size
 *
 * Return:      Code    CMD_SUCCESS |
 *                      COMPARE_ERROR |
 *                      COUNT_ERROR (Byte count is not multiple of 4) |
 *                      ADDR_ERROR |
 *                      ADDR_NOT_MAPPED
 *              Result0: Offset of the first mismatch if the Status Code is COMPARE_ERROR.
 *****************************************************************************/
uint32_t compare(uint32_t tmp_adr_dst, uint32_t tmp_adr_src, uint32_t tmp_size)
{
    return iap(COMPARE, tmp_adr_dst, tmp_adr_src, tmp_size, 0);
}

static uint8_t iap_get_sector(uint32_t addr)
{
    if (addr <= 0x00000FFF) {
        return 0;
    }

    if ((addr >= 0x00001000) && (addr <= 0x00001FFF)) {
        return 1;
    }

    if ((addr >= 0x00002000) && (addr <= 0x00002FFF)) {
        return 2;
    }

    if ((addr >= 0x00003000) && (addr <= 0x00003FFF)) {
        return 3;
    }

    if ((addr >= 0x00004000) && (addr <= 0x00004FFF)) {
        return 4;
    }

    if ((addr >= 0x00005000) && (addr <= 0x00005FFF)) {
        return 5;
    }

    if ((addr >= 0x00006000) && (addr <= 0x00006FFF)) {
        return 6;
    }

    if ((addr >= 0x00007000) && (addr <= 0x00007FFF)) {
        return 7;
    }

    if ((addr >= 0x00008000) && (addr <= 0x0000FFFF)) {
        return 8;
    }

    if ((addr >= 0x00010000) && (addr <= 0x00017FFF)) {
        return 9;
    }

    if ((addr >= 0x00018000) && (addr <= 0x0001FFFF)) {
        return 10;
    }

    if ((addr >= 0x00020000) && (addr <= 0x00027FFF)) {
        return 11;
    }

    if ((addr >= 0x00028000) && (addr <= 0x0002FFFF)) {
        return 12;
    }

    if ((addr >= 0x00030000) && (addr <= 0x00037FFF)) {
        return 13;
    }

    if ((addr >= 0x00038000) && (addr <= 0x0003FFFF)) {
        return 14;
    }

    if ((addr >= 0x00040000) && (addr <= 0x00047FFF)) {
        return 15;
    }

    if ((addr >= 0x00048000) && (addr <= 0x0004FFFF)) {
        return 16;
    }

    if ((addr >= 0x00050000) && (addr <= 0x00057FFF)) {
        return 17;
    }

    if ((addr >= 0x00058000) && (addr <= 0x0005FFFF)) {
        return 18;
    }

    if ((addr >= 0x00060000) && (addr <= 0x00067FFF)) {
        return 19;
    }

    if ((addr >= 0x00068000) && (addr <= 0x0006FFFF)) {
        return 20;
    }

    if ((addr >= 0x00070000) && (addr <= 0x00077FFF)) {
        return 21;
    }

    if ((addr >= 0x00078000) && (addr <= 0x00078FFF)) {
        return 22;
    }

    if ((addr >= 0x00079000) && (addr <= 0x00079FFF)) {
        return 23;
    }

    if ((addr >= 0x0007A000) && (addr <= 0x0007AFFF)) {
        return 24;
    }

    if ((addr >= 0x0007B000) && (addr <= 0x0007BFFF)) {
        return 25;
    }

    if ((addr >= 0x0007C000) && (addr <= 0x0007CFFF)) {
        return 26;
    }

    if ((addr >= 0x0007D000) && (addr <= 0x0007DFFF)) {
        return 27;
    }

    /* no valid address within flash */
    return INVALID_ADDRESS;
}
