/*
 * Recovered C-like source from STM32_FlashLoader.axf.
 *
 * Source hints came from DWARF symbols in the AXF:
 *   C:\Documents and Settings\junseok\...\STM32\A1000_Flash_loader\main.c
 *   RVCT3.0 [Build 441], Cortex-M3 Thumb code, loaded at 0x20000000.
 *
 * This is not the original source. It is a readable reconstruction of the
 * business logic and the flash routines visible in the disassembly.
 */

#include <stdint.h>

typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef volatile uint32_t vu32;

typedef enum {
    FLASH_BUSY = 1,
    FLASH_ERROR_PG = 2,
    FLASH_ERROR_WRP = 3,
    FLASH_COMPLETE = 4,
    FLASH_TIMEOUT = 5
} FLASH_Status;

typedef enum {
    FAILED = 0,
    PASSED = 1
} TestStatus;

typedef enum {
    ERROR = 0,
    SUCCESS = 1
} ErrorStatus;

typedef enum {
    RESET = 0,
    SET = 1
} FlagStatus;

typedef enum {
    DISABLE = 0,
    ENABLE = 1
} FunctionalState;

typedef struct {
    u32 Command;        /* +0x00 */
    u32 Result;         /* +0x04 */
    u32 Reserved1[4];   /* +0x08 */
    u32 Reserved2;      /* +0x18 */
    u32 TargetAddr;     /* +0x1c */
    u32 Length;         /* +0x20 */
    u32 Reserved3;      /* +0x24 */
    u32 Reserved4;      /* +0x28 */
    u32 Reserved5;      /* +0x2c */
    u32 Reserved6;      /* +0x30 */
    u32 Reserved7;      /* +0x34 */
    u32 Reserved8;      /* +0x38 */
    u8 Buf[8192];       /* +0x3c */
} FLASH_INFO;

#define ERASE_COUNTER_ADDR  0x20000cd8u
#define NBR_OF_PAGE_ADDR    0x20000cdcu
#define FLASH_STATUS_ADDR   0x20000ce0u
#define MEM_STATUS_ADDR     0x20000ce1u
#define HSE_STATUS_ADDR     0x20000ce2u
#define FLASH_INFO_ADDR     0x20000ce4u
#define FLASH_BASE          0x08000000u
#define FLASH_PAGE_SIZE     0x400u
#define FLASH_PAGE_COUNT    0x80u
#define FLASH_TIMEOUT_LONG  0x0fffu
#define FLASH_TIMEOUT_PROG  0x000fu

#define FLASH_ACR           (*(volatile u32 *)0x40022000u)
#define FLASH_KEYR          (*(volatile u32 *)0x40022004u)
#define FLASH_SR            (*(volatile u32 *)0x4002200cu)
#define FLASH_CR            (*(volatile u32 *)0x40022010u)
#define FLASH_AR            (*(volatile u32 *)0x40022014u)
#define FLASH_OBR           (*(volatile u32 *)0x4002201cu)

#define FLASH_KEY1          0x45670123u
#define FLASH_KEY2          0xcdef89abu

#define EraseCounter        (*(volatile u32 *)ERASE_COUNTER_ADDR)
#define NbrOfPage           (*(volatile u32 *)NBR_OF_PAGE_ADDR)
#define FLASHStatus         (*(volatile FLASH_Status *)FLASH_STATUS_ADDR)
#define MemoryProgramStatus (*(volatile TestStatus *)MEM_STATUS_ADDR)
#define HSEStartUpStatus    (*(volatile ErrorStatus *)HSE_STATUS_ADDR)
#define Flash_Info          (*(volatile FLASH_INFO *)FLASH_INFO_ADDR)

void RCC_DeInit(void);
void RCC_HSEConfig(u32 RCC_HSE);
ErrorStatus RCC_WaitForHSEStartUp(void);
void RCC_HCLKConfig(u32 RCC_SYSCLK);
void RCC_PCLK1Config(u32 RCC_HCLK);
void RCC_PCLK2Config(u32 RCC_HCLK);
void RCC_PLLConfig(u32 RCC_PLLSource, u32 RCC_PLLMul);
void RCC_PLLCmd(FunctionalState NewState);
FlagStatus RCC_GetFlagStatus(u8 RCC_FLAG);
void RCC_SYSCLKConfig(u32 RCC_SYSCLKSource);
u8 RCC_GetSYSCLKSource(void);
void NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset);
void FLASH_SetLatency(u32 FLASH_Latency);
void FLASH_PrefetchBufferCmd(u32 FLASH_PrefetchBuffer);

void delay(void)
{
    u32 i = 0x0fff;
    while (i != 0) {
        i--;
    }
}

FLASH_Status FLASH_GetStatus(void)
{
    if ((FLASH_SR & 0x01u) == 0x01u) {
        return FLASH_BUSY;
    }
    if ((FLASH_SR & 0x04u) != 0) {
        return FLASH_ERROR_PG;
    }
    if ((FLASH_SR & 0x10u) != 0) {
        return FLASH_ERROR_WRP;
    }
    return FLASH_COMPLETE;
}

FLASH_Status FLASH_WaitForLastOperation(u32 timeout)
{
    FLASH_Status status = FLASH_GetStatus();

    while (status == FLASH_BUSY && timeout != 0) {
        delay();
        status = FLASH_GetStatus();
        timeout--;
    }

    if (timeout == 0) {
        status = FLASH_TIMEOUT;
    }

    return status;
}

void FLASH_Unlock(void)
{
    FLASH_KEYR = FLASH_KEY1;
    FLASH_KEYR = FLASH_KEY2;
}

void FLASH_Lock(void)
{
    FLASH_CR |= 0x80u;
}

void FLASH_ClearFlag(u32 flags)
{
    FLASH_SR = flags;
}

