#include "core/config.h"
#include "drivers/extmem_flash/extmem_flash.h"

#include "core/gpio.h"
#include "core/main.h"
#include "core/config_pins.h"

 #if AT45DB321D_POWER_MANAGEMENT
 static void ExtMem_SetPinPowerOn();
 static void ExtMem_SetPinPowerOff();
 #endif
 #if AT45DB321D_RESET_MANAGEMENT                                         
 static void ExtMem_ResetPinAssert();
 static void ExtMem_ResetPinRelease();
 #endif
 #if AT45DB321D_HW_WR_PROTECT
 static void ExtMem_HwWrProtPinAssert(); 
 static void ExtMem_HwWrProtPinRelease();
 #endif

 static bool ExtMem_Write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size );  
 static bool ExtMem_Read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size );
 static bool ExtMem_RangeCheck_Read( flash_address_t address, flash_address_t size );  
 static bool ExtMem_RangeCheck_Write( flash_address_t address, flash_address_t size ); 
 static bool ExtMem_BankProtect( eExtMem_Bank_t bankId, bool protectStatus );
 static bool ExtMem_CheckBankProtect( eExtMem_Bank_t bankId, bool * pActualProtectStatus );
 static void ExtMem_Pins_Init();
 static void ExtMem_Pins_Deinit();
 static bool ExtMem_DeInit( bool bForce );
 static bool ExtMem_Init();

 volatile bool bExtMemStatus = false;
 
 #define EXTMEM_BANK0_SIZE       (0x100000)  // do not forget to change ACMMemory_Bank0
 #define EXTMEM_BANK1_SIZE       (0x100000)  // do not forget to change ACMMemory_Bank1
 
 static const ExtMem_Bank_Properties_t BanksProperties = 
 {
    .factoryBankSize = EXTMEM_BANK0_SIZE,
    .userBankSize = EXTMEM_BANK1_SIZE
 }; 
  
 const ExtMemPins_Handle_t ExtMemPinsHandle = 
 {
    #if AT45DB321D_POWER_MANAGEMENT
    .SetPinPowerOn      = ExtMem_SetPinPowerOn,
    .SetPinPowerOff     = ExtMem_SetPinPowerOff,
    #endif
    #if AT45DB321D_RESET_MANAGEMENT                                         
    .ResetPinAssert     = ExtMem_ResetPinAssert,
    .ResetPinRelease    = ExtMem_ResetPinRelease,
    #endif
    #if AT45DB321D_HW_WR_PROTECT
    .HwWrProtPinAssert  = ExtMem_HwWrProtPinAssert, 
    .HwWrProtPinRelease = ExtMem_HwWrProtPinRelease,
    #endif
 };
 
 const ExtMem_Handle_t ExtMemHandle = {

    .Init = ExtMem_Init,
    .Write = ExtMem_Write,
    .Read = ExtMem_Read,
    .RangeCheck_Read = ExtMem_RangeCheck_Read,
    .RangeCheck_Write = ExtMem_RangeCheck_Write,
    .BankProtect = ExtMem_BankProtect,
    .CheckBankProtect = ExtMem_CheckBankProtect,
    .DeInit = ExtMem_DeInit,
    .pFlashProperties = &FlashProperties,
    .pBanksProperties = &BanksProperties
  };

 // Memory Banking Template
 // Divides the whole chip memory into the common banks
 // This structure describes the sectors mask included into the bank
 typedef struct 
 {
     struct flash_sectors_st selectRegister;
 }
 sMemoryBankingTemplate_t;
 //
 // Current Model Memory Template: Bank 0 (Factory)
 const static sMemoryBankingTemplate_t ACMMemory_Bank0 = {

     .selectRegister = {
         .loprotect = 0x0001FFFE,
       //.loprotect = 0x000001FE,  // "First" 8MBit --- bug found 29.05.19, each bit represent only 512kbit, not 1Mbit.
         .hiprotect = 0x00000000,  
     }

 };

 // 32MBit flash AT45DB321D is installed;
 // But the application supposes that only first 8MBit is presents
 // So it adresses first 4MBit as a factory bank, and the second as 
 // a user bank. The rest part is useless.

 // Current Model Memory Template: Bank 1 (User)
 const static sMemoryBankingTemplate_t ACMMemory_Bank1 = {

     .selectRegister = {
         .loprotect = 0xFFFE0000,  // "Second" 8MBit
       //.loprotect = 0x0001FE00,  // "Second" 8MBit --- bug found 29.05.19, each bit represent only 512kbit, not 1Mbit.
         .hiprotect = 0x00000001,
       //.hiprotect = 0x00000000,                    --- bug found 29.05.19, each bit represent only 512kbit, not 1Mbit.
     }

 };

 #define pBankTemplate_Bank0 (&ACMMemory_Bank0)
 #define pBankTemplate_Bank1 (&ACMMemory_Bank1)

 #define pSPIHandle (&SPIHandle)

 // ExtMem_DeInit()
 // De-Initializes external memory driver.
 // Makes the chip fall asleep.
 // @bForce: if 'false' the function will detect if the chip 
 //   is busy and interrupt the operation with error.
 //   If 'true', the function ignores current chip state and makes it
 //   fall asleep instantly. 
 // Returns 'true' in success case, and 'false' otherwise.
 bool ExtMem_DeInit( bool bForce )
 {
   // Clear status
   bExtMemStatus = false;

   #if EXTMEM_HIBERNATE_ENABLED
   // Make flash chip fall asleep
   if( FLASH_ERROR( flash_finalize( true, bForce ) ) )
   #else
   // Deinitialize driver
   if( FLASH_ERROR( flash_finalize( false, bForce ) ) )
   #endif
   {
       return false;
   }

   // de-initialize SPI driver
   pSPIHandle->DeInit();

   // De-Initialize power- and reset- management pins
   ExtMem_Pins_Deinit();

   // set status and return
   return true;
 }

 // ExtMem_Init()
 // Initializes external memory driver.
 // Wakes the memory chip up if it is necessary.
 // Returns 'true' in success case, and 'false' otherwise.
 bool ExtMem_Init()
 { 
   // Clear status
   bExtMemStatus = false;

   if( ExtMemHandle.pFlashProperties->minAddress >= ExtMemHandle.pFlashProperties->maxAddress )  
       return false;

   if( ExtMemHandle.pFlashProperties->maxSectors == 0 || ExtMemHandle.pFlashProperties->sectorSize == 0 )  
       return false;

   // Check the Bank sizes:
   //  the banks can not share one sector, the sector can be owned by only one bank.
   if( ((ExtMemHandle.pBanksProperties->factoryBankSize % ExtMemHandle.pFlashProperties->sectorSize > 0 )?(ExtMemHandle.pFlashProperties->sectorSize):(0))
      +((ExtMemHandle.pBanksProperties->userBankSize % ExtMemHandle.pFlashProperties->sectorSize > 0 )?(ExtMemHandle.pFlashProperties->sectorSize):(0))
      >  ExtMemHandle.pFlashProperties->maxSectors * ExtMemHandle.pFlashProperties->sectorSize )
   {
       return false;
   }

   // Initialize power- and reset- management pins 
   ExtMem_Pins_Init();

   // initialize SPI driver
   pSPIHandle->Init();

   // Initialize flash driver
   if( FLASH_ERROR( flash_initialize() ) )
   {
       // de-initialize SPI driver
       pSPIHandle->DeInit();

       // flash driver initialization error
       return false;
   }

   // set status and return
   return (bExtMemStatus = true);
 }

 // Initialize ExtMem power- and reset- management GPIO-pins
 static void ExtMem_Pins_Init()
 {
    // Configure GPIO pins for AT45 IC : PB10=nWP (reset signal)
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // Configure as input to detect actual level:
    GPIO_InitStruct.Pin = CONFIG_PIN__EXTMEM__RST;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;  
    HAL_GPIO_Init(CONFIG_PORT__EXTMEM__RST, &GPIO_InitStruct);

    // read actual level
    GPIO_PinState xPreservedState = HAL_GPIO_ReadPin( CONFIG_PORT__EXTMEM__RST, CONFIG_PIN__EXTMEM__RST );

    // Configure as analog
    GPIO_InitStruct.Pin = CONFIG_PIN__EXTMEM__RST;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;  
    HAL_GPIO_Init(CONFIG_PORT__EXTMEM__RST, &GPIO_InitStruct);

    // prepare pin state:
    HAL_GPIO_WritePin( CONFIG_PORT__EXTMEM__RST, CONFIG_PIN__EXTMEM__RST, xPreservedState );

    // Configure as output:
    GPIO_InitStruct.Pin = CONFIG_PIN__EXTMEM__RST;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;        
    HAL_GPIO_Init(CONFIG_PORT__EXTMEM__RST, &GPIO_InitStruct);
 }

 // De-Initialize ExtMem power- and reset- management GPIO-pins
 static void ExtMem_Pins_Deinit()
 {
    // Configure GPIO pins for AT45 IC : PB10=nWP (reset signal)
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = CONFIG_PIN__EXTMEM__RST;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;    
    HAL_GPIO_Init(CONFIG_PORT__EXTMEM__RST, &GPIO_InitStruct);
 }

 typedef struct
 {
     // Actual protection bits for all sectors:
     __FLASH_DWORD actual_protection_mask_lo; 
     __FLASH_DWORD actual_protection_mask_hi; 
     
     // Desired protection bits for selected sectors:
     __FLASH_DWORD desired_protection_mask_lo;
     __FLASH_DWORD desired_protection_mask_hi;

     bool globalProtectionStatus;
     bool individualProtectionStatus;
 }
 sProtectionEntry_t;

 // Queries actual protection information from the flash-memory and fills the [sProtectionEntry_t] structure.
 // If required, fills the protection register that can be used to set the protection information
 // @bankId = memory bank identifier;
 // @desiredProtectStatus = desired protecion state
 // @protEntry = pointer to the [sProtectionEntry_t] structure to fill, mandatory.
 // @setProtectRegister = pointer to the set-protection register to fill, optional, it then can be used by flash_protect_ex()
 // Returns the operaion status, true if succeded, false otherwise.
 // The actual protection state will be returned in @protEntry.
 static bool ExtMem_Internal_FillProtectionEntry( eExtMem_Bank_t bankId, bool desiredProtectStatus, sProtectionEntry_t * pProtEntry, flash_api_protect_t * setProtectRegister )
 {
     const sMemoryBankingTemplate_t * pBankTemplate = NULL;
     flash_api_getprotect_t getProtectRegister;

     if( NULL != pProtEntry )
     {
         // This function uses individual sector protecton
         // 
         switch( bankId )
         {
             case extmem_bank_factory:
             {    
                  pBankTemplate = pBankTemplate_Bank0;
             }
             break;
             case extmem_bank_user:
             {    
                  pBankTemplate = pBankTemplate_Bank1;
             }
             break;
         }

         if( NULL != pBankTemplate )
         {
            // Retrieve currect sector protection register:
            if( FLASH_SUCCESS( flash_getprotect( &getProtectRegister ) ) )
            {   
                // Get actual protection bits for all sectors:
                pProtEntry->actual_protection_mask_lo = getProtectRegister.sectors.loprotect;
                pProtEntry->actual_protection_mask_hi = getProtectRegister.sectors.hiprotect;

                // Initialize desired protection bits for selected sectors:
                pProtEntry->desired_protection_mask_lo = ((desiredProtectStatus)?0xFFFFFFFF:0x00000000);
                pProtEntry->desired_protection_mask_hi = ((desiredProtectStatus)?0xFFFFFFFF:0x00000000);
                
                // Filter only selected sectors (specified by selection register on memory bank template)
                pProtEntry->actual_protection_mask_lo &= pBankTemplate->selectRegister.loprotect;
                pProtEntry->actual_protection_mask_hi &= pBankTemplate->selectRegister.hiprotect;
                pProtEntry->desired_protection_mask_lo &= pBankTemplate->selectRegister.loprotect;
                pProtEntry->desired_protection_mask_hi &= pBankTemplate->selectRegister.hiprotect;

                // Compare actual and desired sector protection status:
                if(  pProtEntry->actual_protection_mask_lo != pProtEntry->desired_protection_mask_lo
                     ||
                     pProtEntry->actual_protection_mask_hi != pProtEntry->desired_protection_mask_hi )                 
                {
                     // PROTECTION STATUS: SOME SECTORS HAVE INCORRECT PROTECTION FLAGS

                     pProtEntry->individualProtectionStatus = false;

                     if( NULL != setProtectRegister )
                     {
                         // Do not change COMMON PROTECTION FLAG:
                         setProtectRegister->protection_modify = flash_sector_protect_nomodify;

                         // Mark selected sectors to be protected:
                         setProtectRegister->protection_enabled = flash_sector_protect_pick;

                         // Copy sectors selection mask:
                         setProtectRegister->sectors.loprotect = pBankTemplate->selectRegister.loprotect;
                         setProtectRegister->sectors.hiprotect = pBankTemplate->selectRegister.hiprotect;
                     }                      
                }
                else
                {
                     // PROTECTION STATUS: ALL SECTORS HAVE CORRECT PROTECTION FLAGS
                     pProtEntry->individualProtectionStatus = true;
                }

                // QUERY GLOBAL PROTECTION FLAG
                switch( flash_getprotect( NULL ) )
                {
                    case FLERR_PROTECT_ENABLED:
                    pProtEntry->globalProtectionStatus = true;
                    return true;
                
                    case FLERR_PROTECT_DISABLED:
                    pProtEntry->globalProtectionStatus = false;
                    return true;
                }  
            }
         }
     }

     return false;
 } 

 // ExtMem_BankProtect()
 // Implements external memory protection by Memory Bank Identifier 
 // @bankId = memory bank identifier;
 // @desiredProtectStatus - protection status (true=protected, false=unprotected)
 // Returns the result of operation
 static bool ExtMem_BankProtect( eExtMem_Bank_t bankId, bool desiredProtectStatus )
 {   
#warning 31/05/19 Была изменена эта функция, поверхностные тесты прошла. Требуется тщательная проверка
#warning 02/07/19 error->warning, будем проверять
     sProtectionEntry_t     protEntry_Bank0;       // protection information for Bank0     
     sProtectionEntry_t     protEntry_Bank1;       // protection information for Bank1
     
     // --------------------
     if( extmem_bank_factory != bankId
         &&
         extmem_bank_user != bankId )
     {
         return false; // error
     }
     // --------------------
     // Query actual protection information for both banks
     //  Argument ExtMem_Internal_FillProtectionEntry(... @desiredProtectStatus = true ...):
     //   that ensures that all sectors are protected with individual protection flags. 
     if( ExtMem_Internal_FillProtectionEntry( extmem_bank_factory, true, &protEntry_Bank0, NULL )
         &&
         ExtMem_Internal_FillProtectionEntry( extmem_bank_user, true, &protEntry_Bank1, NULL ) )
     {         
         // Actual bank's protection state is depends on the global protection state and the individual protection state for bank
         bool stateBank0 = ( protEntry_Bank0.individualProtectionStatus && protEntry_Bank0.globalProtectionStatus );
         bool stateBank1 = ( protEntry_Bank1.individualProtectionStatus && protEntry_Bank1.globalProtectionStatus );
         
         // Check if current bank's protection state matchs to desired state
         if(
           (( extmem_bank_factory == bankId ) && ( desiredProtectStatus == stateBank0 ))
           ||
           (( extmem_bank_user    == bankId ) && ( desiredProtectStatus == stateBank1 ))
         )
         {
             // All bank's protection statuses are correct, no operations are required
             return true; // success
         }
         else // Only if current state and the desired state mismatch:
         {
             // The global protection state must be set if at least one bank must be protected.
             bool requiredGlobalState = (( extmem_bank_factory == bankId )?desiredProtectStatus:stateBank0)
                                        ||
                                        (( extmem_bank_user    == bankId )?desiredProtectStatus:stateBank1);

             if( requiredGlobalState )
             {
                 // If global protection state will be set

                 // Ensure that all banks have correct individual sector protection flags:
                 // Remember: @requiredGlobalState is true 
                 if( protEntry_Bank0.individualProtectionStatus != (( extmem_bank_factory == bankId )?desiredProtectStatus:stateBank0) )
                 {
                     // @individualProtectionStatus does not match to the desired state
                      
                     flash_api_protect_t    setProtectRegister;   // set-protection register

                     // new bank protection status
                     stateBank0 = !protEntry_Bank0.individualProtectionStatus;

                     // Query actual protection information for both banks
                     // Specify argument ExtMem_Internal_FillProtectionEntry(... @desiredProtectStatus ...) to
                     // the inverted @individualProtectionStatus state, because current @individualProtectionStatus value is wrong                     
                     if( !ExtMem_Internal_FillProtectionEntry( extmem_bank_factory, stateBank0, &protEntry_Bank0, &setProtectRegister ) )
                     {
                         // query error
                         return false;
                     }

                     // Execute operation: modify only individual sectors protection marks to the desired state                 
                     if( FLASH_ERROR( flash_protect_ex( &setProtectRegister,
                             (( stateBank0 )?(flash_sector_protected):(flash_sector_unprotected))
                       ) ) )
                     {
                         // Error: can not modify individual sector protection flags
                         return false;
                     }
                 }

                 if( protEntry_Bank1.individualProtectionStatus != (( extmem_bank_user    == bankId )?desiredProtectStatus:stateBank1) )
                 {
                     // @individualProtectionStatus does not match to the desired state
                      
                     flash_api_protect_t    setProtectRegister;   // set-protection register

                     // new bank protection status
                     stateBank1 = !protEntry_Bank1.individualProtectionStatus;

                     // Query actual protection information for both banks
                     // Specify argument ExtMem_Internal_FillProtectionEntry(... @desiredProtectStatus ...) to
                     // the inverted @individualProtectionStatus state, because current @individualProtectionStatus value is wrong                     
                     if( !ExtMem_Internal_FillProtectionEntry( extmem_bank_user, stateBank1, &protEntry_Bank1, &setProtectRegister ) )
                     {
                         // query error
                         return false;
                     }

                     // Execute operation: modify only individual sectors protection marks to the desired state                 
                     if( FLASH_ERROR( flash_protect_ex( &setProtectRegister,
                             (( stateBank1 )?(flash_sector_protected):(flash_sector_unprotected))
                       ) ) )
                     {
                         // Error: can not modify individual sector protection flags
                         return false;
                     }
                 }

                 // Modify global write-protection flag: protected
                 return FLASH_SUCCESS( flash_protect( NULL ) );
             }
             else
             {
                 // If global protection state will be reset: all banks must be unprotected
                 // Modify global write-protection flag: unprotected
                 return FLASH_SUCCESS( flash_unprotect( NULL ) );
             }
         }
     }

     return false; // error
  }

 // ExtMem_CheckBankProtect()
 // Implements external memory protection by Memory Bank Identifier 
 // @bankId = memory bank identifier;
 // @pActualProtectStatus - pointer to the boolean variable to receive actual protection status
 // Returns the result of operation
 static bool ExtMem_CheckBankProtect( eExtMem_Bank_t bankId, bool * pActualProtectStatus )
 {     
     sProtectionEntry_t protEntry;
     
     if( NULL != pActualProtectStatus )
     {
         // Query current protection information
         if( ExtMem_Internal_FillProtectionEntry( bankId, true, &protEntry, NULL ) )
         {    
             *pActualProtectStatus = (protEntry.globalProtectionStatus) && (protEntry.individualProtectionStatus);

             return true; // success, no operations are required
         }
     }
     return false; // error
  }
 /*
 // ExtMem_BankProtect()
 // Implements external memory protection by Memory Bank Identifier 
 // @bankId = memory bank identifier;
 // @protectStatus - protection status (true=protected, false=unprotected)
 // Returns the result of operation
 static bool ExtMem_BankProtect0( eExtMem_Bank_t bankId, bool protectStatus )
 {
     flash_api_getprotect_t getProtectRegister;
     flash_api_protect_t    setProtectRegister;
     flash_api_protect_bits mode = (( protectStatus )?(flash_sector_protected):(flash_sector_unprotected));
     const sMemoryBankingTemplate_t * pBankTemplate = NULL;

     // This function uses individual sector protecton
     // 
     switch( bankId )
     {
         case extmem_bank_factory:
         {    
              pBankTemplate = pBankTemplate_Bank0;
         }
         break;
         case extmem_bank_user:
         {    
              pBankTemplate = pBankTemplate_Bank1;
         }
         break;
     }

     if( NULL != pBankTemplate )
     {
        // Retrieve currect sector protection register:
        if( FLASH_SUCCESS( flash_getprotect( &getProtectRegister ) ) )
        {            
            // Get actual protection bits for all sectors:
            __FLASH_DWORD actual_protection_mask_lo = getProtectRegister.sectors.loprotect;
            __FLASH_DWORD actual_protection_mask_hi = getProtectRegister.sectors.hiprotect;

            // Initialize desired protection bits for selected sectors:
            __FLASH_DWORD desired_protection_mask_lo = ((protectStatus)?0xFFFFFFFF:0x00000000);
            __FLASH_DWORD desired_protection_mask_hi = ((protectStatus)?0xFFFFFFFF:0x00000000);
            
            // Filter only selected sectors (specified by selection register on memory bank template)
             actual_protection_mask_lo &= pBankTemplate->selectRegister.loprotect;
             actual_protection_mask_hi &= pBankTemplate->selectRegister.hiprotect;
            desired_protection_mask_lo &= pBankTemplate->selectRegister.loprotect;
            desired_protection_mask_hi &= pBankTemplate->selectRegister.hiprotect;

            // Compare actual and desired sector protection status:
            if(  actual_protection_mask_lo != desired_protection_mask_lo
                 ||
                 actual_protection_mask_hi != desired_protection_mask_hi )                 
            {
                 
                 // Do not change COMMON PROTECTION FLAG:
                 setProtectRegister.protection_modify = flash_sector_protect_nomodify;

                 // Mark selected sectors to be protected:
                 setProtectRegister.protection_enabled = flash_sector_protect_pick;

                 // Copy sectors selection mask:
                 setProtectRegister.sectors.loprotect = pBankTemplate->selectRegister.loprotect;
                 setProtectRegister.sectors.hiprotect = pBankTemplate->selectRegister.hiprotect;

                 // Execute operation: modify only individual sectors protection marks to the desired state                 
                 if( FLASH_SUCCESS( flash_protect_ex( &setProtectRegister, mode ) ) )
                 {
                     // Modify global write-protection flag: protected (for only selected sectors)
                     return FLASH_SUCCESS( flash_protect( NULL ) );
                 }                 
            }
            else
            {
                 // Modify global write-protection flag: protected (for only selected sectors)
                 return FLASH_SUCCESS( flash_protect( NULL ) );
            }
        }
     }

     return false;
  }
 */
 #if AT45DB321D_POWER_MANAGEMENT
 // Implement ExtMem power-management: set power enable pin
 static void ExtMem_SetPinPowerOn()
 {
    #error Not-implemented
 }

 // Implement ExtMem power-management: set power disable pin
 static void ExtMem_SetPinPowerOff()
 {
    #error Not-implemented
 }
 #endif

 #if AT45DB321D_RESET_MANAGEMENT
 // Implement ExtMem reset-management: set reset enable pin
 static void ExtMem_ResetPinAssert()
 {  
    HAL_GPIO_WritePin( CONFIG_PORT__EXTMEM__RST, CONFIG_PIN__EXTMEM__RST, GPIO_PIN_RESET );
 }

 // Implement ExtMem reset-management: set reset disable pin
 static void ExtMem_ResetPinRelease()
 {
    HAL_GPIO_WritePin( CONFIG_PORT__EXTMEM__RST, CONFIG_PIN__EXTMEM__RST, GPIO_PIN_SET );
 }
 #endif

 #if AT45DB321D_HW_WR_PROTECT
 // Implement ExtMem hardware-write-protection: enable write protection
 static void ExtMem_HwWrProtPinAssert()
 {
    #error Not-implemented
 }

 // Implement ExtMem hardware-write-protection: disable write protection
 static void ExtMem_HwWrProtPinRelease()
 {
    #error Not-implemented
 }
 #endif
 
 #if CONFIG_EXTMEM_EMULATEWRITE
 static bool ExtMem_Write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
     return true;
 }
 #else
 static bool ExtMem_Write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
     extern volatile bool bExtMemStatus;
     
     return bExtMemStatus && FLASH_SUCCESS( flash_write( ExtMemHandle.pFlashProperties->minAddress + address, pBuffer, size, fwm_safewrite ) );
 }
 #endif
 #if CONFIG_EXTMEM_EMULATEREAD
 static bool ExtMem_Read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
     memset( pBuffer, 0xDE, size );
     
     return true;
 }
 #else
 static bool ExtMem_Read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
     extern volatile bool bExtMemStatus;

     return bExtMemStatus && FLASH_SUCCESS( flash_read( ExtMemHandle.pFlashProperties->minAddress + address, pBuffer, size ) );
 }
 #endif
 
 static bool ExtMem_RangeCheck_Read( flash_address_t address, flash_address_t size )
 {
     if( size <= (ExtMemHandle.pFlashProperties->maxAddress - ExtMemHandle.pFlashProperties->minAddress) )
     {
         if( address < ExtMemHandle.pFlashProperties->maxAddress - ExtMemHandle.pFlashProperties->minAddress - size )
         {
             return true;
         }
     }

     return false;
 }

 static bool ExtMem_RangeCheck_Write( flash_address_t address, flash_address_t size )
 {
     if( size <= (ExtMemHandle.pFlashProperties->maxAddress - ExtMemHandle.pFlashProperties->minAddress) )
     {
         if( address < ExtMemHandle.pFlashProperties->maxAddress - ExtMemHandle.pFlashProperties->minAddress - size )
         {
             return true;
         }
     }

     return false;
 }

