#include "main.h"

// Configure clocks based on a 8 MHz external crystal for:
// SYSCLK, AHB, APB2 72 Mhz
// APB1, ADC 36 MHz
static inline void Clock_Init(void)
{
    // Activate HSE and wait for it to be ready
    RCC->CR = RCC_CR_HSEON | RCC_CR_HSION;
    while(!(RCC->CR & RCC_CR_HSERDY));

    RCC->CFGR = RCC_CFGR_SW_0;
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_0);

    FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_1;

    // Set PLL to x9 (-> 72MHz system clock)
    RCC->CFGR = RCC_CFGR_PLLMULL9 | RCC_CFGR_PLLSRC | RCC_CFGR_PPRE1_2
        | RCC_CFGR_SW_0;

    // Activate PLL and wait
    RCC->CR = RCC_CR_PLLON | RCC_CR_HSEON | RCC_CR_HSION;
    while(!(RCC->CR & RCC_CR_PLLRDY));

    // Select PLL as clock source
    RCC->CFGR = RCC_CFGR_PLLMULL9 | RCC_CFGR_PLLSRC | RCC_CFGR_PPRE1_2
        | RCC_CFGR_SW_1;

    // Disable all interrupts
    RCC->CIR = 0x00000000;

    // Enable peripheral clocks
    RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN;
    RCC->APB1ENR = RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN | RCC_APB1ENR_USBEN;
    RCC->AHBENR = RCC_AHBENR_CRCEN;
}

static inline bool Bootloader_EntryCondition(void)
{
    // Activate pull-up for test point
    GPIOA->CRH = (0x44444444
        & ~(0x0f << (PIN_USB_PULLUP * 4 - 32)))
        | (0x08<< (PIN_USB_PULLUP * 4 - 32))
        ;
    GPIOA->BSRR = (1 << PIN_USB_PULLUP);

    // The first word in the application frame is the stack end pointer
    const uint32_t *application_start = (uint32_t*)FLASH_APPLICATION_BASE;
    // If it is not a valid RAM address, we can assume there's no application
    // present in flash and thus enter the bootloader
    if((*application_start & 0xffff8000) != 0x20000000)
    {
        return true;
    }

    // Check RTC backup register 1 for magic value
    PWR->CR = PWR_CR_DBP;
    if(BKP->DR1 == 0xb007)
    {
        return true;
    }

    // Check if test point is held low externally
    if(~GPIOA->IDR & (1 << PIN_USB_PULLUP))
    {
        return true;
    }

    return false;
}

static inline void Bootloader_Exit(void)
{
    // Reset RTC backup register
    BKP->DR1 = 0x0000;

    // Reset peripherals
    RCC->APB1RSTR = RCC_APB1RSTR_USBRST;
    RCC->APB2RSTR = RCC_APB2RSTR_IOPARST | RCC_APB2RSTR_AFIORST;
    RCC->APB1RSTR = 0x00000000;
    RCC->APB2RSTR = 0x00000000;

    // Reset peripheral clock enable registers to their reset values
    RCC->AHBENR = 0x00000014;   // Disable CRC
    RCC->APB2ENR = 0x00000000;  // Disable GPIOA, GPIOB, AFIO
    RCC->APB1ENR = 0x00000000;  // Disable USB, PWR, BKP

    // Set vector table location
    SCB->VTOR = FLASH_APPLICATION_BASE - FLASH_BASE;

    // Set stack pointer and jump into application
    uint32_t application_stack_pointer = *(uint32_t*)FLASH_APPLICATION_BASE;
    uint32_t application_reset_handler = *(uint32_t*)(FLASH_APPLICATION_BASE
        + 4);
    __asm__ volatile(".syntax unified \n"
        "msr msp, %[stack_pointer] \n"
        "bx %[reset_handler] \n"
        :
        : [stack_pointer] "r" (application_stack_pointer),
          [reset_handler] "r" (application_reset_handler));
}

int main(void)
{
    Clock_Init();

    if(Bootloader_EntryCondition())
    {
        USB_Init();

        while(USB_Poll());

        // Delay to allow answer token to be fetched by host
        Util_Delay(100000);
    }

    Bootloader_Exit();
}