FLASH_Status FLASH_ErasePage(u32 pageAddress)
{
    FLASH_Status status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_LONG);

    if (status == FLASH_COMPLETE) {
        FLASH_CR |= 0x02u;          /* PER */
        FLASH_AR = pageAddress;
        FLASH_CR |= 0x40u;          /* STRT */

        status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_LONG);

        if (status != FLASH_BUSY) {
            FLASH_CR &= 0x00001ffdu; /* clear PER */
        }
    }

    return status;
}

FLASH_Status FLASH_EraseAllPages(void)
{
    FLASH_Status status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_LONG);

    if (status == FLASH_COMPLETE) {
        FLASH_CR |= 0x04u;          /* MER */
        FLASH_CR |= 0x40u;          /* STRT */

        status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_LONG);

        if (status != FLASH_BUSY) {
            FLASH_CR &= 0x00001ffbu; /* clear MER */
        }
    }

    return status;
}

FLASH_Status FLASH_ProgramHalfWord(u32 targetP, u16 *srcP)
{
    FLASH_Status status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_PROG);

    if (status == FLASH_COMPLETE) {
        FLASH_CR |= 0x01u;                  /* PG */
        *(volatile u16 *)targetP = *srcP;

        status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_PROG);

        if (status != FLASH_BUSY) {
            FLASH_CR &= 0x00001ffeu;        /* clear PG */
        }
    }

    return status;
}

FlagStatus FLASH_GetReadOutProtectionStatus(void)
{
    return (FLASH_OBR & 0x02u) ? SET : RESET;
}

FlagStatus FLASH_GetFlagStatus(u32 flag)
{
    return ((FLASH_SR & flag) == flag) ? SET : RESET;
}

void ReadChipID(void)
{
    Flash_Info.Result = 1;
}

void EraseSector(void)
{
    int i;

    Flash_Info.Result = 0;

    for (i = 0; i < (int)(NbrOfPage - 1); i++) {
        u32 nextPage = FLASH_BASE + ((u32)(i + 1) * FLASH_PAGE_SIZE);
        if (nextPage > Flash_Info.TargetAddr) {
            break;
        }
    }

    while ((u32)i < (NbrOfPage - 1) && FLASHStatus == FLASH_COMPLETE) {
        u32 endAddr = Flash_Info.TargetAddr + Flash_Info.Length;
        u32 pageAddr = FLASH_BASE + ((u32)i * FLASH_PAGE_SIZE);

        if (endAddr <= pageAddr) {
            break;
        }

        FLASHStatus = FLASH_ErasePage(pageAddr);
        if (FLASHStatus == 0) {
            return;
        }

        i++;
    }

    Flash_Info.Result = 1;
}

void EraseAll(void)
{
    EraseCounter = 0;

    while (EraseCounter < NbrOfPage && FLASHStatus == FLASH_COMPLETE) {
        FLASHStatus = FLASH_ErasePage(FLASH_BASE + (EraseCounter * FLASH_PAGE_SIZE));
        EraseCounter++;
    }

    Flash_Info.Result = 1;
}

void Program(void)
{
    u32 targetP = Flash_Info.TargetAddr;
    u16 *srcP = (u16 *)Flash_Info.Buf;
    u32 i = 0;

    while (i < Flash_Info.Length) {
        FLASHStatus = FLASH_ProgramHalfWord(targetP, srcP);
        i += 2;
        targetP += 2;
        srcP++;
    }

    Flash_Info.Result = 1;
}

void NVIC_Configuration(void)
{
    NVIC_SetVectorTable(FLASH_BASE, 0);
}

void RCC_Configuration(void)
{
    RCC_DeInit();
    RCC_HSEConfig(0x00010000u);
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    if (HSEStartUpStatus == SUCCESS) {
        RCC_HCLKConfig(0);
        RCC_PCLK2Config(0);
        RCC_PCLK1Config(0x400u);

        FLASH_SetLatency(2);
        FLASH_PrefetchBufferCmd(0x10);

        RCC_PLLConfig(0x00010000u, 0x001c0000u);
        RCC_PLLCmd(ENABLE);

        while (RCC_GetFlagStatus(0x39) == RESET) {
        }

        RCC_SYSCLKConfig(2);

        while (RCC_GetSYSCLKSource() != 0x08) {
        }
    }
}

void Flash_Init(void)
{
    FLASHStatus = FLASH_COMPLETE;
    MemoryProgramStatus = PASSED;

    RCC_Configuration();
    NVIC_Configuration();
    FLASH_Unlock();

    NbrOfPage = FLASH_PAGE_COUNT;
    FLASH_ClearFlag(0x35);
}

void Main(void)
{
    Flash_Init();

    switch (Flash_Info.Command) {
    case 0:
        Program();
        break;
    case 1:
        EraseAll();
        break;
    case 2:
        EraseSector();
        break;
    case 3:
        ReadChipID();
        break;
    default:
        Flash_Info.Result = 0;
        break;
    }
}

int BlankCheck(unsigned long targetAddr, unsigned long targetSize)
{
    volatile unsigned long *targetP = (volatile unsigned long *)targetAddr;
    unsigned long i = 0;

    while (i < targetSize) {
        if (*targetP++ != 0xffffffffu) {
            return 0;
        }
        i += 4;
    }

    return 1;
}

int Verification(unsigned long targetAddr, unsigned long limit, unsigned long srcAddr)
{
    volatile unsigned long *targetP = (volatile unsigned long *)targetAddr;
    volatile unsigned long *sourceP = (volatile unsigned long *)srcAddr;
    int i = 0;

    while ((unsigned long)i < limit) {
        if (*targetP++ != *sourceP++) {
            return 0;
        }
        i += 4;
    }

    return 1;
}
