#define EXTMEM_FLASH_C
#include "core/config.h"
#include "drivers/flash/base/extmem_flash.h"

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

 #define pSPIHandle (&SPIHandle)

 #if AT45DBXXX_POWER_MANAGEMENT || W25QXXX_POWER_MANAGEMENT
 static void ExtMem_SetPinPowerOn();
 static void ExtMem_SetPinPowerOff();
 #endif
 #if AT45DBXXX_RESET_MANAGEMENT || W25QXXX_RESET_MANAGEMENT
 static void ExtMem_ResetPinAssert();
 static void ExtMem_ResetPinRelease();
 #endif
 #if AT45DBXXX_HW_WR_PROTECT || W25QXXX_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();

 // -----------------------------------------------------------------------------
 // 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;

 static void ExtMem_Internal_FillBankingTemplate( const ExtMem_Bank_Properties_t * pBankProperties, sMemoryBankingTemplate_t * pFactoryProtectionTemplate );
 // -----------------------------------------------------------------------------

 volatile bool bExtMemStatus = false;

 typedef struct 
 {
    const flash_api_descriptor_t * api;
 }
 ExtMemDriver_t;   

 // Supported flash-drivers
 static const ExtMemDriver_t flash_drivers[] = 
 {   
   { &W25Q16JV_API   }   // W25Q driver
 };
 static const ExtMemDriver_t * flashDriver = NULL; // active driver pointer

 #define EXTMEM_BANK0_SIZE       (0x0E0000)
 #define EXTMEM_BANK1_SIZE       (0x100000)
 
 // Dynamically filled Bank properties
 static ExtMem_Banks_Properties_t ActualBankProperties =
 {
    .factoryBank = { .bankSize = 0, .bankAddress = 0 },
    .userBank    = { .bankSize = 0, .bankAddress = 0 },
 };

 #if AT45DBXXX_POWER_MANAGEMENT || AT45DBXXX_RESET_MANAGEMENT || AT45DBXXX_HW_WR_PROTECT
 const ExtMemPins_Handle_AT45_t ExtMemPinsHandle_AT45 =
 {
    #if AT45DBXXX_POWER_MANAGEMENT
    .SetPinPowerOn      = ExtMem_SetPinPowerOn,
    .SetPinPowerOff     = ExtMem_SetPinPowerOff,
    #endif
    #if AT45DBXXX_RESET_MANAGEMENT
    .ResetPinAssert     = ExtMem_ResetPinAssert,
    .ResetPinRelease    = ExtMem_ResetPinRelease,
    #endif
    #if AT45DBXXX_HW_WR_PROTECT
    .HwWrProtPinAssert  = ExtMem_HwWrProtPinAssert,
    .HwWrProtPinRelease = ExtMem_HwWrProtPinRelease,
    #endif
 };
 #endif

 #if W25QXXX_POWER_MANAGEMENT || W25QXXX_RESET_MANAGEMENT || W25QXXX_HW_WR_PROTECT
 const ExtMemPins_Handle_W25Q_t ExtMemPinsHandle_W25Q =
 {
    #if W25QXXX_POWER_MANAGEMENT
    .SetPinPowerOn      = ExtMem_SetPinPowerOn,
    .SetPinPowerOff     = ExtMem_SetPinPowerOff,
    #endif
    #if W25QXXX_RESET_MANAGEMENT
    .ResetPinAssert     = ExtMem_ResetPinAssert,
    .ResetPinRelease    = ExtMem_ResetPinRelease,
    #endif
    #if W25QXXX_HW_WR_PROTECT
    .HwWrProtPinAssert  = ExtMem_HwWrProtPinAssert,
    .HwWrProtPinRelease = ExtMem_HwWrProtPinRelease,
    #endif
 };
 #endif

 static const char Malfunction_FlashProperties_Description[] = "";

 static const flash_properties_t Malfunction_FlashProperties = 
 {
    .minAddress = 0,
    .maxAddress = 0,
    .maxSectors = 0,
    .sectorSize = 0,
    .pChipDescription = Malfunction_FlashProperties_Description,
 };

 static const ExtMem_Banks_Properties_t Malfunction_BanksProperties =
 {
    .factoryBankSize = 0,
    .userBankSize = 0,
    .factoryBankAddress = 0,
    .userBankAddress = 0,
 };

 // Function stub
 static bool Malfunction_ExtMem_Write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
    return false;
 }

 // Function stub
 static bool Malfunction_ExtMem_Read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
    return false;
 }

 // Function stub
 static bool Malfunction_ExtMem_RangeCheck_Read( flash_address_t address, flash_address_t size )
 {
    return false;
 }

 // Function stub
 static bool Malfunction_ExtMem_RangeCheck_Write( flash_address_t address, flash_address_t size )
 {
    return false;
 }

 // Function stub
 static bool Malfunction_ExtMem_BankProtect( eExtMem_Bank_t bankId, bool protectStatus )
 {
    return false;
 }

 // Function stub
 static bool Malfunction_ExtMem_CheckBankProtect( eExtMem_Bank_t bankId, bool * pActualProtectStatus )
 {
    return false;
 }

 // Exporting External Memory descriptor
 ExtMem_Handle_t ExtMemHandle = {

    .Init = ExtMem_Init,                                      // Always set to actual value
    .Write = Malfunction_ExtMem_Write,                        // will be set after Init()
    .Read = Malfunction_ExtMem_Read,                          // will be set after Init()
    .RangeCheck_Read = Malfunction_ExtMem_RangeCheck_Read,    // will be set after Init()
    .RangeCheck_Write = Malfunction_ExtMem_RangeCheck_Write,  // will be set after Init()
    .BankProtect = Malfunction_ExtMem_BankProtect,            // will be set after Init()
    .CheckBankProtect = Malfunction_ExtMem_CheckBankProtect,  // will be set after Init()
    .DeInit = ExtMem_DeInit,                                  // Always set to actual value
    .pFlashProperties = &Malfunction_FlashProperties,         // will be set after Init()
    .pBanksProperties = &Malfunction_BanksProperties          // will be set after Init()
  };


