 // Файл с API функциями работы с FLASH-чипом W25Q16JV
 // v 1.0 от 14/10/20
 // Автор: Сычев А.
 #define W25Q16JV_API_C
 #define W25Q16JV_APILEVEL   // использование интерфейса низкоуровневых функций
 #include "drivers\flash\w25q\config\W25Q16JV_CONF.h"
 #include "drivers\flash\base\flash_api_error.h"
 #include "drivers\flash\base\flash_api_types.h"
 #include "drivers\flash\base\flash_api_base.h"
 #include "drivers\flash\w25q\lowlevel\W25Q16JV_GLOB.h"
 #include "drivers\flash\w25q\lowlevel\W25Q16JV_LL_func.h"
 #include "drivers\flash\w25q\common\W25Q_HAL.h"

 // внутренний дескриптор LL-буфера
 typedef struct
 {
    union
    {
       void * rawptr;

       __flash_sectorprotectionregister_t * pSectorProtectionRegister;
       __FLASH_BYTE                       * pageBuffer;
    };

    size_t size;
 }
 flash_LL_buf_t;

 static const char W25QFLASH_TEXTUAL_DESCRIPTION[] = "W25Q16JV";

 // дескриптор конфигурации памяти
 static const flash_properties_t FlashProperties_W25Q16JV =
 {
    .minAddress  = W25QFLASH_MINIMUM_ADDRESS,
    .maxAddress  = W25QFLASH_MAXIMUM_ADDRESS,
    .maxSectors  = W25QFLASH_SECTORS,
    .sectorSize  = (W25QFLASH_PAGES_PER_SECTOR * W25QFLASH_PAGE_SIZE),
    .pChipDescription = W25QFLASH_TEXTUAL_DESCRIPTION
 };

 // flash_api_initialize - инициализация драйвера и API работы с устройством
 static flash_err_t flash_api_initialize( bool detectOnly );

 // flash_api_getready - проверка готовности устройства (со стандартным таймаутом)
 static flash_err_t flash_api_getready();

 // flash_api_service - функция получения общих сведений об флеш-памяти.
 static flash_err_t flash_api_service( flash_service_info_t si, void * pdata, __FLASH_WORD * pbufsize );

 // flash_api_protect - установить защиту секторов
 static flash_err_t flash_api_protect( flash_api_protect_t * content );

 // flash_api_unprotect - снять защиту секторов
 static flash_err_t flash_api_unprotect( flash_api_protect_t * content );

 // flash_api_protect_ex - установить/снять защиту секторов
 static flash_err_t flash_api_protect_ex( flash_api_protect_t * content, flash_api_protect_bits mode );

 // flash_api_getprotect - получить информацию о защищенных секторах.
 static flash_err_t flash_api_getprotect( flash_api_getprotect_t * content );

 // flash_api_erase - мультирежимное стирание данных
 static flash_err_t flash_api_erase( flash_erase_mode_t mode, __FLASH_DWORD start, __FLASH_DWORD count );

 // flash_api_write - мультирежимная запись данных по адресу
 static flash_err_t flash_api_write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size, flash_write_mode_t mode );

 // flash_api_read - чтение данных по адресу
 static flash_err_t flash_api_read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size );

 // flash_api_sleepmode - переход в режим пониженного энергопотребления
 static flash_err_t flash_api_sleepmode();

 // flash_api_wakeup - выход из режима пониженного энергопотребления
 static flash_err_t flash_api_wakeup();

 // flash_api_finalize - деинициализация драйвера
 static flash_err_t flash_api_finalize( bool bHibernate, bool bForce );

 // дескриптор API
 const flash_api_descriptor_t W25Q16JV_API =
 {
   .routines = {
     .flashInitialize = flash_api_initialize,
     .flashGetReady   = flash_api_getready,
     .flashService    = flash_api_service,
     .flashProtect    = flash_api_protect,
     .flashUnprotect  = flash_api_unprotect,
     .flashProtectEx  = flash_api_protect_ex,
     .flashGetProtect = flash_api_getprotect,
     .flashErase      = flash_api_erase,
     .flashWrite      = flash_api_write,
     .flashRead       = flash_api_read,
     .flashSleepMode  = flash_api_sleepmode,
     .flashWakeup     = flash_api_wakeup,
     .flashFinalize   = flash_api_finalize,
   },

   .flashProperties = &FlashProperties_W25Q16JV
 };

// Enter() и Return(.) - макросы блокировки ресурса в многозадачных ОС
// При входе в критический участок кода - выполнение API функции - вызывается Enter(),
// блокируя ресурс, при выходе - Leave() или Return(.) ( для выхода из функции )
#define Enter()              { __FLASH_LOCK();   }
#define Leave()              { __FLASH_UNLOCK(); }
#define Return(code)         { __FLASH_UNLOCK(); return (code); }

