Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

FreescaleIAP/FreescaleIAP.cpp

Committer:
mjr
Date:
2017-02-03
Revision:
76:7f5912b6340e
Parent:
60:f38da020aa13
Child:
77:0b96f6867312

File content as of revision 76:7f5912b6340e:

// FreescaleIAP, private version
//
// This is a heavily modified version of Erik Olieman's FreescaleIAP, a
// flash memory writer for Freescale boards.  This version is adapted to
// the special needs of the KL25Z.
//
// Simplifications:
//
// Unlike EO's original version, this version combines erase and write
// into a single opreation, so the caller can simply give us a buffer
// and a location, and we'll write it, including the erase prep.  We
// don't need to be able to separate the operations, so the combined
// interface is simpler at the API level and also lets us do all of the
// interrupt masking in one place (see below).
//
// Stability improvements:
//
// The KL25Z has severe restrictions on flash writing that make it very
// delicate.  The key restriction is that the flash controller (FTFA) 
// doesn't allow any read operations while a sector erase is in progress.  
// The reason this complicates things is that all program code is stored
// in flash by default.  This means that every instruction fetch is a 
// flash read operation.  The FTFA's response to a read while an erase is
// in progress is to fail the read and return arbitrary data.  When the
// read is actually an instruction fetch, this results in the CPU trying
// to execute random garbage, which virtually always crashes the program
// and freezes the CPU.  Making this even more complicated, the erase
// operation runs a whole sector at a time, which takes a very long time
// in CPU terms, on the order of milliseconds.  Even if the code that
// initiates the erase is very careful to loop without branching to any
// flash locations, it's still a long time to go without an interrupt
// occurring
//
// We use two strategies to avoid flash fetches while we're working.
// First, the code that performs all of the FTFA operations is written
// in assembly, in a module AREA marked READWRITE.  This forces the
// linker to put the code in RAM.  The code could otherwise just have
// well been written in C++, but as far as I know there's no way to tell
// the mbed C++ compiler to put code in RAM.  Since the FTFA code is all
// in RAM, it doesn't trigger any flash fetches as it executes.  Second,
// we explicitly disable all of the peripheral interrupts that we use
// anywhere in the program (USB, all the timers, etc) via the NVIC.  It
// isn't sufficient to disable interrupts with __disable_irq() or the
// equivalent assembly instruction CPSID I; we have to turn them off at
// the NVIC level.  My understanding of ARM system architecture isn't
// detailed enough to know why this is required, but experimentally it
// definitely seems to be needed.

#include "FreescaleIAP.h"
 
//#define IAPDEBUG

// assembly interface
extern "C" {
    void iapEraseSector(FTFA_Type *ftfa, uint32_t address);
    void iapProgramBlock(FTFA_Type *ftfa, uint32_t address, const void *src, uint32_t length);
}


 
enum FCMD {
    Read1s = 0x01,
    ProgramCheck = 0x02,
    ReadResource = 0x03,
    ProgramLongword = 0x06,
    EraseSector = 0x09,
    Read1sBlock = 0x40,
    ReadOnce = 0x41,
    ProgramOnce = 0x43,
    EraseAll = 0x44,
    VerifyBackdoor = 0x45
};


/* Check if an error occured 
   Returns error code or Success*/
static IAPCode check_error(void) 
{
    if (FTFA->FSTAT & FTFA_FSTAT_FPVIOL_MASK) {
        #ifdef IAPDEBUG
        printf("IAP: Protection violation\r\n");
        #endif
        return ProtectionError;
    }
    if (FTFA->FSTAT & FTFA_FSTAT_ACCERR_MASK) {
        #ifdef IAPDEBUG
        printf("IAP: Flash access error\r\n");
        #endif
        return AccessError;
    }
    if (FTFA->FSTAT & FTFA_FSTAT_RDCOLERR_MASK) {
        #ifdef IAPDEBUG
        printf("IAP: Collision error\r\n");
        #endif
        return CollisionError;
    }
    if (FTFA->FSTAT & FTFA_FSTAT_MGSTAT0_MASK) {
        #ifdef IAPDEBUG
        printf("IAP: Runtime error\r\n");
        #endif
        return RuntimeError;
    }
    #ifdef IAPDEBUG
    printf("IAP: No error reported\r\n");
    #endif
    return Success;
}
 