// // Current Model Memory Template: Bank 0 (Factory)
// // [WRONG]
// #warning Remove this line as soon as the following entity has been validated
// const static sMemoryBankingTemplate_t ACMMemory_Bank0 = {
//
//     .selectRegister = {
//         .loprotect = 0x000000FE,  // First 4MBit (actually 7MBit, the bigger the better)
//                                   // First Sector 0 is skipped, and Sectors 1..7 is used as Bank0.
//                                   // Each sector = 128KBytes
//         .hiprotect = 0x00000000,
//     }
//
// };
//
// // 16MBit flash AT45DB161E is installed;
// // But the application supposes that only 8MBit (4+4Mbits) 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)
// // [WRONG]
// #warning Remove this line as soon as the following entity has been validated
// const static sMemoryBankingTemplate_t ACMMemory_Bank1 = {
//
//     .selectRegister = {
//         .loprotect = 0x0000FF00,  // Second 4MBit (actually 8MBit, the bigger the better)
//                                   // Sectors 8..15 is used as Bank1.
//         .hiprotect = 0x00000000,
//     }
//
// };

 // @ACMMemory_BankX: Dynamically filled template for sector protection operation
 static sMemoryBankingTemplate_t ACMMemory_BankX;  

 // 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;

   // Check if the driver is loaded
   if( NULL != flashDriver )
   {    
       #if EXTMEM_HIBERNATE_ENABLED
       // Make flash chip fall asleep
       if( FLASH_ERROR( flashDriver->api->routines.flashFinalize( true, bForce ) ) )
       #else
       // Deinitialize driver
       if( FLASH_ERROR( flashDriver->api->routines.flashFinalize( false, bForce ) ) )
       #endif
       {
           return false;
       }
   }

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

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

   // clear routines and data
   ExtMemHandle.Write = Malfunction_ExtMem_Write;
   ExtMemHandle.Read = Malfunction_ExtMem_Read;
   ExtMemHandle.RangeCheck_Read = Malfunction_ExtMem_RangeCheck_Read;
   ExtMemHandle.RangeCheck_Write = Malfunction_ExtMem_RangeCheck_Write;
   ExtMemHandle.BankProtect = Malfunction_ExtMem_BankProtect;
   ExtMemHandle.CheckBankProtect = Malfunction_ExtMem_CheckBankProtect;
   ExtMemHandle.pFlashProperties = &Malfunction_FlashProperties;
   ExtMemHandle.pBanksProperties = &Malfunction_BanksProperties;

   // set status and return
   return true;
 }

 // ExtMem_DriverLoad
 // Detects the chip on the bus and returns the appropriate driver to control it
 static const ExtMemDriver_t * ExtMem_DriverLoad()
 {   
    for( size_t flash_driver_active = 0; flash_driver_active < sizeof(flash_drivers)/sizeof(flash_drivers[0]); ++flash_driver_active )
    {
         if( FLASH_SUCCESS(flash_drivers[ flash_driver_active ].api->routines.flashInitialize( true ) ) )
         {
             return &flash_drivers[ flash_driver_active ];             
         }
    }

    return NULL; // driver not found
 }

 // 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;

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

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

   // Detect chip and load driver
   flashDriver = ExtMem_DriverLoad();

   // Check if the driver is loaded
   if( NULL == flashDriver )
   {
       // deinitialize power- and reset- management pins
       ExtMem_Pins_Deinit();

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

       return false;
   }

   // Check boundaries
   if( flashDriver->api->flashProperties->minAddress >= flashDriver->api->flashProperties->maxAddress )
       return false;

   // Check sizes
   if( flashDriver->api->flashProperties->maxSectors == 0 || flashDriver->api->flashProperties->sectorSize == 0 )
       return false;

   // Update banks properties with actual values
   ActualBankProperties.factoryBankSize = EXTMEM_BANK0_SIZE;
   ActualBankProperties.userBankSize = EXTMEM_BANK1_SIZE;
   ActualBankProperties.factoryBankAddress = flashDriver->api->flashProperties->minAddress;
   ActualBankProperties.userBankAddress = flashDriver->api->flashProperties->minAddress + ActualBankProperties.factoryBankSize;

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

   // initialize driver routines and data
   ExtMemHandle.Write = ExtMem_Write;
   ExtMemHandle.Read = ExtMem_Read;
   ExtMemHandle.RangeCheck_Read = ExtMem_RangeCheck_Read;
   ExtMemHandle.RangeCheck_Write = ExtMem_RangeCheck_Write;
   ExtMemHandle.BankProtect = ExtMem_BankProtect;
   ExtMemHandle.CheckBankProtect = ExtMem_CheckBankProtect;
   ExtMemHandle.pFlashProperties = flashDriver->api->flashProperties;
   ExtMemHandle.pBanksProperties = &ActualBankProperties; 

   // Initialize flash driver
   if( FLASH_ERROR( flashDriver->api->routines.flashInitialize(false) ) )
   {
       // finalize after initialization fail
       flashDriver->api->routines.flashFinalize( false, true );

       // deinitialize power- and reset- management pins
       ExtMem_Pins_Deinit();

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

       // flash driver initialization error
       return false;
   }

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

 // ExtMem_Internal_FillBankingTemplate
 // Calculates the sMemoryBankingTemplate_t structure during run-time.
 // Function relies on the bank properties passed in @pBankProperties.
 static void ExtMem_Internal_FillBankingTemplate( const ExtMem_Bank_Properties_t * pBankProperties, sMemoryBankingTemplate_t * pFactoryProtectionTemplate )
 {
   // The first bank's sector (including the sector)
   size_t factoryBankSectorBegin = __FLASH_ADDRESS2SECTOR_BEGIN(pBankProperties->bankAddress, ExtMemHandle.pFlashProperties->sectorSize);
 
   // The last bank's sector (including the sector) 
   size_t factoryBankSectorEnd = __FLASH_ADDRESS2SECTOR_END(pBankProperties->bankAddress + pBankProperties->bankSize, ExtMemHandle.pFlashProperties->sectorSize);
   
   // Fill the structure with zeros before operation
   for( size_t nZero = 0; nZero < sizeof(pFactoryProtectionTemplate->selectRegister); ++nZero )
   {
       pFactoryProtectionTemplate->selectRegister.bytes[nZero] = 0;
   }

   // Mark the affected sectors in the output structure
   for( size_t nSector = factoryBankSectorBegin; nSector < factoryBankSectorEnd; ++nSector )
   {
       BITBUFFER_SET( pFactoryProtectionTemplate->selectRegister.bytes, nSector );
   }
 }

 // Initialize ExtMem power- and reset- management GPIO-pins 
 static void ExtMem_Pins_Init()
 {
    #if W25QXXX_RESET_MANAGEMENT || AT45DBXXX_RESET_MANAGEMENT 
    // Configure GPIO pins for AT45 IC : 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);
    #endif
 }

 // De-Initialize ExtMem power- and reset- management GPIO-pins 
 static void ExtMem_Pins_Deinit()
 {
    #if W25QXXX_RESET_MANAGEMENT || AT45DBXXX_RESET_MANAGEMENT 
    // 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);
    #endif
 }
 
 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;

     union
     {
        bool globalProtectionStatus;     // AT25
        bool anySectorProtectionStatus;  // W25Q
     };
     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 )
 {
     sMemoryBankingTemplate_t * pBankTemplate = NULL;
     flash_api_getprotect_t getProtectRegister;

     // Check if the driver is loaded
     if( NULL == flashDriver ) return false;

     if( NULL != pProtEntry )
     {
         // This function uses individual sector protecton
         //
         switch( bankId )
         {
             case extmem_bank_factory:
             {
                  pBankTemplate = &ACMMemory_BankX;
                  // use dynamic template for FACTORY bank
                  ExtMem_Internal_FillBankingTemplate( &ExtMemHandle.pBanksProperties->factoryBank, pBankTemplate );
             }
             break;
             case extmem_bank_user:
             {
                  pBankTemplate = &ACMMemory_BankX;
                  // use dynamic template for USER bank
                  ExtMem_Internal_FillBankingTemplate( &ExtMemHandle.pBanksProperties->userBank, pBankTemplate );
             }
             break;
         }

         if( NULL != pBankTemplate )
         {
            // Retrieve currect sector protection register:
            if( FLASH_SUCCESS( flashDriver->api->routines.flashGetProtect( &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( flashDriver->api->routines.flashGetProtect( 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 )
 {
     sProtectionEntry_t     protEntry_Bank0;       // protection information for Bank0
     sProtectionEntry_t     protEntry_Bank1;       // protection information for Bank1

     // Check if the driver is loaded
     if( NULL == flashDriver ) return false;
     // --------------------
     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( flashDriver->api->routines.flashProtectEx( &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( flashDriver->api->routines.flashProtectEx( &setProtectRegister,
                             (( stateBank1 )?(flash_sector_protected):(flash_sector_unprotected))
                       ) ) )
                     {
                         // Error: can not modify individual sector protection flags
                         return false;
                     }
                 }

                 // Modify global write-protection flag: protected
                 // W25Q: stub call, always return FLERR_SUCCESS
                 return FLASH_SUCCESS( flashDriver->api->routines.flashProtect( NULL ) );
             }
             else
             {
                 // If global protection state will be reset: all banks must be unprotected
                 // Modify global write-protection flag: unprotected
                 return FLASH_SUCCESS( flashDriver->api->routines.flashUnprotect( 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
  }

 #if AT45DBXXX_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 AT45DBXXX_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 AT45DBXXX_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 )
 {
     // Check if the driver is loaded
     if( NULL == flashDriver ) return false;

     extern volatile bool bExtMemStatus;

     return bExtMemStatus && FLASH_SUCCESS( flashDriver->api->routines.flashWrite( flashDriver->api->flashProperties->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 )
 {
     // Check if the driver is loaded
     if( NULL == flashDriver ) return false;

     extern volatile bool bExtMemStatus;

     return bExtMemStatus && FLASH_SUCCESS( flashDriver->api->routines.flashRead( flashDriver->api->flashProperties->minAddress + address, pBuffer, size ) );
 }
 #endif

 static bool ExtMem_RangeCheck_Read( flash_address_t address, flash_address_t size )
 {
     // Check if the driver is loaded
     if( NULL == flashDriver ) return false;

     if( size <= (flashDriver->api->flashProperties->maxAddress - flashDriver->api->flashProperties->minAddress) )
     {
         if( address < flashDriver->api->flashProperties->maxAddress - flashDriver->api->flashProperties->minAddress - size )
         {
             return true;
         }
     }

     return false;
 }

 static bool ExtMem_RangeCheck_Write( flash_address_t address, flash_address_t size )
 {
     // Check if the driver is loaded
     if( NULL == flashDriver ) return false;

     if( size <= (flashDriver->api->flashProperties->maxAddress - flashDriver->api->flashProperties->minAddress) )
     {
         if( address < flashDriver->api->flashProperties->maxAddress - flashDriver->api->flashProperties->minAddress - size )
         {
             return true;
         }
     }

     return false;
 }

 // legacy compatibility
 flash_err_t flash_read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
     // Check if the driver is loaded
     if( NULL == flashDriver ) return FLERR_DEVICE_MALFUNCTION;

     extern volatile bool bExtMemStatus;

     if( bExtMemStatus )
     return flashDriver->api->routines.flashRead( address, pBuffer, size );

     return FLERR_DEVICE_MALFUNCTION; // !bExtMemStatus
 }

 // legacy compatibility
 flash_err_t flash_write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size, flash_write_mode_t unused )
 {
     // Check if the driver is loaded
     if( NULL == flashDriver ) return FLERR_DEVICE_MALFUNCTION;

     extern volatile bool bExtMemStatus;

     (void)unused;

     if( bExtMemStatus )
     return flashDriver->api->routines.flashWrite( address, pBuffer, size, fwm_safewrite );

     return FLERR_DEVICE_MALFUNCTION; // !bExtMemStatus
 }
