| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623 |
- // Файл с 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;
- }
|