IAPCode FreescaleIAP::program_flash(int address, const void *src, unsigned int length) 
{    
    #ifdef IAPDEBUG
    printf("IAP: Programming flash at %x with length %d\r\n", address, length);
    #endif
    
    // Disable peripheral IRQs.  Empirically, this seems to be vital to 
    // getting the writing process (especially the erase step) to work 
    // reliably.  Even with the CPU interrupt mask off (CPSID I in the
    // assembly), it appears that peripheral interrupts will 
    NVIC_DisableIRQ(PIT_IRQn);
    NVIC_DisableIRQ(I2C0_IRQn);
    NVIC_DisableIRQ(I2C1_IRQn);
    NVIC_DisableIRQ(PORTA_IRQn);
    NVIC_DisableIRQ(PORTD_IRQn);
    NVIC_DisableIRQ(USB0_IRQn);
    NVIC_DisableIRQ(TPM0_IRQn);
    NVIC_DisableIRQ(TPM1_IRQn);
    NVIC_DisableIRQ(TPM2_IRQn);
    NVIC_DisableIRQ(RTC_IRQn);
    NVIC_DisableIRQ(RTC_Seconds_IRQn);
    NVIC_DisableIRQ(LPTimer_IRQn);
            
    // presume success
    IAPCode status = Success;

    // I'm not 100% convinced this is 100% reliable yet.  So let's show
    // some diagnostic lights while we're working.  If anyone sees any
    // freezes, the lights that are left on at the freeze will tell us
    // which step is crashing.
    extern void diagLED(int,int,int);
    
    // Erase the sector(s) covered by the write.  Before writing, we must
    // erase each sector that we're going to touch on the write.
    for (uint32_t ofs = 0 ; ofs < length ; ofs += SECTOR_SIZE)
    {
        // Show RED on the first sector, GREEN on second, BLUE on third.  Each
        // sector is 1K, so I don't think we'll need more than 3 for the 
        // foreseeable future.  (RAM on the KL25Z is so tight that it will
        // probably stop us from adding enough features to require more
        // configuration variables than 3K worth.)
        diagLED(ofs/SECTOR_SIZE == 0, ofs/SECTOR_SIZE == 1, ofs/SECTOR_SIZE == 2);
        
        // erase the sector
        iapEraseSector(FTFA, address + ofs);
    }
        
    // If the erase was successful, write the data.
    if ((status = check_error()) == Success)
    {
        // show cyan while the write is in progress
        diagLED(0, 1, 1);

        // do the write
        iapProgramBlock(FTFA, address, src, length);
        
        // show white when the write completes
        diagLED(1, 1, 1);
        
        // check again for errors
        status = check_error();
    }
    
    // restore peripheral IRQs
    NVIC_EnableIRQ(PIT_IRQn);
    NVIC_EnableIRQ(I2C0_IRQn);
    NVIC_EnableIRQ(I2C1_IRQn);
    NVIC_EnableIRQ(PORTA_IRQn);
    NVIC_EnableIRQ(PORTD_IRQn);
    NVIC_EnableIRQ(USB0_IRQn);
    NVIC_EnableIRQ(TPM0_IRQn);
    NVIC_EnableIRQ(TPM1_IRQn);
    NVIC_EnableIRQ(TPM2_IRQn);
    NVIC_EnableIRQ(RTC_IRQn);
    NVIC_EnableIRQ(RTC_Seconds_IRQn);
    NVIC_EnableIRQ(LPTimer_IRQn);

    // return the result
    return status;
}
 
uint32_t FreescaleIAP::flash_size(void) 
{
    uint32_t retval = (SIM->FCFG2 & 0x7F000000u) >> (24-13);
    if (SIM->FCFG2 & (1<<23))           //Possible second flash bank
        retval += (SIM->FCFG2 & 0x007F0000u) >> (16-13);
    return retval;
}
 
/* Check if no flash boundary is violated
   Returns true on violation */
bool check_boundary(int address, unsigned int length) 
{
    int temp = (address+length - 1) / SECTOR_SIZE;
    address /= SECTOR_SIZE;
    bool retval = (address != temp);
    #ifdef IAPDEBUG
    if (retval)
        printf("IAP: Boundary violation\r\n");
    #endif
    return retval;
}
 
/* Check if address is correctly aligned
   Returns true on violation */
bool check_align(int address) 
{
    bool retval = address & 0x03;
    #ifdef IAPDEBUG
    if (retval)
        printf("IAP: Alignment violation\r\n");
    #endif
    return retval;
}