//#define ret                  __FLASH_UNLOCK(); return

 // Done:
 //   flash_api_initialize(...)  // Высокоуровневая инициализация памяти (установка размера страницы, установка параметров защиты секторов...)
 //   flash_api_erase(...)       // стирание памяти (блоками, страницами или секторами)
 //   flash_api_read(...)        // чтение памяти по адресу
 //   flash_api_protect(...)     // защита секторов (модификация конфигурации защищаемых секторов)
 //   flash_api_unprotect(...)   // снятие защиты (модификация конфигурации защищаемых секторов)
 //   flash_api_service(...)     // получение информации о производителе, расширенной информации, размере памяти, размере страницы, количестве секторов, блоков, страниц и т.п.
 //   flash_api_write(...)       // запись памяти по адресу

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_getready - проверка готовности устройства (со стандартным таймаутом)
 //
 // # Функция возвращает FLERR_SUCCESS, еслиу устройство готово, иначе FLERR_TIMEOUT
 static flash_err_t flash_api_getready() // [W25Q OK]
 {
     Enter();

     if( W25Q_LL_Routines.ready.smartWaitms( W25QXXX_API_TIMEOUT )  )
     {
         Return(FLERR_SUCCESS);
     }

     Return(FLERR_TIMEOUT);
 }

 static flash_err_t flash_getready_time( __FLASH_WORD timeout ) // [W25Q OK]
 {
     Enter();

     if( W25Q_LL_Routines.ready.smartWaitms( timeout )  )
     {
         Return(FLERR_SUCCESS);
     }

     Return(FLERR_TIMEOUT);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_modeSetup - Настройка общих параметров работы на этапе инициализации
 // - настройка режима SPI (Отключение Quad-режима)
 // - сброс режима комплиментарности групповой защиты (фактически выключение групповой защиты в купе со сбросом индикаторов BP)
 // - проверка блокировки статусных регистров
 // - проверка отсутствия режима suspend
 // - инициализация режима индивидуальной защиты секторов
 // - режим групповой защиты секторов будет отключен, т.к. он не удовлетворяет API
 // - настройка тока выходного драйвера
 // - настройка вывода управления ~WP: включение/отключение защиты по ~WP в зависимости от W25QXXX_HW_WR_PROTECT
 // - сброс индикаторов групповой защиты (BP)
 // * Достигается состояние, при котором при выборе режима групповой защиты (WPS=0) защита секторов выключается, что
 //   позволяет использовать WPS как общий бит защиты секторов: при WPS=0 защита выключена, при WPS=1 включена индивидуально
 // # Функция возвращает FLERR_SUCCESS. Только в этом случае гарантируется нормальная работа.
 // # Функция возвращает ошибку инициализации при появлении сбоев или при непрохождении проверки
 static flash_err_t flash_modeSetup() // [W25Q OK]
 {
    flash_err_t success = FLERR_SUCCESS;
    // -------------------------------------------------------------------------
    // - настройка режима SPI (Отключение Quad-режима)
    // - проверка блокировки статусных регистров
    // - проверка отсутствия режима suspend
    {
      __flash_status2_t status;
      __flash_status2_t status_bak;

      // чтение текущего режима
      W25Q_LL_Routines.status.statusRead2( &status );

      // копируем состояние регистра для последующей сверки
      status_bak.status = status.status;

      // проверка блокировки регистров
      if( __FLASH_SRL_LOCKED == status.bStatLock )
      {
          // запись в статусные регистры запрещена по следующего цикла включения питания

          #if W25QXXX_POWER_MANAGEMENT
          // если разрешено управление питанием, пробуем разрешить проблему циклом подачи питания
          W25Q_LL_Routines.powerPulse();

          // повторное чтение регистра
          W25Q_LL_Routines.status.statusRead2( &status );

          // повторная проверка
          if( __FLASH_SRL_LOCKED == status.bStatLock )
          {  // если без изменений - результат: ошибка
          #endif

          // ошибка настройки: регистры блокированы
          success = FLERR_DEVICE_MALFUNCTION;
          return(success);

          #if W25QXXX_POWER_MANAGEMENT
          }
          #endif
      }

      // проверка режима suspend
      if( __FLASH_SYS_ENABLED == status.bSuspended )
      {
          // задействован режим suspend, нужно сбросить

          #if W25QXXX_POWER_MANAGEMENT
          // если разрешено управление питанием, пробуем разрешить проблему циклом подачи питания
          W25Q_LL_Routines.powerPulse();

          // повторное чтение регистра
          W25Q_LL_Routines.status.statusRead2( &status );

          // повторная проверка
          if( __FLASH_SYS_ENABLED == status.bSuspended )
          {  // если без изменений - результат: ошибка
          #endif

          // ошибка настройки: регистры блокированы
          success = FLERR_DEVICE_MALFUNCTION;
          return(success);

          #if W25QXXX_POWER_MANAGEMENT
          }
          #endif
      }

      // отключение режима Quad, если возможно.
      // На чипах IQ и JQ (W25QFLASH_FAMILYDATAFLASHQ) режим Quad заблокирован от выключения.
      __FLASH_DWORD flashID = W25Q_LL_Routines.id.manufacturerIdRead(0);
      if( W25QFLASH_FAMILYDATAFLASHQ != ((__flash_rvid_t*) &flashID)->DevId.Family )
      {
      // проверка режима Quad
      if( __FLASH_QUAD_ENABLED == status.bQuadEn )
      {
          // отключаем режим QUAD
          status.bQuadEn = __FLASH_QUAD_DISABLED;
      }
      }

      // сброс режима комплиментарности групповой защиты (фактически выключение групповой защиты)
      if( __FLASH_QUAD_ENABLED == status.bCmpProt )
      {
          // отключаем режим комплиментарности
          status.bCmpProt = __FLASH_CMP_DISABLE;
      }

      // проверка, есть ли измененные настройки, которые нужно записать?
      if( status_bak.status ^ status.status )
      {
          // включение разрешения инструкций записи
          W25Q_LL_Routines.operations.writeEnable();

          // запись регистра (сохранение параметров режима защиты)
          W25Q_LL_Routines.status.statusWrite2( &status );

          // проверка результата: повторное чтение
          W25Q_LL_Routines.status.statusRead2( &status_bak );
      }

      // проверка установленных параметров
      if( status_bak.status ^ status.status )
      {
          // ошибка настройки: не удалось записать требуемые настройки
          success = FLERR_DEVICE_MALFUNCTION;
          return(success);
      }
    }
    // -------------------------------------------------------------------------
    // - инициализация режима индивидуальной защиты секторов
    // - режим групповой защиты секторов будет отключен, т.к. он не удовлетворяет API
    // - настройка тока выходного драйвера
    {
      __flash_status3_t status;
      __flash_status3_t status_bak;

      // чтение текущего режима
      W25Q_LL_Routines.status.statusRead3( &status );

      // копируем состояние регистра для последующей сверки
      status_bak.status = status.status;

      if( __FLASH_WPS_GROUP_PROTECT == status.bWPSel )
      {
          // выбран групповой режим защиты: вЫключаем его
          status.bWPSel = __FLASH_WPS_INDIVIDUAL_PROTECT; // переключение режима защиты от записи: индивидуальная защита
      }

      if( __FLASH_DRS_DEFAULT != status.cDrvStr )
      {
          // выбрано неверное значение тока ключа
          status.cDrvStr = __FLASH_DRS_DEFAULT; // переключение выходного тока ключа
      }

      // проверка, есть ли измененные настройки, которые нужно записать?
      if( status_bak.status ^ status.status )
      {
          // включение разрешения инструкций записи
          W25Q_LL_Routines.operations.writeEnable();

          // запись регистра (сохранение параметров режима защиты)
          W25Q_LL_Routines.status.statusWrite3( &status );

          // проверка результата: повторное чтение
          W25Q_LL_Routines.status.statusRead3( &status_bak );
      }

      // проверка установленных параметров
      if( status_bak.status ^ status.status )
      {
          // ошибка настройки: не удалось записать требуемые настройки
          success = FLERR_DEVICE_MALFUNCTION;
          return(success);
      }
    }
    // -------------------------------------------------------------------------
    // - настройка вывода управления ~WP: включение/отключение защиты по ~WP в зависимости от W25QXXX_HW_WR_PROTECT
    // - сброс индикаторов групповой защиты
    {
      __flash_status1_t status;
      __flash_status1_t status_bak;

      // чтение текущего режима
      W25Q_LL_Routines.status.statusRead1( &status );

      // копируем состояние регистра для последующей сверки
      status_bak.status = status.status;

      if( __FLASH_SRP_WP_DEFAULT != status.bStatProt )
      {
          // Настройка выводв ~WP отличается от требуемой
          status.bStatProt = __FLASH_SRP_WP_DEFAULT; // переключение режима ~WP
      }

      if( __FLASH_BP_RESET != status.cBlkProt )
      {
          // сброс индикаторов групповой защиты
          status.cBlkProt = __FLASH_BP_RESET; // переключение режима защиты
      }

      // проверка, есть ли измененные настройки, которые нужно записать?
      if( status_bak.status ^ status.status )
      {
          // включение разрешения инструкций записи
          W25Q_LL_Routines.operations.writeEnable();

          // запись регистра (сохранение параметров режима защиты)
          W25Q_LL_Routines.status.statusWrite1( &status );

          // проверка результата: повторное чтение
          W25Q_LL_Routines.status.statusRead1( &status_bak );
      }

      // проверка установленных параметров
      if( status_bak.status ^ status.status )
      {
          // ошибка настройки: не удалось записать требуемые настройки
          success = FLERR_DEVICE_MALFUNCTION;
          return(success);
      }
    }

    return success;
 }

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_initialize - инициализация драйвера и API работы с устройством
 // * Если @detectOnly=true, производится только детектирование наличия чипа!
 // # Функция возвращает FLERR_SUCCESS. Только в этом случае гарантируется нормальная работа.
 // # Функция возвращает ошибку инициализации при появлении сбоев или при непрохождении проверки (см файл конфигурации AT45DB161E_CONF.h)
 static flash_err_t flash_api_initialize( bool detectOnly ) // [W25Q OK]
 {
    // производится определение наличия чипа на шине
    if( detectOnly )
    {
        return W25Q_LL_Routines.service.startupDetect();
    }

    // производим нисзкоуровневую инициализацию: подаем питание и будим, проверяем наличие чипа, серию, производителя, размер...
    flash_err_t success = W25Q_LL_Routines.service.initialize();

    if( FLASH_ERROR(success) )
    {
        // что-то пошло не так: ай печаль-печаль.
        return(success);
    }

    // -------------------------------------------------------------------------
    // конфигурирование режима работы
    success = flash_modeSetup();

    if( FLASH_ERROR(success) )
    {
        // что-то пошло не так: ай печаль-печаль.
        return(success);
    }

    // -------------------------------------------------------------------------
    // отключаем защиту секторов при инициализации (разрешение записи во все сектора)
    success = flash_api_unprotect( NULL );

    if( FLASH_ERROR(success) )
    {
        // что-то пошло не так: ай печаль-печаль.
        return(success);
    }

    // считываем статус
    if( ! W25Q_LL_Routines.ready.smartWaitms( W25QXXX_API_TIMEOUT ) )
    {
        // что-то пошло не так: ай печаль-печаль.
        return(success);
    }

    return(FLERR_SUCCESS);
 }

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_finalize - безопасная де-инициализация драйвера
 // Функция завершает работу с флеш-памятью, отключаяя ее или переводя в режим гибернации
 //  @bHibernate: булевый флаг, показывает, что нужно перевести флеш-память в режим гибернации,
 //    если false - драйвер попытается отключить питание микросхемы, если разрешено управление питанием (AT45DBXXX_POWER_MANAGEMENT)
 //    если false и управление питанием запрещено, будет возвращена ошибка FLERR_INVALID_PARAMETER.
 //  @bForce: насильное выполнение финализации в случае, если флеш-память занята незаконченной операцией.
 //    если false и драйвер детектирует незаконченную операцию, будет возвращена ошибка FLERR_UNEXPECTED_BUSY.
 // Если выбран режим гибернации, после выдачи команды функция проверяет, уснула ли микросхема через чтение статуса.
 // Если в этом случае обнаруживается, что статус все еще считывается, генерируется ошибка FLERR_GENERIC_ERROR.
 // Данную проверку можно отключить опцией W25QXXX_DONOTCHECK_HIBERNATE=1.
 // Внимание: проверка не реализуется если bHibernate=false.
 // # Функция возвращает FLERR_SUCCESS всегда, за исключением вышеописанных случаев.
 static flash_err_t flash_api_finalize( bool bHibernate, bool bForce ) // [W25Q OK]
 {
     Enter();

     // подготовка к деинициализации
     flash_err_t status = W25Q_LL_Routines.service.finalizePrepare();

     if( FLASH_ERROR(status) )
     {
         // ошибка, обнаружена проблема - устройство не готово

         // проверяем, разрешено ли принудительное завершение?
         if( bForce )
         {
             // разрешено, сначала производим сброс

             // доступно управление сигналом сброса?
             #if W25QXXX_RESET_MANAGEMENT
                 // да, производим сброс чипа
                 W25Q_LL_Routines.reset.resetPulse();
             #else
                 // нет, а доступно управление питанием?
                 #if W25QXXX_POWER_MANAGEMENT
                 // да, производим быстрый сброс чипа по питанию
                 W25Q_LL_Routines.power.powerPulse( 0, 0 );
                 #else
                 // нет, управление питанием тоже недоступно
                 // возвращаем ошибку FLERR_INVALID_PARAMETER - невозможно выполнить принудительное отключение
                 Return(FLERR_INVALID_PARAMETER);
                 #endif
             #endif
         }
         else
         {
             // нет, принудительное завершение запрещено
             // возвращаем ошибку
             Return(status);
         }
     }

     // все нормально, продолжаем

     // выбран режим гибернации?
     if( bHibernate )
     {
         // отправляем микросхему поспать
         W25Q_LL_Routines.sleep.sleepMode();

         #if W25QXXX_DONOTCHECK_HIBERNATE == 0
         // проверяем, ответит ли микросхема?
         if( W25Q_LL_Routines.ready.smartWaitms( W25QXXX_API_TIMEOUT ) )
         {
             // отвечает - бит готовности прочитался...
             // Может это просто подтянутый сигнал к +VCC?
             // попробуем по другому:

             if( FLASH_SUCCESS(W25Q_LL_Routines.service.detect()) )
             {
                 // ответила! значит точно не ложилась спать - ошибка
                 Return(FLERR_DEVICE_MALFUNCTION);
             }
         }
         #endif
     }
     else
     {
         // доступно управление питанием?
         #if W25QXXX_POWER_MANAGEMENT == 0
         // нет, недоступно, нечего делать, генерируем ошибку
         Return(FLERR_INVALID_PARAMETER);
         #else
         // Доступно, отключаем питание.
         W25Q_LL_Routines.power_off();

         // не проверяем доступность, доверяем функции отключения питания
         #endif
     }

     Return(FLERR_SUCCESS);
 }

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_service - функция получения общих сведений об флеш-памяти.
 // Полный список возможных запросов приведен ниже:
 //        ЗАПРОС        РАЗМЕР   ТИП       ТИП ИНФОРМАИИ
 //                      БУФЕРА   ЗНАЧНИЯ
 //   fsvc_getVendorId      2   __FLASH_WORD            ID производителя
 //   fsvc_getDeviceId      2   flash_deviceid_t        ID устройства
 //   fsvc_getExtInfo       *   __FLASH_BYTE[]          расширенную информацию производителя
 //   fsvc_getExtInfoLen    1   __FLASH_BYTE            длинну расширенной информации производителя
 //   fsvc_getCapacity      4   __FLASH_DWORD           заявляемая по документации емкость устройства (MBit-s)
 //   fsvc_getFullCapacity  4   __FLASH_DWORD           полная емкость устройства (в байтах)
 //   fsvc_getDataCapacity  4   __FLASH_DWORD           доступная емкость устройства (в байтах)
 //   fsvc_getProtect       1   __FLASH_BYTE            информацию о защите секторов (общий бит защиты 0x00 или 0x01)
 //   fsvc_getSectorsCount  2   __FLASH_WORD            количество секторов
 //   fsvc_getPagesCount    2   __FLASH_WORD            количество страниц
 //   fsvc_getBlocksCount   2   __FLASH_WORD            количество блоков
 //   fsvc_getPageSize      2   __FLASH_WORD            размер страницы
 //   fsvc_getReadyState    1   __FLASH_BYTE            готовность устройства
 //   fsvc_getMinAddress    4   __FLASH_DWORD           минимально возможный линейный адрес
 //   fsvc_getMaxAddress    4   __FLASH_DWORD           максимально возможный линейный адрес

 //
 // * Параметр @si задает тип запроса (см. flash_service_info_t)
 // * Параметр @pdata должен указывать на заранее выделенный буфер памяти для сохранения
 //   получаемых данных. Размер буфера зависит от типа запроса. Параметр не может быть NULL
 // * Параметр @pbufsize должен указывать на размер буфера, на который указывает pdata.
 //   Параметр не может быть 0. Значение по указателю @pbufsize не может быть 0.
 //   При успешном выполнении функции, по указателю @pbufsize будет записано фактическое количество
 //   записанных в буфер данных.
 //
 // * Функция НЕ ОБНУЛЯЕТ память по указателю @pdata в случае, если передан слишком большой буфер.
 //
 // # Функция вернет ошибку FLERR_INVALID_PARAMETER при некорректном запросе @si
 // # Функция вернет ошибку FLERR_INVALID_PARAMETER если @pdata или @bufsize равны 0.
 // # Функция вернет ошибку FLERR_INVALID_BUFFER если размер буфера @size не достаточен для сохранения данных
 //
 // # Функция вернет FLERR_SUCCESS при отсутствии ошибок
 //
 static flash_err_t flash_api_service( flash_service_info_t si, void * pdata, __FLASH_WORD * pbufsize ) // [W25Q OK]
 {
    if( !pdata || !pbufsize || !*pbufsize ) return(FLERR_INVALID_PARAMETER);

    Enter();

    switch( si )
    {
       case fsvc_getVendorId:        // ID производителя
       case fsvc_getDeviceId:        // ID устройства
       case fsvc_getCapacity:        // заявляемая по документации емкость устройства (MBit-s)
       case fsvc_getDataCapacity:    // доступная емкость устройства
       case fsvc_getFullCapacity:    // полная емкость устройства
       {
            __FLASH_DWORD flashID =  W25Q_LL_Routines.id.manufacturerIdRead( 0 );

            switch( si )
            {
                 case fsvc_getVendorId:     if( sizeof( __FLASH_WORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_WORD*)(pdata)) = ((__flash_rvid_t*) &flashID)->FullId;
                 *pbufsize                 = sizeof(__FLASH_WORD);

                 Return(FLERR_SUCCESS);
                 //-------------------------
                 case fsvc_getDeviceId:     if( sizeof( __FLASH_WORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((flash_deviceid_t*)(pdata)) = ((__flash_rvid_t*) &flashID)->DevId;
                 *pbufsize                 = sizeof(__FLASH_WORD);

                 Return(FLERR_SUCCESS);
                 //-------------------------
                 case fsvc_getCapacity:     if( sizeof( __FLASH_DWORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_DWORD*)(pdata)) = __FLASH_DENSITY2CAPACITY( ((flash_deviceid_t*)&((__flash_rvid_t*) &flashID)->DevId)->Density );
                 *pbufsize                  = sizeof(__FLASH_DWORD);

                 Return(FLERR_SUCCESS);

                 //-------------------------
                 case fsvc_getDataCapacity: if( sizeof( __FLASH_DWORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_DWORD*)(pdata)) = (W25QFLASH_MAXIMUM_ADDRESS - W25QFLASH_MINIMUM_ADDRESS);
                 *pbufsize                  = sizeof(__FLASH_DWORD);

                 Return(FLERR_SUCCESS);

                 //-------------------------
                 case fsvc_getFullCapacity: if( sizeof( __FLASH_DWORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_DWORD*)(pdata)) = (W25QFLASH_MAXIMUM_ADDRESS);
                 *pbufsize                  = sizeof(__FLASH_DWORD);

                 Return(FLERR_SUCCESS);
            }
       }
       break;

       case fsvc_getMinAddress:
       {
            if( sizeof( __FLASH_DWORD ) > *pbufsize )
            Return(FLERR_INVALID_BUFFER);

            *((__FLASH_DWORD*)(pdata)) = (W25QFLASH_MINIMUM_ADDRESS);
            *pbufsize                  = sizeof(__FLASH_DWORD);

            Return(FLERR_SUCCESS);
       }
       break;

       case fsvc_getMaxAddress:
       {
            if( sizeof( __FLASH_DWORD ) > *pbufsize )
            Return(FLERR_INVALID_BUFFER);

            *((__FLASH_DWORD*)(pdata)) = (W25QFLASH_MAXIMUM_ADDRESS);
            *pbufsize                  = sizeof(__FLASH_DWORD);

            Return(FLERR_SUCCESS);
       }
       break;

       //-------------------------
       case fsvc_getExtInfoLen:      // длинну расширенной информации производителя
       case fsvc_getExtInfo:         // расширенную информацию производителя
       {
            __flash_id_t  mid;

            switch( si )
            {
                 case fsvc_getExtInfoLen:

                       if( sizeof( __FLASH_BYTE ) > *pbufsize )
                       Return(FLERR_INVALID_BUFFER);

                       mid.pExtBf = 0;
                       mid.ExtLen = 0;

                       // получение ДЛИННЫ расширенной информации
                       W25Q_LL_Routines.id.manufacturerIdRead( &mid );

                       *((__FLASH_BYTE*)(pdata)) = mid.ExtLen;
                       *pbufsize                 = sizeof(__FLASH_BYTE);

                 Return(FLERR_SUCCESS);

                 case  fsvc_getExtInfo:

                       if( 1 * sizeof( __FLASH_BYTE ) > *pbufsize )
                       Return(FLERR_INVALID_BUFFER);

                       mid.pExtBf = pdata;
                       mid.ExtLen = *pbufsize;

                       // получение расширенной информации
                       W25Q_LL_Routines.id.manufacturerIdRead( &mid );

                       *pbufsize = mid.ExtLen;

                 Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getReadyState:      // готовность устройства
       {
            if( sizeof( __FLASH_BYTE ) <= *pbufsize )
            {
                *((__FLASH_BYTE*)(pdata)) = ( FLASH_SUCCESS( W25Q_LL_Routines.ready.getReadyFast() ) )? 0x01: 0x00;

                *pbufsize = sizeof(__FLASH_BYTE);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getProtect:         // информацию о защите секторов
       {
            if( sizeof( __FLASH_BYTE ) <= *pbufsize )
            {
                flash_api_getprotect_t content;

                flash_err_t status = flash_api_getprotect( &content );

                if( FLASH_SUCCESS(status) )
                {
                    // любой из секторов защищен?
                    if( content.sectors.hiprotect != 0 && content.sectors.loprotect != 0 )
                    {
                        // как минимум один из секторов защищен
                        *pbufsize = sizeof(__FLASH_BYTE);
                        *((__FLASH_BYTE*)(pdata)) = 1;
                    }
                    else
                    {
                        // ни один из секторов не защищен
                        *pbufsize = sizeof(__FLASH_BYTE);
                        *((__FLASH_BYTE*)(pdata)) = 0;
                    }

                    Return(FLERR_SUCCESS);
                }

                Return(status);  // Ошибка?
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getPageSize:        // размер страницы
       {
            if( sizeof( __FLASH_WORD ) >= *pbufsize )
            {
                *((__FLASH_WORD*)(pdata)) = W25QFLASH_PAGE_SIZE;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getSectorsCount:    // количество секторов
       {
            if( sizeof( __FLASH_WORD ) <= *pbufsize )
            {
                *((__FLASH_WORD*)(pdata)) = W25QFLASH_SECTORS;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getPagesCount:      // количество страниц
       {
            if( sizeof( __FLASH_WORD ) >= *pbufsize )
            {
                *((__FLASH_WORD*)(pdata)) = W25QFLASH_PAGES;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getBlocksCount:     // количество блоков
       {
            if( sizeof( __FLASH_WORD ) >= *pbufsize )
            {
                *((__FLASH_WORD*)(pdata)) = W25QFLASH_BLOCKS;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);
    }

    Return(FLERR_INVALID_PARAMETER);
 }

 // -----
 static flash_err_t flash_api_sectorprotectregister_read( __flash_sectorprotectionregister_t * contents );
 static flash_err_t flash_api_sectorprotectregister_write( __flash_sectorprotectionregister_t * contents );
 static flash_err_t flash_api_writeprotection_disable();

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_protect_ex - установить/снять защиту секторов
 // Поробности см. описание flash_api_protect() / flash_api_unprotect()
 //
 // @mode: flash_sector_unprotected  - вызов из flash_api_unprotect()
 // @mode: flash_sector_protected    - вызов из flash_api_protect()
 //
 static flash_err_t flash_api_protect_ex( flash_api_protect_t * content, flash_api_protect_bits mode ) // [W25Q WRONG]
 {
     Enter();

     // проверяем устройство на занятость
     if( FLERR_TIMEOUT == flash_api_getready() ) Return(FLERR_TIMEOUT);

     // проверяем на NULL переданный параметр
     if( NULL == content )
     {
         // попытка вызова без указания схемы защиты
         // допустимо только если mode===flash_sector_unprotected
         // НЕ допустимо, если mode===flash_sector_protected

         // проверка режима:
         if( mode == flash_sector_protected )
         {
            // параметр NULL, схема защиты не указана, нужно бы вернуть ошибку, т.к.
            // неизвестно, какие сектора нужно защищать, но для совместимости с драйверами чипов,
            // имеющих глобальный флаг защиты, здесь возвращается УСПЕХ, т.к. предполагается
            // что индивидуальные флаги защиты уже были проставлены на предыдущем этапе.
            // Для W25Q именно индивидуальные флаги будут использованы, а данный вызов - лишь заглушка
            // оставленная для совместимости
            Return(FLERR_SUCCESS);
         }

         // снятие защиты: если схема не указана (content=null), разрешаем запись во все сектора
         Return(flash_api_writeprotection_disable());
     }
     else
     {
         // указана схема модификации индивидуальных битов защиты секторов (content != NULL)

         // проверка режима модификации общего бита защиты
         switch( content->protection_modify )
         {
           // без модификации общего бита защиты
           case flash_sector_protect_nomodify:

                // не трогаем общий бит защиты

           break;

           // модификация общего бита защиты
           case flash_sector_protect_modify:

                if( mode != flash_sector_unprotected )
                {
                    // модификация общего бита защиты при установке защиты
                    // с указанием схемы - недоступна.
                    Return(FLERR_INVALID_PARAMETER);
                }

                // flash_sector_protected
                if( content->protection_enabled )
                {
                    // попытка установки общего флага защиты вместе с указанием схемы защиты
                    // не поддерживается
                    Return(FLERR_INVALID_PARAMETER);
                }
                // flash_sector_unprotected
                else
                {
                    // попытка снятия общего флага защиты вместе с указанием схемы защиты
                    // схема защиты - игнорируются
                    // разрешается запись во все сектора

                    Return(flash_api_writeprotection_disable());
                }

           break;

           default:
                // неправильный параметр в поле protection_modify
                Return(FLERR_INVALID_PARAMETER);
         }

         // LL-буфер
         flash_LL_buf_t buf;

         // получаем указатель на буфер драйвера
         // зануляем память (буфер занулен по умолчанию)
         buf.rawptr = W25Q_LL_Routines.service.getBuf( fibufSectorFlags, &buf.size );

         // считываем флаги защиты секторов
         flash_api_sectorprotectregister_read( buf.pSectorProtectionRegister );

         //------------------------------------------------------
         // модифицируем флаги защиты
         for( size_t nSector = 0; nSector < __SECTORS; ++nSector )
         {
              if( FLASH_PROTECTION_REGISTER_CHECK( content->sectors.bytes, nSector ) )
              {
                   // сектор выбран: устанавливаем или снимаем флаги защиты
                   if( mode == flash_sector_unprotected )
                   {
                       FLASH_PROTECTION_REGISTER_CLEAR( buf.pSectorProtectionRegister->bSectors, nSector );
                   }
                   else
                   {
                       FLASH_PROTECTION_REGISTER_SET( buf.pSectorProtectionRegister->bSectors, nSector );
                   }

              } else { /* не модифицировать статус защиты сектора */ }
         }

         //------------------------------------------------------
         // записываем флаги защиты секторов
         flash_api_sectorprotectregister_write( buf.pSectorProtectionRegister );

         //------------------------------------------------------
         {  // №№№№№№ ПРОВЕРКА №№№№№№
            // считываем флаги защиты секторов обратно для сравнения
            flash_api_sectorprotectregister_read( buf.pSectorProtectionRegister );

            // проверяем флаги защиты
            for( size_t nSector = 0; nSector < __SECTORS_TYPICAL; ++nSector )
            {
                 if( FLASH_PROTECTION_REGISTER_CHECK( content->sectors.bytes, nSector ) )
                 {
                      // сектор выбран: проверяем биты защиты
                      if( mode == flash_sector_unprotected )
                      {
                         if( FLASH_PROTECTION_REGISTER_CHECK( buf.pSectorProtectionRegister->bSectors, nSector ) )
                         Return(FLERR_GENERIC_ERROR);
                      }
                      else
                      {
                         if( ! FLASH_PROTECTION_REGISTER_CHECK( buf.pSectorProtectionRegister->bSectors, nSector ) )
                         Return(FLERR_GENERIC_ERROR);
                      }
                 }
            }
         }

         //------------------------------------------------------
         // успех.
         Return(FLERR_SUCCESS);
     }
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_protect - установить защиту секторов
 //
 // Функция модифицирует флаги защиты сектров выбранных пользователем в состояние "защищен".
 // Установите соответствующие флаги в структуре @content в состояние "1" для защиты сектора.
 // Функция не обрабатывает модификацию общего флага защиты, т.к. в W25QXXX он отсутствует.
 // Функция не может защитить выбранные сектора без указания схемы защиты, поскольку регистры индивидуальной
 // защиты секторов не сохраняются при отключении питания. Поэтому передача схемы защиты при вызове функции
 // обязательна, в отличие от flash_api_unprotect,  которая просто разрешает запись во все сектора сразу.
 // Поскольку схема защиты обязательна, общий флаг защиты при вызове функции упразднен, т.к. не имеет смысла.
 // Для сохранения сингатуры вызова функции (совместимость с драйвером AT45), поле protection_modify должно
 // быть установлено в значение flash_sector_protect_nomodify, в противном случае функция вернет
 // ошибку FLERR_INVALID_PARAMETER. Значение protection_enabled будет игнорироваться.
 //
 // * установите поле protection_modify в состояние "flash_sector_protect_nomodify".
 // * поле protection_enabled игнорируется
 // * параметр @content может быть равен NULL (совместимость с драйверами с общим флагом защиты)
 // * если @content равен NULL, функция всегда возвращает FLERR_SUCCESS для совместимости драйвера с драйверами
 //   чипов, имеющих общий флаг защиты. Предполагается, что этому предшествовал вызов с @content!=NULL,
 //   где указана схема защиты, которая и будет использована.
 //
 // # Функция вернет FLERR_SUCCESS, если в процессе операции не произошло ошибок.
 // # Функция вернет FLERR_INVALID_PARAMETER в случае, если обнаружит ошибку в переданном параметре
 // # Функция вернет FLERR_UNEXPECTED_BUSY в случае, если процесс выполнения был прерван из-за неожиданного отказа устройства
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если общий бит защиты не был установлен из-за ошибки
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если хотя бы один из выбранных битов защиты не был установлен/сброшен
 //
 static flash_err_t flash_api_protect( flash_api_protect_t * content ) // [W25Q OK]
 {
     return( flash_api_protect_ex( content, flash_sector_protected ));
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_unprotect - снять защиту секторов. Функция аналогичная flash_api_protect(),
 // только имеет обратный эффект - снятие защиты с выбранных секторов.
 //
 // Функция модифицирует флаги защиты сектров выбранных пользователем в состояние "не защищен".
 // Установите соответствующие флаги в структуре @content в состояние "1" для снятия защиты сектора.
 // Функция также обрабатывает модификацию общего флага защиты выбранных секторов, но особым образом.
 // Т.к. в W25QXXX отсутствует общий флаг защиты, функция просто разрешает запись во все секторы при попытке
 // сброса общего бита защиты. Установка общего бита защиты недоступна.
 // При вызове поддерживается сочетание protection_modify=flash_sector_protect_modify и protection_enabled=0,
 // либо protection_modify=flash_sector_protect_nomodify, в противном случае будет возвращена ошибка FLERR_INVALID_PARAMETER.
 //
 // * Флаги защиты секторов могут быть сброшены индивидуально, либо все сразу (сбросом изменения общего флага защиты)
 // * Для сброса защиты всех секторов установите поле protection_modify в состояние "flash_sector_protect_modify", а
 //   а поле protection_enabled=0, при этом значения частных флагов защиты секторов игнорируются
 //
 // * параметр @content может быть равен NULL, в этом случае разрешается запись во все сектора
 //
 // * Для снятия частных флагов защиты сектров:
 //      - установите поля частных флагов защиты требуемых секторов (s0...s63) в состояние flash_sector_protect_modify,
 //        и установите protection_modify в значение "flash_sector_protect_nomodify". Поле protection_enabled игнорируется.
 //
 // # Функция вернет FLERR_SUCCESS, если в процессе операции не произошло ошибок.
 // # Функция вернет FLERR_INVALID_PARAMETER в случае, если обнаружит ошибку в переданном параметре
 // # Функция вернет FLERR_UNEXPECTED_BUSY в случае, если процесс выполнения был прерван из-за неожиданного отказа устройства
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если общий бит защиты не был установлен из-за ошибки
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если хотя бы один из выбранных битов защиты не был установлен/сброшен
 //
 static flash_err_t flash_api_unprotect( flash_api_protect_t * content ) // [W25Q OK]
 {
     return( flash_api_protect_ex( content, flash_sector_unprotected ));
 }

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_getprotect - получить информацию о защищенных секторах.
 // * Указатель @content может быть NULL.
 // # Возвращаемое значение - FLERR_PROTECT_ENABLED если хотя бы один из секторов защищен от записи.
 // Если @content не NULL, то в @content будет записана структура типа flash_api_getprotect_t, содержащая детальную
 // информацию о защите сектора (один сектор на один бит), 1b значит, что сектор защищен, 0b - не защищен, а поле
 // protection_enabled будет равняться возвращаемому значению
 static flash_err_t flash_api_getprotect( flash_api_getprotect_t * content ) // [W25Q OK]
 {
     flash_err_t retVal = FLERR_INVALID_PARAMETER;

     Enter();

     {
         if( FLERR_TIMEOUT == flash_api_getready() ) Return(FLERR_TIMEOUT);

         // LL-буфер
         flash_LL_buf_t buf;

         // получаем указатель на буфер драйвера
         // зануляем память (буфер занулен по умолчанию)
         buf.rawptr = W25Q_LL_Routines.service.getBuf( fibufSectorFlags, &buf.size );

         // считываем флаги защиты секторов
         flash_api_sectorprotectregister_read( buf.pSectorProtectionRegister );

         retVal = FLERR_PROTECT_DISABLED;

         if( NULL != content )
         {
             content->protection_enabled = flash_sector_unprotected;
         }

         // проверяем флаги защиты
         for( size_t nSector = 0; nSector < __SECTORS; ++nSector )
         {
             // проверяем биты защиты
             if( FLASH_PROTECTION_REGISTER_CHECK( buf.pSectorProtectionRegister->bSectors, nSector ) )
             {
                 retVal = FLERR_PROTECT_ENABLED;

                 if( NULL != content )
                 {
                    content->protection_enabled = flash_sector_protected;
                    FLASH_PROTECTION_REGISTER_SET( content->sectors.bytes, nSector );
                 }
                 else
                   break; // код @retVal установлен, content=NULL, здесь больше нечего делать
             }
             else
             {
                 if( NULL != content )
                 {
                    FLASH_PROTECTION_REGISTER_CLEAR( content->sectors.bytes, nSector );
                 }
             }
         }
     }

     Return(retVal);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_erase - мультирежимное стирание данных
 // * Параметр @mode задает режим стирания:
 //      mode==fem_byaddress - начальное смещение задается адресом в параметре @start, длинна отрезка - @count в байтах
 //                            При этом минимальный стираемый диапазон - размер страницы.
 //                            Страница, которой принадлежит адрес @start стирается полностью
 //                            Страница, которой принадлежит адрес @start + @count также стирается полность
 //      mode==fem_bypage    - начальное смещение задается номером страницы в параметре @start.
 //                            Количество стираемых страниц задается параметром @count
 //
 //      mode==fem_byblock   - не поддерживается в W25Q. установка этого значения эквивалентна fem_bypage
 //
 //      mode==fem_bysector  - начальное смещение задается номером сектора в параметре @start
 //                            Количество стираемых секторов задается параметром @count.
 //
 //
 // * Параметр @count не может быть 0.
 //
 // ! Функция НЕ проверяет защиту секторов !
 //
 // # Функция возвращает FLERR_SUCCESS если не произошло ошибок.
 // # Функция вернет FLERR_INVALID_PARAMETER если диапазон стирания выходит за границу адресного пространства
 // # Функция вернет FLERR_INVALID_PARAMETER если @count равняется 0.
 // # Функция вернет FLERR_INVALID_PARAMETER если @mode имеет значение, отличное от перечисленных.
 // # Функция вернет FLERR_UNEXPECTED_BUSY если во время операции устройство перестало отвечать
 //
 static flash_err_t flash_api_erase( flash_erase_mode_t mode, __FLASH_DWORD start, __FLASH_DWORD count ) // [W25Q WRONG]
 {
    if( 0 == count ) return(FLERR_INVALID_PARAMETER);

    Enter();

    switch( mode )
    {
       case  fem_byaddress:   // стирание по адресу

             #if W25QFLASH_MINIMUM_ADDRESS > 0
             if( start <  W25QFLASH_MINIMUM_ADDRESS )         Return(FLERR_INVALID_PARAMETER);
             #endif
             if( start + count >  W25QFLASH_MAXIMUM_ADDRESS ) Return(FLERR_INVALID_PARAMETER);

       case  fem_bypage:     // стирание по страницам
       case  fem_byblock:    // стирание по блокам (не поддерживается, эквивалентно fem_bypage)
       {
             __FLASH_DWORD PagesToErase=0;
             __FLASH_DWORD PageBegin   =0;

             if( mode == fem_byaddress )
             {
                 // стирание по адресу
                 PagesToErase = __FLASH_PAGESINRANGE( start , count );
                 PageBegin    = __FLASH_ADDRESS2PAGE( start );
             }
             else
             {
                 // стирание по страницам
                 #if W25QFLASH_MINIMUM_ADDRESS > 0
                 if( start < __FLASH_ADDRESS2PAGE(W25QFLASH_MINIMUM_ADDRESS) ) Return(FLERR_INVALID_PARAMETER);
                 #endif
                 if( start + count > __FLASH_ADDRESS2PAGE(W25QFLASH_MAXIMUM_ADDRESS)  ) Return(FLERR_INVALID_PARAMETER);

                 PagesToErase = count;
                 PageBegin    = start;
             }

             while( PagesToErase )
             {
                    // команда разрешения операций стирания/записи
                    W25Q_LL_Routines.operations.writeEnable();

                    // проверяем, нельзя ли использовать секторное стирание?
                    if( __FLASH_PAGE_ARRAGED_BY_SECTOR ( PageBegin ) &&     // проверяем выравниваение
                      ( PagesToErase >= W25QFLASH_PAGES_PER_SECTOR )  ) // проверяем количество страниц
                    {
                        // можно!

                        // стираем сектор - это быстрее
                        W25Q_LL_Routines.data.sectorErase( __FLASH_PAGE2SECTOR( PageBegin ) );

                        #if W25QXXX_BKGOPERATIONS
                        if( FLERR_TIMEOUT == flash_getready_time( _TIME_SCERS_ms ) )
                        #else
                        if( FLERR_TIMEOUT == flash_api_getready() )
                        #endif
                        {
                            // фатальная ошибка: устройство занято дольше обычного
                            Return(FLERR_UNEXPECTED_BUSY);
                        }

                        PageBegin    += __PAGES_PER_SECTOR;
                        PagesToErase -= __PAGES_PER_SECTOR;
                    }
                    else
                    {
                        // нельзя
                        W25Q_LL_Routines.data.pageErase( PageBegin );

                        #if W25QXXX_BKGOPERATIONS
                        if( FLERR_TIMEOUT == flash_getready_time( _TIME_PGERS_ms ) )
                        #else
                        if( FLERR_TIMEOUT == flash_api_getready() )
                        #endif
                        {
                            // фатальная ошибка: устройство занято дольше обычного
                            Return(FLERR_UNEXPECTED_BUSY);
                        }

                        PageBegin    ++;
                        PagesToErase --;
                    }
             }
       }
       break;

       case  fem_bysector:   // стирание по секторам
       {
             if( start <= __FLASH_ADDRESS2SECTOR(W25QFLASH_MINIMUM_ADDRESS) ) Return(FLERR_INVALID_PARAMETER);
             if( start + count > __FLASH_ADDRESS2SECTOR(W25QFLASH_MAXIMUM_ADDRESS)  ) Return(FLERR_INVALID_PARAMETER);

             while( count )
             {
                  // команда разрешения операций стирания/записи
                  W25Q_LL_Routines.operations.writeEnable();

                  W25Q_LL_Routines.data.sectorErase( start );

                  #if W25QXXX_BKGOPERATIONS
                  if( FLERR_TIMEOUT == flash_getready_time( _TIME_SCERS_ms ) )
                  #else
                  if( FLERR_TIMEOUT == flash_api_getready() )
                  #endif
                  {
                      // фатальная ошибка: устройство занято дольше обычного
                      Return(FLERR_UNEXPECTED_BUSY);
                  }

                  start ++;
                  count --;
             }
       }
       break;

    default: Return(FLERR_INVALID_PARAMETER);
    }

    // успех.
    Return(FLERR_SUCCESS);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_write - мультирежимная запись данных по адресу
 //
 // * Параметр @address - указывает линейный адрес в адресном пространстве чипа.
 // * Параметр @pBuffer - должен указывать на буфер-источник данных для записи. Не может быть NULL.
 // * Параметр @size - должен указывать на размер данных на запись. Не может быть равен 0.
 // * Параметр @mode - задает режим записи. Доступны следующий режимы:
 //   - fwm_safewrite - режим безопасной записи. Медленный режим, когда запись/стирание происходит
 //                     постранично с верификацией. В случае ошибки верификации запись прекращается,
 //                     остальная (незаписанная) часть данных не переписывается/не стрирается,
 //                     при этом функция возвращает ошибку FLERR_VERIFY_ERROR
 //
 //   - fwm_fastwrite - режим быстрой записи. Стирание происходит по блокам (8 станиц, см. заголовочный файл)
 //                     Запись постраничная. Верификация данных не проводится.
 //
 //   - fwm_compatible  НЕ ПОДДЕРЖИВАЕТСЯ, возвращается ошибка FLERR_INVALID_PARAMETER
 //
 //
 // # Функция возвращает FLERR_SUCCESS, если не произшло никаких ошибок.
 // # Функция возвращает FLERR_UNEXPECTED_BUSY, если во время записи произошел отказ устройства.
 // # Функция возвращает FLERR_VERIFY_ERROR, если обнаружена ошибка верификации данных.
 // # Функция возвращает FLERR_INVALID_PARAMETER, если указан неверный параметр:
 //   - @address указывает на зарезервированную область, либо область записи выходит за границу адресного пространства;
 //   - @pBuffer или @size равны 0;
 //   - @mode имеет значение, отличное от перечисленных
 //
 // # Для быстрой работы требует включения режима W25QXXX_BKGOPERATIONS
 // Функция использует две различные монорежимные функции записи:
 //----------
 static flash_err_t flash_api_buffer_programm( flash_LL_buf_t * buf, __FLASH_DWORD page );
 //----------
 static flash_err_t flash_api_write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size, flash_write_mode_t mode ) // [W25Q OK]
 {
      #if W25QFLASH_MINIMUM_ADDRESS > 0
      if( address < W25QFLASH_MINIMUM_ADDRESS )         return(FLERR_INVALID_PARAMETER);
      #endif
      if( !size || !pBuffer )                           return(FLERR_INVALID_PARAMETER);
      if( address + size > W25QFLASH_MAXIMUM_ADDRESS )  return(FLERR_INVALID_PARAMETER);

      bool verify = false;;

      switch( mode )
      {
        case fwm_safewrite:  //режим безопасной записи.
        // > Данные страницы кешируются в низкоуровневом программном буфере
        // > Производится модификация данных в буфере (запись в буфер со смещением)
        // > Используется команда стирание страницы
        // > Используется команда запись страницы
        // > Производится чтение данных по смещению в буфер
        // > Производится данных буфера и переданных данных
        // > Если данные совпадают - запись без ошибок
        /*   ______________________
            /                      \
            | Slow  Moderate  Fast |
            |    \     |      /    |
            |      \               |
            |       \              |
            |        \             |
            |       __\____        |
             \_____________________/

        */
        verify = true;
        break;

        case fwm_fastwrite:  //режим быстрой записи.
        // > Данные страницы кешируются в низкоуровневом программном буфере
        // > Производится модификация данных в буфере (запись в буфер со смещением)
        // > Используется команда стирание страницы/сектора
        // > Постранично записываем данные в чип
        // > Готово.
        /*   ______________________
            /                      \
            | Slow  Moderate  Fast |
            |    \      |     /    |
            |               /      |
            |              /       |
            |             /        |
            |       _____/_    [!] |
             \_____________________/

        */
        break;

        default: return(FLERR_INVALID_PARAMETER);
      }

      // > Данные страницы кешируются в низкоуровневом программном буфере
      // > Производится модификация данных в буфере (запись в буфер со смещением)
      // > Используется команда стирание страницы
      // > Используется команда запись страницы
      // > Если включена верификация {
      // >>> Производится чтение данных по смещению в буфер
      // >>> Производится данных буфера и переданных данных
      // > }
      // > Если данные совпадают - запись без ошибок

        __FLASH_DWORD pageBegin    = __FLASH_ADDRESS2PAGE( address );           // вычисляем номер первой страницы
        __FLASH_DWORD PagesToWrite = __FLASH_PAGESINRANGE( address, size );     // вычисляем количество затрагиваемых страниц
        __FLASH_DWORD BytesToWrite = __FLASH_DATAREMAININGINPAGE( address );    // вычисляем для первой страницы

        Enter();

        flash_LL_buf_t buf;

        // запрос буфера драйвера LL
        buf.rawptr = W25Q_LL_Routines.service.getBuf( fibufPageIO, &buf.size );

        do
        {
            if( size < BytesToWrite ) BytesToWrite = size;

         //---------------------
         // ПЕРВАЯ и ПОСЛЕДНЯЯ страницы могут записываться частично: требуется предварительное чтение
            if( __FLASH_ADDRESSOFFSETPAGE( address )  || PagesToWrite<=1 )
            {
                // буферизация страницы в программном буфере
                W25Q_LL_Routines.data.arrayRead( address - __FLASH_ADDRESSOFFSETPAGE(address),
                                                 buf.pageBuffer,
                                                 buf.size );

                // Производится модификация данных в буфере (запись в буфер со смещением)
                __imp_sys_memcpy( buf.pageBuffer + __FLASH_ADDRESSOFFSETPAGE( address ),
                                  pBuffer,
                                  BytesToWrite );
            }
         //---------------------
            // идет запись: разрешение стирания страницы
            W25Q_LL_Routines.operations.writeEnable();

            // идет запись: СТИРАНИЕ
            W25Q_LL_Routines.data.pageErase( pageBegin );

            if( FLASH_ERROR( W25Q_LL_Routines.ready.smartWaitms( _TIME_PGERS_ms ) ) )
              Return(FLERR_UNEXPECTED_BUSY);

            // идет запись: ПРОГРАММИРОВАНИЕ
            if( FLASH_ERROR( flash_api_buffer_programm( &buf, pageBegin ) ) )
             Return(FLERR_UNEXPECTED_BUSY);
         //---------------------
            if( verify )
            {
              // чтение записанного куска в буфер
              W25Q_LL_Routines.data.arrayRead( address,
                                               buf.pageBuffer,
                                               BytesToWrite );
              //---------------------
              // Производится сравнение записанной страницы с буфером
              if( 0 != __imp_sys_memcmp( buf.pageBuffer, pBuffer, BytesToWrite ) )
               Return(FLERR_VERIFY_ERROR);
            }
            //---------------------
            pageBegin++;
            address += BytesToWrite;
            pBuffer += BytesToWrite;
            size    -= BytesToWrite;

            BytesToWrite = __FLASH_DATAREMAININGINPAGE( address );
        }
        while( --PagesToWrite );

    Return(FLERR_SUCCESS);
 }

 //----------

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_read - чтение данных по адресу
 //
 // * Функция проверяет правильность переданных параметров
 // * Параметр @address задает линейный адрес внутри адресного пространства устройства.
 //   Параметр @pvBuffer должен указывать на буфер-приемник для прочитанных данных.
 //   Параметр @size должен указывать на размер буфер-приемника. Параметр показывает
 //    количество байт для чтения в буфер.
 // # Если параметр @address указывает на адрес в диапазоне 0...W25QFLASH_MINIMUM_ADDRESS, функция вернет ошибку FLERR_INVALID_PARAMETER
 // # Если параметр @pvBuffer равен NULL, функция вернет ошибку FLERR_INVALID_PARAMETER
 // # Если параметр @size равен 0, функция вернет ошибку FLERR_INVALID_PARAMETER

 static flash_err_t flash_api_read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size ) // [W25Q OK]
 {
      // проверяем входные параметры
      #if W25QFLASH_MINIMUM_ADDRESS > 0
      if( address < W25QFLASH_MINIMUM_ADDRESS ) return(FLERR_INVALID_PARAMETER);
      #endif
      if( NULL == pBuffer || 0 == size )        return(FLERR_INVALID_PARAMETER);
      if( address + size >  __FULL_CHIP_SIZE )  return(FLERR_INVALID_PARAMETER);

      Enter();

      // проверям готовность устройства
      if( FLERR_TIMEOUT == flash_api_getready() ) Return(FLERR_TIMEOUT);

      W25Q_LL_Routines.data.arrayRead( address, pBuffer, size );

      Return(FLERR_SUCCESS);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_sleepmode - переход в режим пониженного энергопотребления
 // # переводит устройство в режим, когда все команды, кроме flash_api_wakeup игнорируются
 static flash_err_t flash_api_sleepmode() // [W25Q OK]
 {
    Enter();

    W25Q_LL_Routines.sleep.sleepMode();

    Return(FLERR_SUCCESS);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_wakeup - выход из режима пониженного энергопотребления
 // # выводит устройство из режима, когда все команды, кроме flash_api_wakeup игнорируются
 static flash_err_t flash_api_wakeup() // [W25Q OK]
 {
    Enter();

    W25Q_LL_Routines.sleep.wakeUp();

    if( W25Q_LL_Routines.ready.smartWaitms( W25QXXX_API_TIMEOUT )  )
    {
         Return(FLERR_SUCCESS);
    }

    Return(FLERR_TIMEOUT);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№

 /*
 // --------------------------------------------------------------------------------------------------------
 // __flash_hal__protectregister_write - записывает регистр защиты (биты индивидуальной защиты секторов/блоков)
 //
 // * потоко-небезопасная функция
 // * параметр @contents не может быть NULL - укажите содержимое регистра защиты (см. __flash_protectionregister_t)
 // * задерживает выполнение на ДЕСЯТКИ МИЛЛИСЕКУНД
 static void __flash_hal__protectregister_write( __flash_protectionregister_t * contents  // содержимое регистра защиты для записи
                                        ) // [W25Q WRONG]
 {
      if( !contents ) return;

      for( size_t nProtSector = 0; nProtSector < __PROT_SECTOR_FLAGS; ++nProtSector )
      {
           __flash_hal__protectStateWrite( nProtSector, contents->Sectors[ nProtSector ] );
      }
 }
 // --------------------------------------------------------------------------------------------------------
 */
 /*
 // --------------------------------------------------------------------------------------------------------
 // __flash_hal__protectregister_read - прочитывает регистр защиты (биты индивидуальной защиты секторов/блоков)
 //
 // * потоко-небезопасная функция
 // * параметр @contents не может быть NULL - укажите буфер для содержимого регистра защиты (см. __flash_protectionregister_t)
 static void __flash_hal__protectregister_read( __flash_protectionregister_t * contents // буфер-приемник содержимого регистра защиты
                                       ) // [W25Q WRONG]
 {
      if( !contents ) return;

      for( size_t nProtSector = 0; nProtSector < __PROT_SECTOR_FLAGS; ++nProtSector )
      {
           contents->Sectors[ nProtSector ] = __flash_hal__protectStateRead( nProtSector );
      }
 }
 // --------------------------------------------------------------------------------------------------------
 */
 // --------------------------------------------------------------------------------------------------------
 // flash_api_sectorprotectregister_write - записывает регистр защиты (биты индивидуальной защиты СЕКТОРОВ 64К)
 //
 // * потоко-небезопасная функция
 // * параметр @contents не может быть NULL - укажите содержимое регистра защиты (см. __flash_sectorprotectionregister_t)
 // * задерживает выполнение на ДЕСЯТКИ МИЛЛИСЕКУНД
 static flash_err_t flash_api_sectorprotectregister_write( __flash_sectorprotectionregister_t * contents  // содержимое регистра защиты для записи
                                        ) // [W25Q OK]
 {
      if( NULL == contents ) return FLERR_INVALID_PARAMETER;

      // цикл по секторам 64КБ
      for( size_t nSector = 0; nSector < __SECTORS; ++nSector )
      {
          size_t nProtSectorCount = 0;
          size_t nProtSectorStart = __SECTOR_TO_PROTSECTOR(nSector, nProtSectorCount);

          // цикл по индивидуальным битам блоков/секторов внутри сектора 64КБ
          for( size_t nProtSector = nProtSectorStart; nProtSector < nProtSectorStart + nProtSectorCount; ++nProtSector )
          {
               bool state = FLASH_PROTECTION_REGISTER_CHECK( contents->bSectors, nSector );
               W25Q_LL_Routines.operations.writeEnable();
               W25Q_LL_Routines.protection.protectStateWrite( nProtSector, state );

               // проверяем готовность устройства
               #if W25QXXX_BKGOPERATIONS
               if( FLERR_TIMEOUT == flash_getready_time( _TIME_PGPRG_ms ) )
               #else
               if( FLERR_TIMEOUT == flash_api_getready() )
               #endif
               return FLERR_TIMEOUT;
          }
      }

      return FLERR_SUCCESS;
 }
 // --------------------------------------------------------------------------------------------------------

 // --------------------------------------------------------------------------------------------------------
 // flash_api_sectorprotectregister_read - прочитывает регистр защиты (биты индивидуальной защиты СЕКТОРОВ 64К)
 //
 // * потоко-небезопасная функция
 // * параметр @contents не может быть NULL - укажите буфер для содержимого регистра защиты (см. __flash_sectorprotectionregister_t)
 static flash_err_t flash_api_sectorprotectregister_read( __flash_sectorprotectionregister_t * contents // буфер-приемник содержимого регистра защиты
                                       ) // [W25Q OK]
 {
      if( NULL == contents ) return FLERR_INVALID_PARAMETER;

      // цикл по секторам 64КБ
      for( size_t nSector = 0; nSector < __SECTORS; ++nSector )
      {
          size_t nProtSectorCount = 0;
          size_t nProtSectorStart = __SECTOR_TO_PROTSECTOR(nSector, nProtSectorCount);

          bool state = false;

          // цикл по индивидуальным битам блоков/секторов внутри сектора 64КБ
          for( size_t nProtSector = nProtSectorStart; nProtSector < nProtSectorStart + nProtSectorCount; ++nProtSector )
          {
               if( W25Q_LL_Routines.protection.protectStateRead(( nProtSector ) ) )
               {
                   state = true; // как минимум один блок особенного сектора защищен
                   break;
               }
          }

          if( state )
            FLASH_PROTECTION_REGISTER_SET(contents->bSectors, nSector);
          else
            FLASH_PROTECTION_REGISTER_CLEAR(contents->bSectors, nSector);
      }

      return FLERR_SUCCESS;
 }
 // --------------------------------------------------------------------------------------------------------
 // flash_api_writeprotection_disable - снимает защиту от записи во всех секторах
 //
 // * потоко-небезопасная функция
 static flash_err_t flash_api_writeprotection_disable() // [W25Q OK]
 {
    // разрешаем опрерации записи/стирания
    W25Q_LL_Routines.operations.writeEnable();

    // команда снятия защиты со всех секторов/блоков
    W25Q_LL_Routines.protection.globalUnlock();

    #if W25QXXX_BKGOPERATIONS // Global Unlock требует ожидания?
    if( FLERR_TIMEOUT == flash_getready_time( _TIME_PGPRG_ms ) )
    #else
    if( FLERR_TIMEOUT == flash_api_getready() )
    #endif
    return FLERR_TIMEOUT;

    flash_LL_buf_t buf;

    // запрос буфера драйвера LL
    buf.rawptr = W25Q_LL_Routines.service.getBuf( fibufSectorFlags, &buf.size );

    // проверка буфера
    if( NULL == buf.pSectorProtectionRegister ||
        buf.size < sizeof(__flash_sectorprotectionregister_t) )
    {
        Return(FLERR_INVALID_BUFFER);
    }

    // чтение флагов защиты секторов
    flash_api_sectorprotectregister_read( buf.pSectorProtectionRegister );

    // проверка флагов всех секторов
    for( size_t nSector = 0; nSector < __SECTORS; ++nSector )
    {
        if( FLASH_PROTECTION_REGISTER_CHECK( buf.pSectorProtectionRegister->bSectors, nSector ) )
        {
            // найден защищенный сектор, хотя такого быть не должно: ошибка выполнения
            Return(FLERR_GENERIC_ERROR);
        }
    }

    // теперь защита от записи снята для всех секторов. команда выполнена
    Return(FLERR_SUCCESS);
 }
 // --------------------------------------------------------------------------------------------------------
 // flash_api_buffer_programm - запись страничного буфера (4K)
 // * внутренняя функция
 // * не стирает страницу перед записью буфера
 // * не проверяет переданные параметры
 // * потоко-небезопасная функция
 // # Возвращает либо FLERR_SUCCESS если ошибок не произошло, либо FLERR_UNEXPECTED_BUSY
 // если чип непредвиденно занят до или после операции записи
 static flash_err_t flash_api_buffer_programm( flash_LL_buf_t * buf, __FLASH_DWORD page )
 {
    __flash_pageprogramptr_t chunkBuffer = (__flash_pageprogramptr_t)buf->rawptr;
    __FLASH_DWORD w24Address = __FLASH_PAGE2ADDRESS(page);

    // проверка занятости
    if( FLASH_ERROR( W25Q_LL_Routines.ready.getReadyFast() ) )  Return(FLERR_UNEXPECTED_BUSY);

    for( __FLASH_DWORD byteWritten = 0; byteWritten < buf->size; )
    {
        // разрешаем опрерации записи/стирания
        W25Q_LL_Routines.operations.writeEnable();

        // производится запись
        W25Q_LL_Routines.data.chunkProgram( chunkBuffer, w24Address );

        #if W25QXXX_BKGOPERATIONS
        if( FLASH_ERROR( W25Q_LL_Routines.ready.smartWaitms( _TIME_PGPRG_ms ) ) ) Return(FLERR_UNEXPECTED_BUSY);
        #else
        if( FLASH_ERROR( W25Q_LL_Routines.ready.getReadyFast() ) )                Return(FLERR_UNEXPECTED_BUSY);
        #endif

        w24Address  += sizeof(*chunkBuffer);
        byteWritten += sizeof(*chunkBuffer);
        chunkBuffer ++; // переход к следующему куску (chunk, 256bytes)
    }

    return FLERR_SUCCESS;
 }
