/*
 * flash.c: flashing user config and system option bytes
 *
 * by default, the pin used for PF2 is configured as a reset.
 * this code will check that the no-reset bit is set.
 * if it is not, this code will update the option bytes to
 * set the BOR voltage, enable BOR, and enable the no-reset bit.
 *
 * file creation: 20231019 0126
 */


#include "py32f0xx_conf.h"



/*
 * checks the flash option byte to determine if the reset pin
 * is configured to be PF2. if not, sets this bit as well as
 * the BOR values. if the bits were updated, they will be reloaded.
 * 
 * only call this function during startup, before interrupts
 * are enabled.
 * 
 * note: this code caused me to lose MCU. consider it buggy.
 */
void flash_optr_checkfix()
{
    FLASH_OBProgramInitTypeDef opt;    

    if ((FLASH->OPTR & FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_RESET) {
        // let's update this
        opt.OptionType = OPTIONBYTE_RDP | OPTIONBYTE_USER;
        opt.USERType   = 0xff00;
        opt.USERConfig = OB_BOR_ENABLE  | OB_BOR_LEVEL_2p3_2p4 | OB_IWDG_SW | OB_WWDG_SW | OB_RESET_MODE_GPIO | OB_BOOT1_SYSTEM | OB_RDP_LEVEL_0;

        if (LL_FLASH_Unlock() != SUCCESS) return;
        if (LL_FLASH_OB_Unlock() != SUCCESS) return;
        
        LL_FLASH_OBProgram(&opt);

        LL_FLASH_OB_Lock();
        LL_FLASH_Lock();

        // reload newly programmed option bytes   
        LL_FLASH_OB_Launch();
    }
}

void flash_write_timings_8MHz()
{
    // load flash write timings for 8MHz operation
    // (these are listed in the datasheet)
    // note: if you decide to operate at a different frequency, then either
    //       update these values, or reduce speed to 8MHz when writing to flash
    FLASH->TS0  = 0x3c;
    FLASH->TS2P = 0x3c;
    FLASH->TS3  = 0x3c;
    
    FLASH->TS1  = 0x90;
    FLASH->TPS3 = 0x240;

    FLASH->PERTPE  = 0x5dc0;
    FLASH->SMERTPE = 0x5dc0;
    FLASH->PRGTPE  = 0x1f40;
    FLASH->PRETPE  = 0x640;
}

__attribute__ ((long_call, section(".ramfunc"))) void flash_init()
{
    // configure flash timings
    flash_write_timings_8MHz();

    // is the PF2/NRST pin configured as reset?
    if ((FLASH->OPTR & FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_RESET) {
        // it is but shouldn't be, so let's configure it as GPIO.
        
        // per datasheet, we must unlock FLASH->CR and then unlock OPTLOCK
        FLASH->KEYR = FLASH_KEY1;
        FLASH->KEYR = FLASH_KEY2;
        if (FLASH->CR & FLASH_CR_LOCK) return;      // somehow, flash is still locked

        FLASH->OPTKEYR = FLASH_OPTKEY1;
        FLASH->OPTKEYR = FLASH_OPTKEY2;
        if (FLASH->CR & FLASH_CR_OPTLOCK) return;	// somehow, OB is still locked
    
        // write new OPTR values
        FLASH->OPTR = OB_RESET_MODE_GPIO | OB_WWDG_SW | OB_IWDG_SW | OB_BOR_LEVEL_2p3_2p4 | FLASH_OPTR_RDP_LEVEL_0;
                        // OB_BOR_ENABLE

        // enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
        // and signal that we want to write option bits,
        // then trigger the write by writing to a random address shown in the DS
        FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_OPTSTRT;
        *((__IO uint32_t *)(0x40022080)) = 0x55aa55aa;

        // wait for BSY to go low, then EOP to go high
        while (FLASH->SR & FLASH_SR_BSY);
        while (!(FLASH->SR & FLASH_SR_EOP));

        // do an OBL_LAUNCH reset to load the new option bits
        // this must be done while OPTLOCK is cleared
        FLASH->CR |= FLASH_CR_OBL_LAUNCH;

        // as the MCU should have reset, we should never get here.
        while(1);
    }
}