/*
 * 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"

#include "flash.h"


/*
 * loads flash timing values.
 * ensure flash is unlocked before calling this function,
 * otherwise the writes will silently fail.
 */
void flash_set_timing(uint32_t *timing_table)
{
    // load flash write timings from provided timing table
    // these are listed in the datasheet, but are also in ROM on the MCU

    // note: if you decide to operate at a different frequency other than
    //       a supported frequency for which timing tables do not exist
    //       in ROM, you'll have to figure out what these values actually mean,
    //       as the meaning of the values isn't documented in DS or RM

    // note: the PRETPE and SMERTPE values seem to differ in MCU ROM from
    //       the values listed in the reference manual. this is true of
    //       all versions I have access to - English v1.0 and Chinese v1.2.
    //       for example, @8MHz, RM says 0x5dc0 for PERTPE but ROM holds 0x6b60

    // note: the datasheet doesn't say it anywhere, but if flash is not unlocked,
    //       then flash timing values aren't writeable

    // apply flash table timing values
    FLASH->TS0  = (timing_table[0] >>  0) &  0xff;
    FLASH->TS3  = (timing_table[0] >>  8) &  0xff;
    FLASH->TS1  = (timing_table[0] >> 16) & 0x1ff;
    
    FLASH->TS2P = (timing_table[1] >>  0) &  0xff;
    FLASH->TPS3 = (timing_table[1] >> 16) & 0x7ff;

    FLASH->PERTPE  = timing_table[2];   // & 0x1ffff;
    FLASH->SMERTPE = timing_table[3];   // & 0x1ffff;

    FLASH->PRGTPE = (timing_table[4] >>  0) & 0xffff;
    FLASH->PRETPE = (timing_table[4] >> 16) &  0x7ff;
}

/*
 * initializes flash timing values, and configures OPTR as necessary
 * if OPTR needs configured, the MCU will reset
 * call this function before interrupts are enabled.
 */
__attribute__ ((long_call, section(".ramfunc"))) void flash_init()
{
    // unlock flash (necessary for writing timing registers
    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;
    while (FLASH->CR & FLASH_CR_LOCK);      // still locked? then stall forever
        
    // configure flash timings
    flash_set_timing(FLASH_HSI_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 (already done above)
        // and then unlock OPTLOCK
        FLASH->OPTKEYR = FLASH_OPTKEY1;
        FLASH->OPTKEYR = FLASH_OPTKEY2;
        while (FLASH->CR & FLASH_CR_OPTLOCK);   // 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);
    }

    // re-lock flash when done
    FLASH->CR = FLASH_CR_LOCK;
}