Explorar el Código

Add spi_mem_manager from https://github.com/xMasterX/all-the-plugins

git-subtree-dir: spi_mem_manager
git-subtree-mainline: ffe4ad5be571c03a5dff5e9759b24465ce143179
git-subtree-split: 7de6ad426417a22cc459aa2d0707c0ce39388f26
Willy-JL hace 2 años
padre
commit
042fc95bd2
Se han modificado 59 ficheros con 5147 adiciones y 0 borrados
  1. 1 0
      spi_mem_manager/.gitsubtree
  2. 18 0
      spi_mem_manager/application.fam
  3. BIN
      spi_mem_manager/images/ChipLooking_64x64/frame_01.png
  4. BIN
      spi_mem_manager/images/ChipLooking_64x64/frame_02.png
  5. BIN
      spi_mem_manager/images/ChipLooking_64x64/frame_03.png
  6. 1 0
      spi_mem_manager/images/ChipLooking_64x64/frame_rate
  7. BIN
      spi_mem_manager/images/Dip8_10px.png
  8. BIN
      spi_mem_manager/images/Dip8_32x36.png
  9. BIN
      spi_mem_manager/images/DolphinMafia_115x62.png
  10. BIN
      spi_mem_manager/images/DolphinNice_96x59.png
  11. BIN
      spi_mem_manager/images/SDQuestion_35x43.png
  12. BIN
      spi_mem_manager/images/Wiring_SPI_128x64.png
  13. 105 0
      spi_mem_manager/lib/spi/spi_mem_chip.c
  14. 34 0
      spi_mem_manager/lib/spi/spi_mem_chip.h
  15. 1399 0
      spi_mem_manager/lib/spi/spi_mem_chip_arr.c
  16. 85 0
      spi_mem_manager/lib/spi/spi_mem_chip_i.h
  17. 152 0
      spi_mem_manager/lib/spi/spi_mem_tools.c
  18. 14 0
      spi_mem_manager/lib/spi/spi_mem_tools.h
  19. 129 0
      spi_mem_manager/lib/spi/spi_mem_worker.c
  20. 54 0
      spi_mem_manager/lib/spi/spi_mem_worker.h
  21. 24 0
      spi_mem_manager/lib/spi/spi_mem_worker_i.h
  22. 214 0
      spi_mem_manager/lib/spi/spi_mem_worker_modes.c
  23. 30 0
      spi_mem_manager/scenes/spi_mem_scene.c
  24. 29 0
      spi_mem_manager/scenes/spi_mem_scene.h
  25. 42 0
      spi_mem_manager/scenes/spi_mem_scene_about.c
  26. 37 0
      spi_mem_manager/scenes/spi_mem_scene_chip_detect.c
  27. 57 0
      spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c
  28. 94 0
      spi_mem_manager/scenes/spi_mem_scene_chip_detected.c
  29. 52 0
      spi_mem_manager/scenes/spi_mem_scene_chip_error.c
  30. 21 0
      spi_mem_manager/scenes/spi_mem_scene_config.h
  31. 62 0
      spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c
  32. 65 0
      spi_mem_manager/scenes/spi_mem_scene_erase.c
  33. 29 0
      spi_mem_manager/scenes/spi_mem_scene_file_info.c
  34. 57 0
      spi_mem_manager/scenes/spi_mem_scene_read.c
  35. 46 0
      spi_mem_manager/scenes/spi_mem_scene_read_filename.c
  36. 76 0
      spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c
  37. 22 0
      spi_mem_manager/scenes/spi_mem_scene_select_file.c
  38. 45 0
      spi_mem_manager/scenes/spi_mem_scene_select_model.c
  39. 70 0
      spi_mem_manager/scenes/spi_mem_scene_select_vendor.c
  40. 84 0
      spi_mem_manager/scenes/spi_mem_scene_start.c
  41. 56 0
      spi_mem_manager/scenes/spi_mem_scene_storage_error.c
  42. 40 0
      spi_mem_manager/scenes/spi_mem_scene_success.c
  43. 59 0
      spi_mem_manager/scenes/spi_mem_scene_verify.c
  44. 43 0
      spi_mem_manager/scenes/spi_mem_scene_verify_error.c
  45. 18 0
      spi_mem_manager/scenes/spi_mem_scene_wiring.c
  46. 58 0
      spi_mem_manager/scenes/spi_mem_scene_write.c
  47. 112 0
      spi_mem_manager/spi_mem_app.c
  48. 3 0
      spi_mem_manager/spi_mem_app.h
  49. 78 0
      spi_mem_manager/spi_mem_app_i.h
  50. 68 0
      spi_mem_manager/spi_mem_files.c
  51. 13 0
      spi_mem_manager/spi_mem_files.h
  52. 7 0
      spi_mem_manager/tools/README.md
  53. 22 0
      spi_mem_manager/tools/chiplist/LICENSE
  54. 984 0
      spi_mem_manager/tools/chiplist/chiplist.xml
  55. 109 0
      spi_mem_manager/tools/chiplist_convert.py
  56. 64 0
      spi_mem_manager/views/spi_mem_view_detect.c
  57. 9 0
      spi_mem_manager/views/spi_mem_view_detect.h
  58. 230 0
      spi_mem_manager/views/spi_mem_view_progress.c
  59. 26 0
      spi_mem_manager/views/spi_mem_view_progress.h

+ 1 - 0
spi_mem_manager/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/xMasterX/all-the-plugins dev base_pack/spi_mem_manager

+ 18 - 0
spi_mem_manager/application.fam

@@ -0,0 +1,18 @@
+App(
+    appid="spi_mem_manager",
+    name="SPI Mem Manager",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="spi_mem_app",
+    requires=["gui"],
+    stack_size=1 * 2048,
+    fap_description="Application for reading and writing 25-series SPI memory chips",
+    fap_version="1.0",
+    fap_icon="images/Dip8_10px.png",
+    fap_category="GPIO",
+    fap_icon_assets="images",
+    fap_private_libs=[
+        Lib(
+            name="spi",
+        ),
+    ],
+)

BIN
spi_mem_manager/images/ChipLooking_64x64/frame_01.png


BIN
spi_mem_manager/images/ChipLooking_64x64/frame_02.png


BIN
spi_mem_manager/images/ChipLooking_64x64/frame_03.png


+ 1 - 0
spi_mem_manager/images/ChipLooking_64x64/frame_rate

@@ -0,0 +1 @@
+2

BIN
spi_mem_manager/images/Dip8_10px.png


BIN
spi_mem_manager/images/Dip8_32x36.png


BIN
spi_mem_manager/images/DolphinMafia_115x62.png


BIN
spi_mem_manager/images/DolphinNice_96x59.png


BIN
spi_mem_manager/images/SDQuestion_35x43.png


BIN
spi_mem_manager/images/Wiring_SPI_128x64.png


+ 105 - 0
spi_mem_manager/lib/spi/spi_mem_chip.c

@@ -0,0 +1,105 @@
+#include "spi_mem_chip_i.h"
+
+const SPIMemChipVendorName spi_mem_chip_vendor_names[] = {
+    {"Adesto", SPIMemChipVendorADESTO},
+    {"AMIC", SPIMemChipVendorAMIC},
+    {"Boya", SPIMemChipVendorBoya},
+    {"EON", SPIMemChipVendorEON},
+    {"PFlash", SPIMemChipVendorPFLASH},
+    {"Terra", SPIMemChipVendorTERRA},
+    {"Generalplus", SPIMemChipVendorGeneralplus},
+    {"Deutron", SPIMemChipVendorDEUTRON},
+    {"EFST", SPIMemChipVendorEFST},
+    {"Excel Semi.", SPIMemChipVendorEXCELSEMI},
+    {"Fidelix", SPIMemChipVendorFIDELIX},
+    {"GigaDevice", SPIMemChipVendorGIGADEVICE},
+    {"ICE", SPIMemChipVendorICE},
+    {"Intel", SPIMemChipVendorINTEL},
+    {"KHIC", SPIMemChipVendorKHIC},
+    {"Macronix", SPIMemChipVendorMACRONIX},
+    {"Micron", SPIMemChipVendorMICRON},
+    {"Mshine", SPIMemChipVendorMSHINE},
+    {"Nantronics", SPIMemChipVendorNANTRONICS},
+    {"Nexflash", SPIMemChipVendorNEXFLASH},
+    {"Numonyx", SPIMemChipVendorNUMONYX},
+    {"PCT", SPIMemChipVendorPCT},
+    {"Spansion", SPIMemChipVendorSPANSION},
+    {"SST", SPIMemChipVendorSST},
+    {"ST", SPIMemChipVendorST},
+    {"Winbond", SPIMemChipVendorWINBOND},
+    {"Zempro", SPIMemChipVendorZEMPRO},
+    {"Zbit", SPIMemChipVendorZbit},
+    {"Berg Micro.", SPIMemChipVendorBerg_Micro},
+    {"Atmel", SPIMemChipVendorATMEL},
+    {"ACE", SPIMemChipVendorACE},
+    {"ATO", SPIMemChipVendorATO},
+    {"Douqi", SPIMemChipVendorDOUQI},
+    {"Fremont", SPIMemChipVendorFremont},
+    {"Fudan", SPIMemChipVendorFudan},
+    {"Genitop", SPIMemChipVendorGenitop},
+    {"Paragon", SPIMemChipVendorParagon},
+    {"Unknown", SPIMemChipVendorUnknown}};
+
+static const char* spi_mem_chip_search_vendor_name(SPIMemChipVendor vendor_enum) {
+    const SPIMemChipVendorName* vendor = spi_mem_chip_vendor_names;
+    while(vendor->vendor_enum != SPIMemChipVendorUnknown && vendor->vendor_enum != vendor_enum)
+        vendor++;
+    return vendor->vendor_name;
+}
+
+bool spi_mem_chip_find_all(SPIMemChip* chip_info, found_chips_t found_chips) {
+    const SPIMemChip* chip_info_arr;
+    found_chips_reset(found_chips);
+    for(chip_info_arr = SPIMemChips; chip_info_arr->model_name != NULL; chip_info_arr++) {
+        if(chip_info->vendor_id != chip_info_arr->vendor_id) continue;
+        if(chip_info->type_id != chip_info_arr->type_id) continue;
+        if(chip_info->capacity_id != chip_info_arr->capacity_id) continue;
+        found_chips_push_back(found_chips, chip_info_arr);
+    }
+    if(found_chips_size(found_chips)) return true;
+    return false;
+}
+
+void spi_mem_chip_copy_chip_info(SPIMemChip* dest, const SPIMemChip* src) {
+    memcpy(dest, src, sizeof(SPIMemChip));
+}
+
+size_t spi_mem_chip_get_size(SPIMemChip* chip) {
+    return (chip->size);
+}
+
+const char* spi_mem_chip_get_vendor_name(const SPIMemChip* chip) {
+    return (spi_mem_chip_search_vendor_name(chip->vendor_enum));
+}
+
+const char* spi_mem_chip_get_vendor_name_by_enum(uint32_t vendor_enum) {
+    return (spi_mem_chip_search_vendor_name(vendor_enum));
+}
+
+const char* spi_mem_chip_get_model_name(const SPIMemChip* chip) {
+    return (chip->model_name);
+}
+
+uint8_t spi_mem_chip_get_vendor_id(SPIMemChip* chip) {
+    return (chip->vendor_id);
+}
+
+uint8_t spi_mem_chip_get_type_id(SPIMemChip* chip) {
+    return (chip->type_id);
+}
+
+uint8_t spi_mem_chip_get_capacity_id(SPIMemChip* chip) {
+    return (chip->capacity_id);
+}
+
+SPIMemChipWriteMode spi_mem_chip_get_write_mode(SPIMemChip* chip) {
+    return (chip->write_mode);
+}
+
+size_t spi_mem_chip_get_page_size(SPIMemChip* chip) {
+    return (chip->page_size);
+}
+
+uint32_t spi_mem_chip_get_vendor_enum(const SPIMemChip* chip) {
+    return ((uint32_t)chip->vendor_enum);
+}

+ 34 - 0
spi_mem_manager/lib/spi/spi_mem_chip.h

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <furi.h>
+#include <m-array.h>
+
+typedef struct SPIMemChip SPIMemChip;
+
+ARRAY_DEF(found_chips, const SPIMemChip*, M_POD_OPLIST)
+
+typedef enum {
+    SPIMemChipStatusBusy,
+    SPIMemChipStatusIdle,
+    SPIMemChipStatusError
+} SPIMemChipStatus;
+
+typedef enum {
+    SPIMemChipWriteModeUnknown = 0,
+    SPIMemChipWriteModePage = (0x01 << 0),
+    SPIMemChipWriteModeAAIByte = (0x01 << 1),
+    SPIMemChipWriteModeAAIWord = (0x01 << 2),
+} SPIMemChipWriteMode;
+
+const char* spi_mem_chip_get_vendor_name(const SPIMemChip* chip);
+const char* spi_mem_chip_get_model_name(const SPIMemChip* chip);
+size_t spi_mem_chip_get_size(SPIMemChip* chip);
+uint8_t spi_mem_chip_get_vendor_id(SPIMemChip* chip);
+uint8_t spi_mem_chip_get_type_id(SPIMemChip* chip);
+uint8_t spi_mem_chip_get_capacity_id(SPIMemChip* chip);
+SPIMemChipWriteMode spi_mem_chip_get_write_mode(SPIMemChip* chip);
+size_t spi_mem_chip_get_page_size(SPIMemChip* chip);
+bool spi_mem_chip_find_all(SPIMemChip* chip_info, found_chips_t found_chips);
+void spi_mem_chip_copy_chip_info(SPIMemChip* dest, const SPIMemChip* src);
+uint32_t spi_mem_chip_get_vendor_enum(const SPIMemChip* chip);
+const char* spi_mem_chip_get_vendor_name_by_enum(uint32_t vendor_enum);

+ 1399 - 0
spi_mem_manager/lib/spi/spi_mem_chip_arr.c

@@ -0,0 +1,1399 @@
+#include "spi_mem_chip_i.h"
+const SPIMemChip SPIMemChips[] = {
+    {0x1F, 0x40, 0x00, "AT25DN256", 32768, 256, SPIMemChipVendorADESTO, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x20, "A25L05PT", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x10, "A25L05PU", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x21, "A25L10PT", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x11, "A25L10PU", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x22, "A25L20PT", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x12, "A25L20PU", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x23, "A25L40PT", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x13, "A25L40PU", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x24, "A25L80PT", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x14, "A25L80PU", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x25, "A25L16PT", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x15, "A25L16PU", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x10, "A25L512", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x11, "A25L010", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x12, "A25L020", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x13, "A25L040", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x14, "A25L080", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x15, "A25L016", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x16, "A25L032", 4194304, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x40, 0x15, "A25LQ16", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x37, 0x40, 0x16, "A25LQ32A", 4194304, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage},
+    {0x68, 0x40, 0x14, "BY25D80", 1048576, 256, SPIMemChipVendorBoya, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x10, "EN25B05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x10, "EN25B05T", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x11, "EN25B10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x11, "EN25B10T", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x12, "EN25B20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x12, "EN25B20T", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x13, "EN25B40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x13, "EN25B40T", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x14, "EN25B80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x14, "EN25B80T", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x15, "EN25B16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x15, "EN25B16T", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x16, "EN25B32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x16, "EN25B32T", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x17, "EN25B64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x17, "EN25B64T", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x10, "EN25F05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x11, "EN25F10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x12, "EN25F20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x13, "EN25F40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x14, "EN25F80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x15, "EN25F16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x16, "EN25F32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x10, "EN25LF05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x11, "EN25LF10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x12, "EN25LF20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x13, "EN25LF40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x10, "EN25P05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x11, "EN25P10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x12, "EN25P20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x13, "EN25P40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x14, "EN25P80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x15, "EN25P16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x16, "EN25P32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x17, "EN25P64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x30, 0x13, "EN25Q40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x30, 0x14, "EN25Q80A", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x30, 0x15, "EN25Q16A", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x30, 0x16, "EN25Q32A", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x70, 0x16, "EN25Q32A", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x30, 0x16, "EN25Q32B", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x30, 0x17, "EN25Q64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x30, 0x18, "EN25Q128", 16777216, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x70, 0x15, "EN25QH16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x70, 0x16, "EN25QH32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x70, 0x17, "EN25QH64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x70, 0x18, "EN25QH128", 16777216, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x70, 0x19, "EN25QH256", 33554432, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x51, 0x14, "EN25T80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x51, 0x15, "EN25T16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x1C, 0x31, 0x17, "EN25F64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage},
+    {0x7F, 0x9D, 0x7C, "Pm25LV010", 131072, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage},
+    {0x7F, 0x9D, 0x21, "Pm25LD010", 131072, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage},
+    {0x7F, 0x9D, 0x22, "Pm25LV020", 262144, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage},
+    {0x7F, 0x9D, 0x7D, "Pm25W020", 262144, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage},
+    {0x7F, 0x9D, 0x7E, "Pm25LV040", 524288, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x10, "TS25L512A", 65536, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x11, "TS25L010A", 131072, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x12, "TS25L020A", 262144, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x15, "TS25L16AP", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x15, "TS25L16BP", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x15, "ZP25L16P", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x20, 0x80, 0x15, "TS25L16PE", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x20, 0x80, 0x14, "TS25L80PE", 1048576, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x16, "TS25L032A", 4194304, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x13, "TS25L40P", 524288, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x10,
+     "GPR25L005E",
+     65536,
+     256,
+     SPIMemChipVendorGeneralplus,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x15,
+     "GPR25L161B",
+     262144,
+     256,
+     SPIMemChipVendorGeneralplus,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x12,
+     "GPR25L020B",
+     262144,
+     256,
+     SPIMemChipVendorGeneralplus,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "GPR25L3203F",
+     4194304,
+     256,
+     SPIMemChipVendorGeneralplus,
+     SPIMemChipWriteModePage},
+    {0x9D, 0x7B, 0x00, "AC25LV512", 65536, 256, SPIMemChipVendorDEUTRON, SPIMemChipWriteModePage},
+    {0x9D, 0x7C, 0x00, "AC25LV010", 131072, 256, SPIMemChipVendorDEUTRON, SPIMemChipWriteModePage},
+    {0x9D, 0x7B, 0x00, "EM25LV512", 65536, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x9D, 0x7C, 0x00, "EM25LV010", 131072, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x20, 0x13, "F25L004A", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x20, 0x14, "F25L008A", 1048576, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x20, 0x15, "F25L016A", 2097152, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x8C, 0x8C, "F25L04UA", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x20, 0x13, "F25L04P", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x30, 0x13, "F25S04P", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x20, 0x14, "F25L08P", 1048576, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x20, 0x15, "F25L16P", 2097152, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x20, 0x16, "F25L32P", 4194304, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x8C, 0x40, 0x16, "F25L32Q", 4194304, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage},
+    {0x4A, 0x20, 0x11, "ES25P10", 131072, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x20, 0x12, "ES25P20", 262144, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x20, 0x13, "ES25P40", 524288, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x20, 0x14, "ES25P80", 1048576, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x20, 0x15, "ES25P16", 2097152, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x20, 0x16, "ES25P32", 4194304, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x32, 0x13, "ES25M40A", 524288, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x32, 0x14, "ES25M80A", 1048576, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0x4A, 0x32, 0x15, "ES25M16A", 2097152, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage},
+    {0xF8, 0x32, 0x14, "FM25Q08A", 1048576, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage},
+    {0xF8, 0x32, 0x15, "FM25Q16A", 2097152, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage},
+    {0xF8, 0x32, 0x15, "FM25Q16B", 2097152, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage},
+    {0xF8, 0x32, 0x16, "FM25Q32A", 4194304, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage},
+    {0xF8, 0x32, 0x17, "FM25Q64A", 8388608, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage},
+    {0xC8, 0x30, 0x13, "GD25D40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8, 0x30, 0x14, "GD25D80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8, 0x20, 0x13, "GD25F40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8, 0x20, 0x14, "GD25F80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x10, "GD25Q512", 65536, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x11, "GD25Q10", 131072, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x12, "GD25Q20", 262144, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8,
+     0x60,
+     0x12,
+     "GD25LQ20C_1.8V",
+     262144,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x13, "GD25Q40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x14, "GD25Q80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x14,
+     "GD25Q80B",
+     1048576,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x14,
+     "GD25Q80C",
+     1048576,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x15, "GD25Q16", 2097152, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x15,
+     "GD25Q16B",
+     2097152,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x16, "GD25Q32", 4194304, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x16,
+     "GD25Q32B",
+     4194304,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8, 0x40, 0x17, "GD25Q64", 8388608, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x17,
+     "GD25Q64B",
+     8388608,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x17,
+     "GD25B64C",
+     8388608,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x18,
+     "GD25Q128B",
+     16777216,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8,
+     0x40,
+     0x18,
+     "GD25Q128C",
+     16777216,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8,
+     0x60,
+     0x17,
+     "GD25LQ064C_1.8V",
+     8388608,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8,
+     0x60,
+     0x18,
+     "GD25LQ128C_1.8V",
+     16777216,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8,
+     0x60,
+     0x19,
+     "GD25LQ256C_1.8V",
+     33554432,
+     256,
+     SPIMemChipVendorGIGADEVICE,
+     SPIMemChipWriteModePage},
+    {0xC8, 0x31, 0x14, "MD25T80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0x51, 0x40, 0x12, "MD25D20", 262144, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0x51, 0x40, 0x13, "MD25D40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0x51, 0x40, 0x14, "MD25D80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0x51, 0x40, 0x15, "MD25D16", 2097152, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage},
+    {0x1C, 0x20, 0x10, "ICE25P05", 65536, 128, SPIMemChipVendorICE, SPIMemChipWriteModePage},
+    {0x89, 0x89, 0x11, "QB25F016S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage},
+    {0x89, 0x89, 0x11, "QB25F160S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage},
+    {0x89, 0x89, 0x12, "QB25F320S33B", 4194304, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage},
+    {0x89, 0x89, 0x13, "QB25F640S33B", 8388608, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage},
+    {0x89, 0x89, 0x11, "QH25F016S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage},
+    {0x89, 0x89, 0x11, "QH25F160S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage},
+    {0x89, 0x89, 0x12, "QH25F320S33B", 4194304, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "KH25L1005", 131072, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "KH25L1005A", 131072, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x12, "KH25L2005", 262144, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "KH25L4005", 524288, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "KH25L4005A", 524288, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "KH25L512", 65536, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "KH25L512A", 65536, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x14, "KH25L8005", 1048576, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x26, 0x15, "KH25L8036D", 1048576, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "MX25L1005", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "MX25L1005A", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "MX25L1005C", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "MX25L1006E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x22, 0x11, "MX25L1021E", 131072, 32, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "MX25L1025C", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "MX25L1026E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12805D",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12835E",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12835F",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12836E",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12839F",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12845E",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12845G",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12845F",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12865E",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12865F",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12873F",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x18,
+     "MX25L12875F",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x19,
+     "MX25L25635E",
+     33554432,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x15, "MX25L1605", 2097152, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x15,
+     "MX25L1605A",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x15,
+     "MX25L1605D",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x15,
+     "MX25L1606E",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x24,
+     0x15,
+     "MX25L1633E",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x24,
+     0x15,
+     "MX25L1635D",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x15,
+     "MX25L1635E",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x24,
+     0x15,
+     "MX25L1636D",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x15,
+     "MX25L1636E",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x24,
+     0x15,
+     "MX25L1673E",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x24,
+     0x15,
+     "MX25L1675E",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x12, "MX25L2005", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x12, "MX25L2005C", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x12, "MX25L2006E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x12, "MX25L2026C", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x12, "MX25L2026E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x16, "MX25L3205", 4194304, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3205A",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3205D",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3206E",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3208E",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x5E,
+     0x16,
+     "MX25L3225D",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3233F",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x5E,
+     0x16,
+     "MX25L3235D",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3235E",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x5E,
+     0x16,
+     "MX25L3236D",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x5E,
+     0x16,
+     "MX25L3237D",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x36,
+     "MX25L3239E",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3273E",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3273F",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x16,
+     "MX25L3275E",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "MX25L4005", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "MX25L4005A", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "MX25L4005C", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "MX25L4006E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "MX25L4026E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "MX25L512", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "MX25L512A", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "MX25L512C", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x22, 0x10, "MX25L5121E", 65536, 32, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x17, "MX25L6405", 8388608, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6405D",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6406E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6408E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6433F",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6435E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6436E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6436F",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x37,
+     "MX25L6439E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6445E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6465E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6473E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6473F",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x17,
+     "MX25L6475E",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x14, "MX25L8005", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x14,
+     "MX25L8006E",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x14,
+     "MX25L8008E",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x14,
+     "MX25L8035E",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x14,
+     "MX25L8036E",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x14,
+     "MX25L8073E",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x14,
+     "MX25L8075E",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x19,
+     "MX25L25673G",
+     33554432,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x28, 0x10, "MX25R512F", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x28, 0x11, "MX25R1035F", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x28,
+     0x15,
+     "MX25R1635F",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x28, 0x12, "MX25R2035F", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x28,
+     0x16,
+     "MX25R3235F",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x28, 0x13, "MX25R4035F", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x28,
+     0x17,
+     "MX25R6435F",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x28,
+     0x14,
+     "MX25R8035F",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x31,
+     "MX25U1001E_1.8V",
+     131072,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x18,
+     "MX25U12835F_1.8V",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x39,
+     "MX25U25673G_1.8V",
+     33554432,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x39,
+     "MX25U25645G_1.8V",
+     33554432,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x35,
+     "MX25U1635E_1.8V",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x35,
+     "MX25U1635F_1.8V",
+     2097152,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x32,
+     "MX25U2032E_1.8V",
+     262144,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x32,
+     "MX25U2033E_1.8V",
+     262144,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x36,
+     "MX25U3235E_1.8V",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x36,
+     "MX25U3235F_1.8V",
+     4194304,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x33,
+     "MX25U4032E_1.8V",
+     524288,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x33,
+     "MX25U4033E_1.8V",
+     524288,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x33,
+     "MX25U4035_1.8V",
+     524288,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x30,
+     "MX25U5121E_1.8V",
+     65536,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x37,
+     "MX25U6435F_1.8V",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x37,
+     "MX25U6473F_1.8V",
+     8388608,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x34,
+     "MX25U8032E_1.8V",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x34,
+     "MX25U8033E_1.8V",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x34,
+     "MX25U8035_1.8V",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x34,
+     "MX25U8035E_1.8V",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x38,
+     "MX25U12873F_1.8V",
+     16777216,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x11, "MX25V1006E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x23, 0x11, "MX25V1035F", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x12, "MX25V2006E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x23, 0x12, "MX25V2035F", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "MX25V512", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "MX25V512C", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x10, "MX25V512E", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x23, 0x10, "MX25V512F", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "MX25V4005", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x13, "MX25V4006E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x25, 0x53, "MX25V4035", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x23, 0x13, "MX25V4035F", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2, 0x20, 0x14, "MX25V8005", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x20,
+     0x14,
+     "MX25V8006E",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2, 0x25, 0x54, "MX25V8035", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage},
+    {0xC2,
+     0x23,
+     0x14,
+     "MX25V8035F",
+     1048576,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x3A,
+     "MX66U51235F_1.8V",
+     67108864,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0xC2,
+     0x25,
+     0x3B,
+     "MX66U1G45G_1.8V",
+     134217728,
+     256,
+     SPIMemChipVendorMACRONIX,
+     SPIMemChipWriteModePage},
+    {0x20, 0xBA, 0x16, "N25Q032A", 4194304, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x20, 0xBA, 0x17, "N25Q064A", 8388608, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x20, 0xBA, 0x19, "N25Q256A13", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x20, 0xBA, 0x20, "N25Q512A83", 67108864, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x2C, 0xCB, 0x19, "N25W256A11", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x20,
+     0xBA,
+     0x18,
+     "MT25QL128AB",
+     16777216,
+     256,
+     SPIMemChipVendorMICRON,
+     SPIMemChipWriteModePage},
+    {0x20, 0xBA, 0x19, "MT25QL256A", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x20, 0xBA, 0x20, "MT25QL512A", 67108864, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x20,
+     0xBA,
+     0x22,
+     "MT25QL02GC",
+     268435456,
+     256,
+     SPIMemChipVendorMICRON,
+     SPIMemChipWriteModePage},
+    {0x20, 0xBB, 0x19, "MT25QU256", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage},
+    {0x20,
+     0xBA,
+     0x21,
+     "N25Q00AA13G",
+     134217728,
+     256,
+     SPIMemChipVendorMICRON,
+     SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x10, "MS25X512", 65536, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x11, "MS25X10", 131072, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x12, "MS25X20", 262144, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x13, "MS25X40", 524288, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x14, "MS25X80", 1048576, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x15, "MS25X16", 2097152, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x16, "MS25X32", 4194304, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage},
+    {0xD5, 0x30, 0x11, "N25S10", 131072, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage},
+    {0xD5, 0x30, 0x12, "N25S20", 262144, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage},
+    {0xD5, 0x30, 0x13, "N25S40", 524288, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage},
+    {0xD5, 0x30, 0x15, "N25S16", 2097152, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage},
+    {0xD5, 0x30, 0x16, "N25S32", 4194304, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage},
+    {0xD5, 0x30, 0x14, "N25S80", 1048576, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage},
+    {0x9D, 0x7F, 0x7C, "NX25P10", 131072, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage},
+    {0xEF, 0x20, 0x15, "NX25P16", 2097152, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage},
+    {0x9D, 0x7F, 0x7D, "NX25P20", 262144, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage},
+    {0xEF, 0x20, 0x16, "NX25P32", 4194304, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage},
+    {0x9D, 0x7F, 0x7E, "NX25P40", 524288, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage},
+    {0x9D, 0x7F, 0x13, "NX25P80", 1048576, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage},
+    {0x20, 0x40, 0x15, "M45PE16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x10, "M25P05", 65536, 128, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x10, "M25P05A", 65536, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x11, "M25P10", 131072, 128, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x11, "M25P10A", 131072, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x12, "M25P20", 262144, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x13, "M25P40", 524288, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x14, "M25P80", 1048576, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x15, "M25P16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x16, "M25P32", 4194304, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x17, "M25P64", 8388608, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20,
+     0x20,
+     0x18,
+     "M25P128_ST25P28V6G",
+     16777216,
+     256,
+     SPIMemChipVendorNUMONYX,
+     SPIMemChipWriteModePage},
+    {0x20, 0x80, 0x11, "M25PE10", 131072, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x80, 0x15, "M25PE16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x80, 0x12, "M25PE20", 262144, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x80, 0x13, "M25PE40", 524288, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0x20, 0x80, 0x14, "M25PE80", 1048576, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage},
+    {0xBF, 0x43, 0x00, "PCT25LF020A", 262144, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0xBF, 0x49, 0x00, "PCT25VF010A", 131072, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0xBF, 0x25, 0x41, "PCT25VF016B", 2097152, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0xBF, 0x43, 0x00, "PCT25VF020A", 262144, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0xBF, 0x25, 0x4A, "PCT25VF032B", 4194304, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0xBF, 0x44, 0x00, "PCT25VF040A", 524288, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0xBF, 0x25, 0x8D, "PCT25VF040B", 524288, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0xBF, 0x25, 0x8E, "PCT25VF080B", 1048576, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x10, "S25FL001D", 131072, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x11, "S25FL002D", 262144, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x12, "S25FL004A", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x12, "S25FL004D", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x13, "S25FL004K", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x13, "S25FL008A", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x13, "S25FL008D", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x14, "S25FL008K", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x14, "S25FL016A", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x15, "S25FL016K", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x15, "S25FL032A", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x16, "S25FL032K", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x15, "S25FL032P", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x12, "S25FL040A", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01,
+     0x02,
+     0x26,
+     "S25FL040A_BOT",
+     524288,
+     256,
+     SPIMemChipVendorSPANSION,
+     SPIMemChipWriteModePage},
+    {0x01,
+     0x02,
+     0x25,
+     "S25FL040A_TOP",
+     524288,
+     256,
+     SPIMemChipVendorSPANSION,
+     SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x16, "S25FL064A", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x17, "S25FL064K", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x02, 0x16, "S25FL064P", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x40, 0x15, "S25FL116K", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0xEF,
+     0x40,
+     0x18,
+     "S25FL128K",
+     16777216,
+     256,
+     SPIMemChipVendorSPANSION,
+     SPIMemChipWriteModePage},
+    {0x01,
+     0x20,
+     0x18,
+     "S25FL128P",
+     16777216,
+     256,
+     SPIMemChipVendorSPANSION,
+     SPIMemChipWriteModePage},
+    {0x01,
+     0x20,
+     0x18,
+     "S25FL128S",
+     16777216,
+     256,
+     SPIMemChipVendorSPANSION,
+     SPIMemChipWriteModePage},
+    {0x01, 0x40, 0x16, "S25FL132K", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01, 0x40, 0x17, "S25FL164K", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage},
+    {0x01,
+     0x02,
+     0x19,
+     "S25FL256S",
+     33554432,
+     256,
+     SPIMemChipVendorSPANSION,
+     SPIMemChipWriteModePage},
+    {0xBF, 0x25, 0x41, "SST25VF016B", 2097152, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord},
+    {0xBF, 0x25, 0x8C, "SST25VF020B", 262144, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord},
+    {0xBF, 0x25, 0x4A, "SST25VF032B", 4194304, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord},
+    {0xBF, 0x25, 0x4B, "SST25VF064C", 8388608, 256, SPIMemChipVendorSST, SPIMemChipWriteModePage},
+    {0xBF, 0x25, 0x8D, "SST25VF040B", 524288, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord},
+    {0xBF, 0x25, 0x8E, "SST25VF080B", 1048576, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord},
+    {0x20, 0x71, 0x15, "M25PX16", 2097152, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x71, 0x16, "M25PX32", 4194304, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x71, 0x17, "M25PX64", 8388608, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x71, 0x14, "M25PX80", 1048576, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x10, "ST25P05", 65536, 128, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x10, "ST25P05A", 65536, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x11, "ST25P10", 131072, 128, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x11, "ST25P10A", 131072, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x15, "ST25P16", 2097152, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x12, "ST25P20", 262144, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x16, "ST25P32", 4194304, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x13, "ST25P40", 524288, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x17, "ST25P64", 8388608, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x14, "ST25P80", 1048576, 256, SPIMemChipVendorST, SPIMemChipWriteModePage},
+    {0xEF, 0x10, 0x00, "W25P10", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x20, 0x15, "W25P16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x11, 0x00, "W25P20", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x20, 0x16, "W25P32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x12, 0x00, "W25P40", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x20, 0x17, "W25P64", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x20, 0x14, "W25P80", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x11,
+     "W25Q10EW_1.8V",
+     131072,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x18, "W25Q128BV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x18, "W25Q128FV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x70, 0x18, "W25Q128JV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x19, "W25Q256FV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x19, "W25Q256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x70, 0x19, "W25Q256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x18,
+     "W25Q128FW_1.8V",
+     16777216,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x15, "W25Q16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x15, "W25Q16BV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x15, "W25Q16CL", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x15, "W25Q16CV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x15, "W25Q16DV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x15,
+     "W25Q16FW_1.8V",
+     2097152,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x15, "W25Q16V", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x12, "W25Q20CL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x12,
+     "W25Q20EW_1.8V",
+     262144,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x16, "W25Q32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x16, "W25Q32BV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x16, "W25Q32FV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x16,
+     "W25Q32FW_1.8V",
+     4194304,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x16, "W25Q32V", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x13, "W25Q40BL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x13, "W25Q40BV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x13, "W25Q40CL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x13,
+     "W25Q40EW_1.8V",
+     524288,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x17, "W25Q64BV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x17, "W25Q64CV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x17, "W25Q64FV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x17, "W25Q64JV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x17,
+     "W25Q64FW_1.8V",
+     8388608,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x14, "W25Q80BL", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x14, "W25Q80BV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x50,
+     0x14,
+     "W25Q80BW_1.8V",
+     1048576,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x14, "W25Q80DV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF,
+     0x60,
+     0x14,
+     "W25Q80EW_1.8V",
+     1048576,
+     256,
+     SPIMemChipVendorWINBOND,
+     SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x10, "W25X05", 65536, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x10, "W25X05CL", 65536, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x11, "W25X10AV", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x11, "W25X10BL", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x11, "W25X10BV", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x11, "W25X10CL", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x11, "W25X10L", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x11, "W25X10V", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x15, "W25X16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x15, "W25X16AL", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x15, "W25X16AV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x15, "W25X16BV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x15, "W25X16V", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x12, "W25X20AL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x12, "W25X20AV", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x12, "W25X20BL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x12, "W25X20BV", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x12, "W25X20CL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x12, "W25X20L", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x12, "W25X20V", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x16, "W25X32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x16, "W25X32AV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x16, "W25X32BV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x16, "W25X32V", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x13, "W25X40AL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x13, "W25X40AV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x13, "W25X40BL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x13, "W25X40BV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x13, "W25X40CL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x13, "W25X40L", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x13, "W25X40V", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x17, "W25X64", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x17, "W25X64BV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x17, "W25X64V", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x14, "W25X80AL", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x14, "W25X80AV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x14, "W25X80BV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x14, "W25X80L", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x30, 0x14, "W25X80V", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x71, 0x19, "W25M512JV", 67108864, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0xEF, 0x40, 0x19, "W25R256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x10, "TS25L512A", 65536, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x11, "TS25L010A", 131072, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage},
+    {0x37, 0x30, 0x12, "TS25L020A", 262144, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x15, "TS25L16AP", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage},
+    {0x20, 0x20, 0x15, "TS25L16BP", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage},
+    {0x37, 0x20, 0x15, "TS25L16P", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage},
+    {0x5E, 0x40, 0x15, "ZB25D16", 2097152, 256, SPIMemChipVendorZbit, SPIMemChipWriteModePage},
+    {0xE0, 0x40, 0x13, "BG25Q40A", 524288, 256, SPIMemChipVendorBerg_Micro, SPIMemChipWriteModePage},
+    {0xE0,
+     0x40,
+     0x14,
+     "BG25Q80A",
+     1048576,
+     256,
+     SPIMemChipVendorBerg_Micro,
+     SPIMemChipWriteModePage},
+    {0xE0,
+     0x40,
+     0x15,
+     "BG25Q16A",
+     2097152,
+     256,
+     SPIMemChipVendorBerg_Micro,
+     SPIMemChipWriteModePage},
+    {0xE0,
+     0x40,
+     0x16,
+     "BG25Q32A",
+     4194304,
+     256,
+     SPIMemChipVendorBerg_Micro,
+     SPIMemChipWriteModePage},
+    {0x1F, 0x23, 0x00, "AT45DB021D", 270336, 264, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x24, 0x00, "AT45DB041D", 540672, 264, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x26, 0x00, "AT45DB161D", 2162688, 528, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x27, 0x01, "AT45DB321D", 4325376, 528, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x43, 0x00, "AT25DF021", 262144, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x44, 0x00, "AT25DF041", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x44, 0x00, "AT25DF041A", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x84, 0x00, "AT25SF041", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x45, 0x00, "AT25DF081", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x45, 0x00, "AT25DF081A", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x46, 0x00, "AT25DF161", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x47, 0x00, "AT25DF321", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x47, 0x00, "AT25DF321A", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x48, 0x00, "AT25DF641", 8388608, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x65, 0x00, "AT25F512B", 65536, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x45, 0x00, "AT26DF081", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x45, 0x00, "AT26DF081A", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x46, 0x00, "AT26DF161", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x46, 0x00, "AT26DF161A", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x47, 0x00, "AT26DF321", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x47, 0x00, "AT26DF321A", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0x1F, 0x04, 0x00, "AT26F004", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage},
+    {0xE0,
+     0x60,
+     0x18,
+     "ACE25A128G_1.8V",
+     16777216,
+     256,
+     SPIMemChipVendorACE,
+     SPIMemChipWriteModePage},
+    {0x9B, 0x32, 0x16, "ATO25Q32", 4194304, 256, SPIMemChipVendorATO, SPIMemChipWriteModePage},
+    {0x54, 0x40, 0x17, "DQ25Q64A", 8388608, 256, SPIMemChipVendorDOUQI, SPIMemChipWriteModePage},
+    {0x0E, 0x40, 0x15, "FT25H16", 2097152, 256, SPIMemChipVendorFremont, SPIMemChipWriteModePage},
+    {0xA1, 0x40, 0x13, "FM25Q04A", 524288, 256, SPIMemChipVendorFudan, SPIMemChipWriteModePage},
+    {0xA1, 0x40, 0x16, "FM25Q32", 4194304, 256, SPIMemChipVendorFudan, SPIMemChipWriteModePage},
+    {0xE0, 0x40, 0x14, "GT25Q80A", 1048576, 256, SPIMemChipVendorGenitop, SPIMemChipWriteModePage},
+    {0xE0, 0x40, 0x13, "PN25F04A", 524288, 256, SPIMemChipVendorParagon, SPIMemChipWriteModePage}};

+ 85 - 0
spi_mem_manager/lib/spi/spi_mem_chip_i.h

@@ -0,0 +1,85 @@
+#pragma once
+
+#include <furi.h>
+#include "spi_mem_chip.h"
+
+typedef enum {
+    SPIMemChipVendorUnknown,
+    SPIMemChipVendorADESTO,
+    SPIMemChipVendorAMIC,
+    SPIMemChipVendorBoya,
+    SPIMemChipVendorEON,
+    SPIMemChipVendorPFLASH,
+    SPIMemChipVendorTERRA,
+    SPIMemChipVendorGeneralplus,
+    SPIMemChipVendorDEUTRON,
+    SPIMemChipVendorEFST,
+    SPIMemChipVendorEXCELSEMI,
+    SPIMemChipVendorFIDELIX,
+    SPIMemChipVendorGIGADEVICE,
+    SPIMemChipVendorICE,
+    SPIMemChipVendorINTEL,
+    SPIMemChipVendorKHIC,
+    SPIMemChipVendorMACRONIX,
+    SPIMemChipVendorMICRON,
+    SPIMemChipVendorMSHINE,
+    SPIMemChipVendorNANTRONICS,
+    SPIMemChipVendorNEXFLASH,
+    SPIMemChipVendorNUMONYX,
+    SPIMemChipVendorPCT,
+    SPIMemChipVendorSPANSION,
+    SPIMemChipVendorSST,
+    SPIMemChipVendorST,
+    SPIMemChipVendorWINBOND,
+    SPIMemChipVendorZEMPRO,
+    SPIMemChipVendorZbit,
+    SPIMemChipVendorBerg_Micro,
+    SPIMemChipVendorATMEL,
+    SPIMemChipVendorACE,
+    SPIMemChipVendorATO,
+    SPIMemChipVendorDOUQI,
+    SPIMemChipVendorFremont,
+    SPIMemChipVendorFudan,
+    SPIMemChipVendorGenitop,
+    SPIMemChipVendorParagon
+} SPIMemChipVendor;
+
+typedef enum {
+    SPIMemChipCMDReadJEDECChipID = 0x9F,
+    SPIMemChipCMDReadData = 0x03,
+    SPIMemChipCMDChipErase = 0xC7,
+    SPIMemChipCMDWriteEnable = 0x06,
+    SPIMemChipCMDWriteDisable = 0x04,
+    SPIMemChipCMDReadStatus = 0x05,
+    SPIMemChipCMDWriteData = 0x02,
+    SPIMemChipCMDReleasePowerDown = 0xAB
+} SPIMemChipCMD;
+
+enum SPIMemChipStatusBit {
+    SPIMemChipStatusBitBusy = (0x01 << 0),
+    SPIMemChipStatusBitWriteEnabled = (0x01 << 1),
+    SPIMemChipStatusBitBitProtection1 = (0x01 << 2),
+    SPIMemChipStatusBitBitProtection2 = (0x01 << 3),
+    SPIMemChipStatusBitBitProtection3 = (0x01 << 4),
+    SPIMemChipStatusBitTopBottomProtection = (0x01 << 5),
+    SPIMemChipStatusBitSectorProtect = (0x01 << 6),
+    SPIMemChipStatusBitRegisterProtect = (0x01 << 7)
+};
+
+typedef struct {
+    const char* vendor_name;
+    SPIMemChipVendor vendor_enum;
+} SPIMemChipVendorName;
+
+struct SPIMemChip {
+    uint8_t vendor_id;
+    uint8_t type_id;
+    uint8_t capacity_id;
+    const char* model_name;
+    size_t size;
+    size_t page_size;
+    SPIMemChipVendor vendor_enum;
+    SPIMemChipWriteMode write_mode;
+};
+
+extern const SPIMemChip SPIMemChips[];

+ 152 - 0
spi_mem_manager/lib/spi/spi_mem_tools.c

@@ -0,0 +1,152 @@
+#include <furi_hal.h>
+#include <furi_hal_spi_config.h>
+#include "spi_mem_chip_i.h"
+#include "spi_mem_tools.h"
+
+static uint8_t spi_mem_tools_addr_to_byte_arr(uint32_t addr, uint8_t* cmd) {
+    uint8_t len = 3; // TODO(add support of 4 bytes address mode)
+    for(uint8_t i = 0; i < len; i++) {
+        cmd[i] = (addr >> ((len - (i + 1)) * 8)) & 0xFF;
+    }
+    return len;
+}
+
+static bool spi_mem_tools_trx(
+    SPIMemChipCMD cmd,
+    uint8_t* tx_buf,
+    size_t tx_size,
+    uint8_t* rx_buf,
+    size_t rx_size) {
+    bool success = false;
+    furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external);
+    do {
+        if(!furi_hal_spi_bus_tx(
+               &furi_hal_spi_bus_handle_external, (uint8_t*)&cmd, 1, SPI_MEM_SPI_TIMEOUT))
+            break;
+        if(tx_buf) {
+            if(!furi_hal_spi_bus_tx(
+                   &furi_hal_spi_bus_handle_external, tx_buf, tx_size, SPI_MEM_SPI_TIMEOUT))
+                break;
+        }
+        if(rx_buf) {
+            if(!furi_hal_spi_bus_rx(
+                   &furi_hal_spi_bus_handle_external, rx_buf, rx_size, SPI_MEM_SPI_TIMEOUT))
+                break;
+        }
+        success = true;
+    } while(0);
+    furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
+    return success;
+}
+
+static bool spi_mem_tools_write_buffer(uint8_t* data, size_t size, size_t offset) {
+    furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external);
+    uint8_t cmd = (uint8_t)SPIMemChipCMDWriteData;
+    uint8_t address[4];
+    uint8_t address_size = spi_mem_tools_addr_to_byte_arr(offset, address);
+    bool success = false;
+    do {
+        if(!furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, &cmd, 1, SPI_MEM_SPI_TIMEOUT))
+            break;
+        if(!furi_hal_spi_bus_tx(
+               &furi_hal_spi_bus_handle_external, address, address_size, SPI_MEM_SPI_TIMEOUT))
+            break;
+        if(!furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, data, size, SPI_MEM_SPI_TIMEOUT))
+            break;
+        success = true;
+    } while(0);
+    furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
+    return success;
+}
+
+bool spi_mem_tools_read_chip_info(SPIMemChip* chip) {
+    uint8_t rx_buf[3] = {0, 0, 0};
+    do {
+        if(!spi_mem_tools_trx(SPIMemChipCMDReadJEDECChipID, NULL, 0, rx_buf, 3)) break;
+        if(rx_buf[0] == 0 || rx_buf[0] == 255) break;
+        chip->vendor_id = rx_buf[0];
+        chip->type_id = rx_buf[1];
+        chip->capacity_id = rx_buf[2];
+        return true;
+    } while(0);
+    return false;
+}
+
+bool spi_mem_tools_check_chip_info(SPIMemChip* chip) {
+    SPIMemChip new_chip_info;
+    spi_mem_tools_read_chip_info(&new_chip_info);
+    do {
+        if(chip->vendor_id != new_chip_info.vendor_id) break;
+        if(chip->type_id != new_chip_info.type_id) break;
+        if(chip->capacity_id != new_chip_info.capacity_id) break;
+        return true;
+    } while(0);
+    return false;
+}
+
+bool spi_mem_tools_read_block(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size) {
+    if(!spi_mem_tools_check_chip_info(chip)) return false;
+    for(size_t i = 0; i < block_size; i += SPI_MEM_MAX_BLOCK_SIZE) {
+        uint8_t cmd[4];
+        if((offset + SPI_MEM_MAX_BLOCK_SIZE) > chip->size) return false;
+        if(!spi_mem_tools_trx(
+               SPIMemChipCMDReadData,
+               cmd,
+               spi_mem_tools_addr_to_byte_arr(offset, cmd),
+               data,
+               SPI_MEM_MAX_BLOCK_SIZE))
+            return false;
+        offset += SPI_MEM_MAX_BLOCK_SIZE;
+        data += SPI_MEM_MAX_BLOCK_SIZE;
+    }
+    return true;
+}
+
+size_t spi_mem_tools_get_file_max_block_size(SPIMemChip* chip) {
+    UNUSED(chip);
+    return (SPI_MEM_FILE_BUFFER_SIZE);
+}
+
+SPIMemChipStatus spi_mem_tools_get_chip_status(SPIMemChip* chip) {
+    UNUSED(chip);
+    uint8_t status;
+    if(!spi_mem_tools_trx(SPIMemChipCMDReadStatus, NULL, 0, &status, 1))
+        return SPIMemChipStatusError;
+    if(status & SPIMemChipStatusBitBusy) return SPIMemChipStatusBusy;
+    return SPIMemChipStatusIdle;
+}
+
+static bool spi_mem_tools_set_write_enabled(SPIMemChip* chip, bool enable) {
+    UNUSED(chip);
+    uint8_t status;
+    SPIMemChipCMD cmd = SPIMemChipCMDWriteDisable;
+    if(enable) cmd = SPIMemChipCMDWriteEnable;
+    do {
+        if(!spi_mem_tools_trx(cmd, NULL, 0, NULL, 0)) break;
+        if(!spi_mem_tools_trx(SPIMemChipCMDReadStatus, NULL, 0, &status, 1)) break;
+        if(!(status & SPIMemChipStatusBitWriteEnabled) && enable) break;
+        if((status & SPIMemChipStatusBitWriteEnabled) && !enable) break;
+        return true;
+    } while(0);
+    return false;
+}
+
+bool spi_mem_tools_erase_chip(SPIMemChip* chip) {
+    do {
+        if(!spi_mem_tools_set_write_enabled(chip, true)) break;
+        if(!spi_mem_tools_trx(SPIMemChipCMDChipErase, NULL, 0, NULL, 0)) break;
+        return true;
+    } while(0);
+    return true;
+}
+
+bool spi_mem_tools_write_bytes(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size) {
+    do {
+        if(!spi_mem_tools_check_chip_info(chip)) break;
+        if(!spi_mem_tools_set_write_enabled(chip, true)) break;
+        if((offset + block_size) > chip->size) break;
+        if(!spi_mem_tools_write_buffer(data, block_size, offset)) break;
+        return true;
+    } while(0);
+    return false;
+}

+ 14 - 0
spi_mem_manager/lib/spi/spi_mem_tools.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include "spi_mem_chip.h"
+
+#define SPI_MEM_SPI_TIMEOUT 1000
+#define SPI_MEM_MAX_BLOCK_SIZE 256
+#define SPI_MEM_FILE_BUFFER_SIZE 4096
+
+bool spi_mem_tools_read_chip_info(SPIMemChip* chip);
+bool spi_mem_tools_read_block(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size);
+size_t spi_mem_tools_get_file_max_block_size(SPIMemChip* chip);
+SPIMemChipStatus spi_mem_tools_get_chip_status(SPIMemChip* chip);
+bool spi_mem_tools_erase_chip(SPIMemChip* chip);
+bool spi_mem_tools_write_bytes(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size);

+ 129 - 0
spi_mem_manager/lib/spi/spi_mem_worker.c

@@ -0,0 +1,129 @@
+#include "spi_mem_worker_i.h"
+
+typedef enum {
+    SPIMemEventStopThread = (1 << 0),
+    SPIMemEventChipDetect = (1 << 1),
+    SPIMemEventRead = (1 << 2),
+    SPIMemEventVerify = (1 << 3),
+    SPIMemEventErase = (1 << 4),
+    SPIMemEventWrite = (1 << 5),
+    SPIMemEventAll =
+        (SPIMemEventStopThread | SPIMemEventChipDetect | SPIMemEventRead | SPIMemEventVerify |
+         SPIMemEventErase | SPIMemEventWrite)
+} SPIMemEventEventType;
+
+static int32_t spi_mem_worker_thread(void* thread_context);
+
+SPIMemWorker* spi_mem_worker_alloc() {
+    SPIMemWorker* worker = malloc(sizeof(SPIMemWorker));
+    worker->callback = NULL;
+    worker->thread = furi_thread_alloc();
+    worker->mode_index = SPIMemWorkerModeIdle;
+    furi_thread_set_name(worker->thread, "SPIMemWorker");
+    furi_thread_set_callback(worker->thread, spi_mem_worker_thread);
+    furi_thread_set_context(worker->thread, worker);
+    furi_thread_set_stack_size(worker->thread, 10240);
+    return worker;
+}
+
+void spi_mem_worker_free(SPIMemWorker* worker) {
+    furi_thread_free(worker->thread);
+    free(worker);
+}
+
+bool spi_mem_worker_check_for_stop(SPIMemWorker* worker) {
+    UNUSED(worker);
+    uint32_t flags = furi_thread_flags_get();
+    return (flags & SPIMemEventStopThread);
+}
+
+static int32_t spi_mem_worker_thread(void* thread_context) {
+    SPIMemWorker* worker = thread_context;
+    while(true) {
+        uint32_t flags = furi_thread_flags_wait(SPIMemEventAll, FuriFlagWaitAny, FuriWaitForever);
+        if(flags != (unsigned)FuriFlagErrorTimeout) {
+            if(flags & SPIMemEventStopThread) break;
+            if(flags & SPIMemEventChipDetect) worker->mode_index = SPIMemWorkerModeChipDetect;
+            if(flags & SPIMemEventRead) worker->mode_index = SPIMemWorkerModeRead;
+            if(flags & SPIMemEventVerify) worker->mode_index = SPIMemWorkerModeVerify;
+            if(flags & SPIMemEventErase) worker->mode_index = SPIMemWorkerModeErase;
+            if(flags & SPIMemEventWrite) worker->mode_index = SPIMemWorkerModeWrite;
+            if(spi_mem_worker_modes[worker->mode_index].process) {
+                spi_mem_worker_modes[worker->mode_index].process(worker);
+            }
+            worker->mode_index = SPIMemWorkerModeIdle;
+        }
+    }
+    return 0;
+}
+
+void spi_mem_worker_start_thread(SPIMemWorker* worker) {
+    furi_thread_start(worker->thread);
+}
+
+void spi_mem_worker_stop_thread(SPIMemWorker* worker) {
+    furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventStopThread);
+    furi_thread_join(worker->thread);
+}
+
+void spi_mem_worker_chip_detect_start(
+    SPIMemChip* chip_info,
+    found_chips_t* found_chips,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context) {
+    furi_check(worker->mode_index == SPIMemWorkerModeIdle);
+    worker->callback = callback;
+    worker->cb_ctx = context;
+    worker->chip_info = chip_info;
+    worker->found_chips = found_chips;
+    furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventChipDetect);
+}
+
+void spi_mem_worker_read_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context) {
+    furi_check(worker->mode_index == SPIMemWorkerModeIdle);
+    worker->callback = callback;
+    worker->cb_ctx = context;
+    worker->chip_info = chip_info;
+    furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventRead);
+}
+
+void spi_mem_worker_verify_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context) {
+    furi_check(worker->mode_index == SPIMemWorkerModeIdle);
+    worker->callback = callback;
+    worker->cb_ctx = context;
+    worker->chip_info = chip_info;
+    furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventVerify);
+}
+
+void spi_mem_worker_erase_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context) {
+    furi_check(worker->mode_index == SPIMemWorkerModeIdle);
+    worker->callback = callback;
+    worker->cb_ctx = context;
+    worker->chip_info = chip_info;
+    furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventErase);
+}
+
+void spi_mem_worker_write_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context) {
+    furi_check(worker->mode_index == SPIMemWorkerModeIdle);
+    worker->callback = callback;
+    worker->cb_ctx = context;
+    worker->chip_info = chip_info;
+    furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventWrite);
+}

+ 54 - 0
spi_mem_manager/lib/spi/spi_mem_worker.h

@@ -0,0 +1,54 @@
+#pragma once
+
+#include <furi.h>
+#include "spi_mem_chip.h"
+
+typedef struct SPIMemWorker SPIMemWorker;
+
+typedef struct {
+    void (*const process)(SPIMemWorker* worker);
+} SPIMemWorkerModeType;
+
+typedef enum {
+    SPIMemCustomEventWorkerChipIdentified,
+    SPIMemCustomEventWorkerChipUnknown,
+    SPIMemCustomEventWorkerBlockReaded,
+    SPIMemCustomEventWorkerChipFail,
+    SPIMemCustomEventWorkerFileFail,
+    SPIMemCustomEventWorkerDone,
+    SPIMemCustomEventWorkerVerifyFail,
+} SPIMemCustomEventWorker;
+
+typedef void (*SPIMemWorkerCallback)(void* context, SPIMemCustomEventWorker event);
+
+SPIMemWorker* spi_mem_worker_alloc();
+void spi_mem_worker_free(SPIMemWorker* worker);
+void spi_mem_worker_start_thread(SPIMemWorker* worker);
+void spi_mem_worker_stop_thread(SPIMemWorker* worker);
+bool spi_mem_worker_check_for_stop(SPIMemWorker* worker);
+void spi_mem_worker_chip_detect_start(
+    SPIMemChip* chip_info,
+    found_chips_t* found_chips,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context);
+void spi_mem_worker_read_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context);
+void spi_mem_worker_verify_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context);
+void spi_mem_worker_erase_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context);
+void spi_mem_worker_write_start(
+    SPIMemChip* chip_info,
+    SPIMemWorker* worker,
+    SPIMemWorkerCallback callback,
+    void* context);

+ 24 - 0
spi_mem_manager/lib/spi/spi_mem_worker_i.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include "spi_mem_worker.h"
+
+typedef enum {
+    SPIMemWorkerModeIdle,
+    SPIMemWorkerModeChipDetect,
+    SPIMemWorkerModeRead,
+    SPIMemWorkerModeVerify,
+    SPIMemWorkerModeErase,
+    SPIMemWorkerModeWrite
+} SPIMemWorkerMode;
+
+struct SPIMemWorker {
+    SPIMemChip* chip_info;
+    found_chips_t* found_chips;
+    SPIMemWorkerMode mode_index;
+    SPIMemWorkerCallback callback;
+    void* cb_ctx;
+    FuriThread* thread;
+    FuriString* file_name;
+};
+
+extern const SPIMemWorkerModeType spi_mem_worker_modes[];

+ 214 - 0
spi_mem_manager/lib/spi/spi_mem_worker_modes.c

@@ -0,0 +1,214 @@
+#include "spi_mem_worker_i.h"
+#include "spi_mem_chip.h"
+#include "spi_mem_tools.h"
+#include "../../spi_mem_files.h"
+
+static void spi_mem_worker_chip_detect_process(SPIMemWorker* worker);
+static void spi_mem_worker_read_process(SPIMemWorker* worker);
+static void spi_mem_worker_verify_process(SPIMemWorker* worker);
+static void spi_mem_worker_erase_process(SPIMemWorker* worker);
+static void spi_mem_worker_write_process(SPIMemWorker* worker);
+
+const SPIMemWorkerModeType spi_mem_worker_modes[] = {
+    [SPIMemWorkerModeIdle] = {.process = NULL},
+    [SPIMemWorkerModeChipDetect] = {.process = spi_mem_worker_chip_detect_process},
+    [SPIMemWorkerModeRead] = {.process = spi_mem_worker_read_process},
+    [SPIMemWorkerModeVerify] = {.process = spi_mem_worker_verify_process},
+    [SPIMemWorkerModeErase] = {.process = spi_mem_worker_erase_process},
+    [SPIMemWorkerModeWrite] = {.process = spi_mem_worker_write_process}};
+
+static void spi_mem_worker_run_callback(SPIMemWorker* worker, SPIMemCustomEventWorker event) {
+    if(worker->callback) {
+        worker->callback(worker->cb_ctx, event);
+    }
+}
+
+static bool spi_mem_worker_await_chip_busy(SPIMemWorker* worker) {
+    while(true) {
+        furi_delay_tick(10); // to give some time to OS
+        if(spi_mem_worker_check_for_stop(worker)) return true;
+        SPIMemChipStatus chip_status = spi_mem_tools_get_chip_status(worker->chip_info);
+        if(chip_status == SPIMemChipStatusError) return false;
+        if(chip_status == SPIMemChipStatusBusy) continue;
+        return true;
+    }
+}
+
+static size_t spi_mem_worker_modes_get_total_size(SPIMemWorker* worker) {
+    size_t chip_size = spi_mem_chip_get_size(worker->chip_info);
+    size_t file_size = spi_mem_file_get_size(worker->cb_ctx);
+    size_t total_size = chip_size;
+    if(chip_size > file_size) total_size = file_size;
+    return total_size;
+}
+
+// ChipDetect
+static void spi_mem_worker_chip_detect_process(SPIMemWorker* worker) {
+    SPIMemCustomEventWorker event;
+    while(!spi_mem_tools_read_chip_info(worker->chip_info)) {
+        furi_delay_tick(10); // to give some time to OS
+        if(spi_mem_worker_check_for_stop(worker)) return;
+    }
+    if(spi_mem_chip_find_all(worker->chip_info, *worker->found_chips)) {
+        event = SPIMemCustomEventWorkerChipIdentified;
+    } else {
+        event = SPIMemCustomEventWorkerChipUnknown;
+    }
+    spi_mem_worker_run_callback(worker, event);
+}
+
+// Read
+static bool spi_mem_worker_read(SPIMemWorker* worker, SPIMemCustomEventWorker* event) {
+    uint8_t data_buffer[SPI_MEM_FILE_BUFFER_SIZE];
+    size_t chip_size = spi_mem_chip_get_size(worker->chip_info);
+    size_t offset = 0;
+    bool success = true;
+    while(true) {
+        furi_delay_tick(10); // to give some time to OS
+        size_t block_size = SPI_MEM_FILE_BUFFER_SIZE;
+        if(spi_mem_worker_check_for_stop(worker)) break;
+        if(offset >= chip_size) break;
+        if((offset + block_size) > chip_size) block_size = chip_size - offset;
+        if(!spi_mem_tools_read_block(worker->chip_info, offset, data_buffer, block_size)) {
+            *event = SPIMemCustomEventWorkerChipFail;
+            success = false;
+            break;
+        }
+        if(!spi_mem_file_write_block(worker->cb_ctx, data_buffer, block_size)) {
+            success = false;
+            break;
+        }
+        offset += block_size;
+        spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded);
+    }
+    if(success) *event = SPIMemCustomEventWorkerDone;
+    return success;
+}
+
+static void spi_mem_worker_read_process(SPIMemWorker* worker) {
+    SPIMemCustomEventWorker event = SPIMemCustomEventWorkerFileFail;
+    do {
+        if(!spi_mem_worker_await_chip_busy(worker)) break;
+        if(!spi_mem_file_create_open(worker->cb_ctx)) break;
+        if(!spi_mem_worker_read(worker, &event)) break;
+    } while(0);
+    spi_mem_file_close(worker->cb_ctx);
+    spi_mem_worker_run_callback(worker, event);
+}
+
+// Verify
+static bool
+    spi_mem_worker_verify(SPIMemWorker* worker, size_t total_size, SPIMemCustomEventWorker* event) {
+    uint8_t data_buffer_chip[SPI_MEM_FILE_BUFFER_SIZE];
+    uint8_t data_buffer_file[SPI_MEM_FILE_BUFFER_SIZE];
+    size_t offset = 0;
+    bool success = true;
+    while(true) {
+        furi_delay_tick(10); // to give some time to OS
+        size_t block_size = SPI_MEM_FILE_BUFFER_SIZE;
+        if(spi_mem_worker_check_for_stop(worker)) break;
+        if(offset >= total_size) break;
+        if((offset + block_size) > total_size) block_size = total_size - offset;
+        if(!spi_mem_tools_read_block(worker->chip_info, offset, data_buffer_chip, block_size)) {
+            *event = SPIMemCustomEventWorkerChipFail;
+            success = false;
+            break;
+        }
+        if(!spi_mem_file_read_block(worker->cb_ctx, data_buffer_file, block_size)) {
+            success = false;
+            break;
+        }
+        if(memcmp(data_buffer_chip, data_buffer_file, block_size) != 0) {
+            *event = SPIMemCustomEventWorkerVerifyFail;
+            success = false;
+            break;
+        }
+        offset += block_size;
+        spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded);
+    }
+    if(success) *event = SPIMemCustomEventWorkerDone;
+    return success;
+}
+
+static void spi_mem_worker_verify_process(SPIMemWorker* worker) {
+    SPIMemCustomEventWorker event = SPIMemCustomEventWorkerFileFail;
+    size_t total_size = spi_mem_worker_modes_get_total_size(worker);
+    do {
+        if(!spi_mem_worker_await_chip_busy(worker)) break;
+        if(!spi_mem_file_open(worker->cb_ctx)) break;
+        if(!spi_mem_worker_verify(worker, total_size, &event)) break;
+    } while(0);
+    spi_mem_file_close(worker->cb_ctx);
+    spi_mem_worker_run_callback(worker, event);
+}
+
+// Erase
+static void spi_mem_worker_erase_process(SPIMemWorker* worker) {
+    SPIMemCustomEventWorker event = SPIMemCustomEventWorkerChipFail;
+    do {
+        if(!spi_mem_worker_await_chip_busy(worker)) break;
+        if(!spi_mem_tools_erase_chip(worker->chip_info)) break;
+        if(!spi_mem_worker_await_chip_busy(worker)) break;
+        event = SPIMemCustomEventWorkerDone;
+    } while(0);
+    spi_mem_worker_run_callback(worker, event);
+}
+
+// Write
+static bool spi_mem_worker_write_block_by_page(
+    SPIMemWorker* worker,
+    size_t offset,
+    uint8_t* data,
+    size_t block_size,
+    size_t page_size) {
+    for(size_t i = 0; i < block_size; i += page_size) {
+        if(!spi_mem_worker_await_chip_busy(worker)) return false;
+        if(!spi_mem_tools_write_bytes(worker->chip_info, offset, data, page_size)) return false;
+        offset += page_size;
+        data += page_size;
+    }
+    return true;
+}
+
+static bool
+    spi_mem_worker_write(SPIMemWorker* worker, size_t total_size, SPIMemCustomEventWorker* event) {
+    bool success = true;
+    uint8_t data_buffer[SPI_MEM_FILE_BUFFER_SIZE];
+    size_t page_size = spi_mem_chip_get_page_size(worker->chip_info);
+    size_t offset = 0;
+    while(true) {
+        furi_delay_tick(10); // to give some time to OS
+        size_t block_size = SPI_MEM_FILE_BUFFER_SIZE;
+        if(spi_mem_worker_check_for_stop(worker)) break;
+        if(offset >= total_size) break;
+        if((offset + block_size) > total_size) block_size = total_size - offset;
+        if(!spi_mem_file_read_block(worker->cb_ctx, data_buffer, block_size)) {
+            *event = SPIMemCustomEventWorkerFileFail;
+            success = false;
+            break;
+        }
+        if(!spi_mem_worker_write_block_by_page(
+               worker, offset, data_buffer, block_size, page_size)) {
+            success = false;
+            break;
+        }
+        offset += block_size;
+        spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded);
+    }
+    return success;
+}
+
+static void spi_mem_worker_write_process(SPIMemWorker* worker) {
+    SPIMemCustomEventWorker event = SPIMemCustomEventWorkerChipFail;
+    size_t total_size =
+        spi_mem_worker_modes_get_total_size(worker); // need to be executed before opening file
+    do {
+        if(!spi_mem_file_open(worker->cb_ctx)) break;
+        if(!spi_mem_worker_await_chip_busy(worker)) break;
+        if(!spi_mem_worker_write(worker, total_size, &event)) break;
+        if(!spi_mem_worker_await_chip_busy(worker)) break;
+        event = SPIMemCustomEventWorkerDone;
+    } while(0);
+    spi_mem_file_close(worker->cb_ctx);
+    spi_mem_worker_run_callback(worker, event);
+}

+ 30 - 0
spi_mem_manager/scenes/spi_mem_scene.c

@@ -0,0 +1,30 @@
+#include "spi_mem_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const spi_mem_on_enter_handlers[])(void*) = {
+#include "spi_mem_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const spi_mem_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "spi_mem_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const spi_mem_on_exit_handlers[])(void* context) = {
+#include "spi_mem_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers spi_mem_scene_handlers = {
+    .on_enter_handlers = spi_mem_on_enter_handlers,
+    .on_event_handlers = spi_mem_on_event_handlers,
+    .on_exit_handlers = spi_mem_on_exit_handlers,
+    .scene_num = SPIMemSceneNum,
+};

+ 29 - 0
spi_mem_manager/scenes/spi_mem_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) SPIMemScene##id,
+typedef enum {
+#include "spi_mem_scene_config.h"
+    SPIMemSceneNum,
+} SPIMemScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers spi_mem_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "spi_mem_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "spi_mem_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "spi_mem_scene_config.h"
+#undef ADD_SCENE

+ 42 - 0
spi_mem_manager/scenes/spi_mem_scene_about.c

@@ -0,0 +1,42 @@
+#include "../spi_mem_app_i.h"
+#include "../lib/spi/spi_mem_chip.h"
+
+#define SPI_MEM_VERSION_APP "0.1.0"
+#define SPI_MEM_DEVELOPER "DrunkBatya"
+#define SPI_MEM_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
+#define SPI_MEM_NAME "\e#\e!       SPI Mem Manager        \e!\n"
+#define SPI_MEM_BLANK_INV "\e#\e!                                                      \e!\n"
+
+void spi_mem_scene_about_on_enter(void* context) {
+    SPIMemApp* app = context;
+    FuriString* tmp_string = furi_string_alloc();
+
+    widget_add_text_box_element(
+        app->widget, 0, 0, 128, 14, AlignCenter, AlignBottom, SPI_MEM_BLANK_INV, false);
+    widget_add_text_box_element(
+        app->widget, 0, 2, 128, 14, AlignCenter, AlignBottom, SPI_MEM_NAME, false);
+    furi_string_printf(tmp_string, "\e#%s\n", "Information");
+    furi_string_cat_printf(tmp_string, "Version: %s\n", SPI_MEM_VERSION_APP);
+    furi_string_cat_printf(tmp_string, "Developed by: %s\n", SPI_MEM_DEVELOPER);
+    furi_string_cat_printf(tmp_string, "Github: %s\n\n", SPI_MEM_GITHUB);
+    furi_string_cat_printf(tmp_string, "\e#%s\n", "Description");
+    furi_string_cat_printf(
+        tmp_string,
+        "SPI memory dumper\n"
+        "Originally written by Hedger, ghettorce and x893 at\n"
+        "Flipper Hackathon 2021\n\n");
+    widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(tmp_string));
+
+    furi_string_free(tmp_string);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+bool spi_mem_scene_about_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+void spi_mem_scene_about_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 37 - 0
spi_mem_manager/scenes/spi_mem_scene_chip_detect.c

@@ -0,0 +1,37 @@
+#include "../spi_mem_app_i.h"
+
+static void spi_mem_scene_chip_detect_callback(void* context, SPIMemCustomEventWorker event) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void spi_mem_scene_chip_detect_on_enter(void* context) {
+    SPIMemApp* app = context;
+    notification_message(app->notifications, &sequence_blink_start_yellow);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewDetect);
+    spi_mem_worker_start_thread(app->worker);
+    spi_mem_worker_chip_detect_start(
+        app->chip_info, &app->found_chips, app->worker, spi_mem_scene_chip_detect_callback, app);
+}
+
+bool spi_mem_scene_chip_detect_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == SPIMemCustomEventWorkerChipIdentified) {
+            scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, 0);
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectVendor);
+        } else if(event.event == SPIMemCustomEventWorkerChipUnknown) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetectFail);
+        }
+    }
+    return success;
+}
+
+void spi_mem_scene_chip_detect_on_exit(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_worker_stop_thread(app->worker);
+    notification_message(app->notifications, &sequence_blink_stop);
+    popup_reset(app->popup);
+}

+ 57 - 0
spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c

@@ -0,0 +1,57 @@
+#include "../spi_mem_app_i.h"
+#include "../lib/spi/spi_mem_chip.h"
+
+static void spi_mem_scene_chip_detect_fail_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    SPIMemApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void spi_mem_scene_chip_detect_fail_on_enter(void* context) {
+    SPIMemApp* app = context;
+    FuriString* str = furi_string_alloc();
+    widget_add_button_element(
+        app->widget,
+        GuiButtonTypeCenter,
+        "Retry",
+        spi_mem_scene_chip_detect_fail_widget_callback,
+        app);
+    widget_add_string_element(
+        app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Detected");
+    widget_add_string_element(
+        app->widget, 64, 20, AlignCenter, AlignBottom, FontPrimary, "unknown SPI chip");
+    furi_string_printf(str, "Vendor\nid: 0x%02X", spi_mem_chip_get_vendor_id(app->chip_info));
+    widget_add_string_multiline_element(
+        app->widget, 16, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str));
+    furi_string_printf(str, "Type\nid: 0x%02X", spi_mem_chip_get_type_id(app->chip_info));
+    widget_add_string_multiline_element(
+        app->widget, 64, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str));
+    furi_string_printf(str, "Capacity\nid: 0x%02X", spi_mem_chip_get_capacity_id(app->chip_info));
+    widget_add_string_multiline_element(
+        app->widget, 110, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str));
+    furi_string_free(str);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+bool spi_mem_scene_chip_detect_fail_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, SPIMemSceneStart);
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == GuiButtonTypeCenter) {
+            scene_manager_previous_scene(app->scene_manager);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_chip_detect_fail_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 94 - 0
spi_mem_manager/scenes/spi_mem_scene_chip_detected.c

@@ -0,0 +1,94 @@
+#include "../spi_mem_app_i.h"
+
+static void spi_mem_scene_chip_detected_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    SPIMemApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+static void spi_mem_scene_chip_detected_print_chip_info(Widget* widget, SPIMemChip* chip_info) {
+    FuriString* tmp_string = furi_string_alloc();
+    widget_add_string_element(
+        widget,
+        40,
+        12,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        spi_mem_chip_get_vendor_name(chip_info));
+    widget_add_string_element(
+        widget, 40, 20, AlignLeft, AlignTop, FontSecondary, spi_mem_chip_get_model_name(chip_info));
+    furi_string_printf(tmp_string, "Size: %zu KB", spi_mem_chip_get_size(chip_info) / 1024);
+    widget_add_string_element(
+        widget, 40, 28, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp_string));
+    furi_string_free(tmp_string);
+}
+
+static void spi_mem_scene_chip_detect_draw_next_button(SPIMemApp* app) {
+    FuriString* str = furi_string_alloc();
+    if(app->mode == SPIMemModeRead) furi_string_printf(str, "%s", "Read");
+    if(app->mode == SPIMemModeWrite) furi_string_printf(str, "%s", "Write");
+    if(app->mode == SPIMemModeErase) furi_string_printf(str, "%s", "Erase");
+    if(app->mode == SPIMemModeCompare) furi_string_printf(str, "%s", "Check");
+    widget_add_button_element(
+        app->widget,
+        GuiButtonTypeRight,
+        furi_string_get_cstr(str),
+        spi_mem_scene_chip_detected_widget_callback,
+        app);
+    furi_string_free(str);
+}
+
+static void spi_mem_scene_chip_detected_set_previous_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneStart;
+    if(app->mode == SPIMemModeCompare || app->mode == SPIMemModeWrite)
+        scene = SPIMemSceneSavedFileMenu;
+    scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene);
+}
+
+static void spi_mem_scene_chip_detected_set_next_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneStart;
+    if(app->mode == SPIMemModeRead) scene = SPIMemSceneReadFilename;
+    if(app->mode == SPIMemModeWrite) scene = SPIMemSceneErase;
+    if(app->mode == SPIMemModeErase) scene = SPIMemSceneErase;
+    if(app->mode == SPIMemModeCompare) scene = SPIMemSceneVerify;
+    scene_manager_next_scene(app->scene_manager, scene);
+}
+
+void spi_mem_scene_chip_detected_on_enter(void* context) {
+    SPIMemApp* app = context;
+    widget_add_button_element(
+        app->widget, GuiButtonTypeLeft, "Retry", spi_mem_scene_chip_detected_widget_callback, app);
+    spi_mem_scene_chip_detect_draw_next_button(app);
+    widget_add_icon_element(app->widget, 0, 12, &I_Dip8_32x36);
+    widget_add_string_element(
+        app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Detected SPI chip");
+    spi_mem_scene_chip_detected_print_chip_info(app->widget, app->chip_info);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+bool spi_mem_scene_chip_detected_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        spi_mem_scene_chip_detected_set_previous_scene(app);
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, SPIMemSceneChipDetect);
+        } else if(event.event == GuiButtonTypeRight) {
+            spi_mem_scene_chip_detected_set_next_scene(app);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_chip_detected_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 52 - 0
spi_mem_manager/scenes/spi_mem_scene_chip_error.c

@@ -0,0 +1,52 @@
+#include "../spi_mem_app_i.h"
+
+static void
+    spi_mem_scene_chip_error_widget_callback(GuiButtonType result, InputType type, void* context) {
+    SPIMemApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void spi_mem_scene_chip_error_on_enter(void* context) {
+    SPIMemApp* app = context;
+    widget_add_button_element(
+        app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_chip_error_widget_callback, app);
+    widget_add_string_element(
+        app->widget, 85, 15, AlignCenter, AlignBottom, FontPrimary, "SPI chip error");
+    widget_add_string_multiline_element(
+        app->widget,
+        85,
+        52,
+        AlignCenter,
+        AlignBottom,
+        FontSecondary,
+        "Error while\ncommunicating\nwith chip");
+    widget_add_icon_element(app->widget, 5, 6, &I_Dip8_32x36);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+static void spi_mem_scene_chip_error_set_previous_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneChipDetect;
+    if(app->mode == SPIMemModeRead || app->mode == SPIMemModeErase) scene = SPIMemSceneStart;
+    scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene);
+}
+
+bool spi_mem_scene_chip_error_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        spi_mem_scene_chip_error_set_previous_scene(app);
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == GuiButtonTypeLeft) {
+            spi_mem_scene_chip_error_set_previous_scene(app);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_chip_error_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 21 - 0
spi_mem_manager/scenes/spi_mem_scene_config.h

@@ -0,0 +1,21 @@
+ADD_SCENE(spi_mem, start, Start)
+ADD_SCENE(spi_mem, chip_detect, ChipDetect)
+ADD_SCENE(spi_mem, chip_detected, ChipDetected)
+ADD_SCENE(spi_mem, chip_detect_fail, ChipDetectFail)
+ADD_SCENE(spi_mem, select_file, SelectFile)
+ADD_SCENE(spi_mem, saved_file_menu, SavedFileMenu)
+ADD_SCENE(spi_mem, read, Read)
+ADD_SCENE(spi_mem, read_filename, ReadFilename)
+ADD_SCENE(spi_mem, delete_confirm, DeleteConfirm)
+ADD_SCENE(spi_mem, success, Success)
+ADD_SCENE(spi_mem, about, About)
+ADD_SCENE(spi_mem, verify, Verify)
+ADD_SCENE(spi_mem, file_info, FileInfo)
+ADD_SCENE(spi_mem, erase, Erase)
+ADD_SCENE(spi_mem, chip_error, ChipError)
+ADD_SCENE(spi_mem, verify_error, VerifyError)
+ADD_SCENE(spi_mem, write, Write)
+ADD_SCENE(spi_mem, storage_error, StorageError)
+ADD_SCENE(spi_mem, select_vendor, SelectVendor)
+ADD_SCENE(spi_mem, select_model, SelectModel)
+ADD_SCENE(spi_mem, wiring, Wiring)

+ 62 - 0
spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c

@@ -0,0 +1,62 @@
+#include "../spi_mem_app_i.h"
+#include "../spi_mem_files.h"
+
+static void spi_mem_scene_delete_confirm_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    SPIMemApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void spi_mem_scene_delete_confirm_on_enter(void* context) {
+    SPIMemApp* app = context;
+    FuriString* file_name = furi_string_alloc();
+    FuriString* message = furi_string_alloc();
+    path_extract_filename(app->file_path, file_name, true);
+    furi_string_printf(message, "\e#Delete %s?\e#", furi_string_get_cstr(file_name));
+    widget_add_text_box_element(
+        app->widget, 0, 0, 128, 27, AlignCenter, AlignCenter, furi_string_get_cstr(message), true);
+    widget_add_button_element(
+        app->widget,
+        GuiButtonTypeLeft,
+        "Cancel",
+        spi_mem_scene_delete_confirm_widget_callback,
+        app);
+    widget_add_button_element(
+        app->widget,
+        GuiButtonTypeRight,
+        "Delete",
+        spi_mem_scene_delete_confirm_widget_callback,
+        app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+    furi_string_free(file_name);
+    furi_string_free(message);
+}
+
+bool spi_mem_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == GuiButtonTypeRight) {
+            app->mode = SPIMemModeDelete;
+            if(spi_mem_file_delete(app)) {
+                scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess);
+            } else {
+                scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError);
+            }
+        } else if(event.event == GuiButtonTypeLeft) {
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, SPIMemSceneSavedFileMenu);
+        }
+    }
+    return success;
+}
+
+void spi_mem_scene_delete_confirm_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 65 - 0
spi_mem_manager/scenes/spi_mem_scene_erase.c

@@ -0,0 +1,65 @@
+#include "../spi_mem_app_i.h"
+
+static void
+    spi_mem_scene_erase_widget_callback(GuiButtonType result, InputType type, void* context) {
+    SPIMemApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+static void spi_mem_scene_erase_callback(void* context, SPIMemCustomEventWorker event) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void spi_mem_scene_erase_on_enter(void* context) {
+    SPIMemApp* app = context;
+    widget_add_button_element(
+        app->widget, GuiButtonTypeLeft, "Cancel", spi_mem_scene_erase_widget_callback, app);
+    widget_add_string_element(
+        app->widget, 64, 15, AlignCenter, AlignBottom, FontPrimary, "Erasing SPI chip");
+    widget_add_string_element(
+        app->widget, 64, 27, AlignCenter, AlignBottom, FontSecondary, "Please be patient");
+    notification_message(app->notifications, &sequence_blink_start_magenta);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+    spi_mem_worker_start_thread(app->worker);
+    spi_mem_worker_erase_start(app->chip_info, app->worker, spi_mem_scene_erase_callback, app);
+}
+
+static void spi_mem_scene_erase_set_previous_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneStart;
+    if(app->mode == SPIMemModeWrite) scene = SPIMemSceneSavedFileMenu;
+    scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene);
+}
+
+static void spi_mem_scene_erase_set_next_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneSuccess;
+    if(app->mode == SPIMemModeWrite) scene = SPIMemSceneWrite;
+    scene_manager_next_scene(app->scene_manager, scene);
+}
+
+bool spi_mem_scene_erase_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        spi_mem_scene_erase_set_previous_scene(app);
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_previous_scene(app->scene_manager);
+        } else if(event.event == SPIMemCustomEventWorkerDone) {
+            spi_mem_scene_erase_set_next_scene(app);
+        } else if(event.event == SPIMemCustomEventWorkerChipFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_erase_on_exit(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_worker_stop_thread(app->worker);
+    notification_message(app->notifications, &sequence_blink_stop);
+    widget_reset(app->widget);
+}

+ 29 - 0
spi_mem_manager/scenes/spi_mem_scene_file_info.c

@@ -0,0 +1,29 @@
+#include "../spi_mem_app_i.h"
+#include "../spi_mem_files.h"
+
+void spi_mem_scene_file_info_on_enter(void* context) {
+    SPIMemApp* app = context;
+    FuriString* str = furi_string_alloc();
+    furi_string_printf(str, "Size: %zu KB", spi_mem_file_get_size(app) / 1024);
+    widget_add_string_element(
+        app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "File info");
+    widget_add_string_element(
+        app->widget, 64, 20, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str));
+    furi_string_free(str);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+bool spi_mem_scene_file_info_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        scene_manager_search_and_switch_to_previous_scene(
+            app->scene_manager, SPIMemSceneSavedFileMenu);
+    }
+    return success;
+}
+void spi_mem_scene_file_info_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 57 - 0
spi_mem_manager/scenes/spi_mem_scene_read.c

@@ -0,0 +1,57 @@
+#include "../spi_mem_app_i.h"
+#include "../spi_mem_files.h"
+#include "../lib/spi/spi_mem_chip.h"
+#include "../lib/spi/spi_mem_tools.h"
+
+void spi_mem_scene_read_progress_view_result_callback(void* context) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewReadCancel);
+}
+
+static void spi_mem_scene_read_callback(void* context, SPIMemCustomEventWorker event) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void spi_mem_scene_read_on_enter(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_view_progress_set_read_callback(
+        app->view_progress, spi_mem_scene_read_progress_view_result_callback, app);
+    notification_message(app->notifications, &sequence_blink_start_blue);
+    spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info));
+    spi_mem_view_progress_set_block_size(
+        app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info));
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress);
+    spi_mem_worker_start_thread(app->worker);
+    spi_mem_worker_read_start(app->chip_info, app->worker, spi_mem_scene_read_callback, app);
+}
+
+bool spi_mem_scene_read_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    UNUSED(app);
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == SPIMemCustomEventViewReadCancel) {
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, SPIMemSceneChipDetect);
+        } else if(event.event == SPIMemCustomEventWorkerBlockReaded) {
+            spi_mem_view_progress_inc_progress(app->view_progress);
+        } else if(event.event == SPIMemCustomEventWorkerDone) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneVerify);
+        } else if(event.event == SPIMemCustomEventWorkerChipFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError);
+        } else if(event.event == SPIMemCustomEventWorkerFileFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_read_on_exit(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_worker_stop_thread(app->worker);
+    spi_mem_view_progress_reset(app->view_progress);
+    notification_message(app->notifications, &sequence_blink_stop);
+}

+ 46 - 0
spi_mem_manager/scenes/spi_mem_scene_read_filename.c

@@ -0,0 +1,46 @@
+#include "../spi_mem_app_i.h"
+#include "../spi_mem_files.h"
+
+void spi_mem_scene_read_filename_view_result_callback(void* context) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventTextEditResult);
+}
+
+void spi_mem_scene_read_set_random_filename(SPIMemApp* app) {
+    if(furi_string_end_with(app->file_path, SPI_MEM_FILE_EXTENSION)) {
+        size_t filename_start = furi_string_search_rchar(app->file_path, '/');
+        furi_string_left(app->file_path, filename_start);
+    }
+    name_generator_make_auto(app->text_buffer, SPI_MEM_TEXT_BUFFER_SIZE, TAG);
+}
+
+void spi_mem_scene_read_filename_on_enter(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_scene_read_set_random_filename(app);
+    text_input_set_header_text(app->text_input, "Name the dump");
+    text_input_set_result_callback(
+        app->text_input,
+        spi_mem_scene_read_filename_view_result_callback,
+        app,
+        app->text_buffer,
+        SPI_MEM_FILE_NAME_SIZE,
+        true);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewTextInput);
+}
+
+bool spi_mem_scene_read_filename_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    UNUSED(app);
+    bool success = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == SPIMemCustomEventTextEditResult) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneRead);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_read_filename_on_exit(void* context) {
+    SPIMemApp* app = context;
+    text_input_reset(app->text_input);
+}

+ 76 - 0
spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c

@@ -0,0 +1,76 @@
+#include "../spi_mem_app_i.h"
+
+typedef enum {
+    SPIMemSceneSavedFileMenuSubmenuIndexWrite,
+    SPIMemSceneSavedFileMenuSubmenuIndexCompare,
+    SPIMemSceneSavedFileMenuSubmenuIndexInfo,
+    SPIMemSceneSavedFileMenuSubmenuIndexDelete,
+} SPIMemSceneSavedFileMenuSubmenuIndex;
+
+static void spi_mem_scene_saved_file_menu_submenu_callback(void* context, uint32_t index) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void spi_mem_scene_saved_file_menu_on_enter(void* context) {
+    SPIMemApp* app = context;
+    submenu_add_item(
+        app->submenu,
+        "Write",
+        SPIMemSceneSavedFileMenuSubmenuIndexWrite,
+        spi_mem_scene_saved_file_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Compare",
+        SPIMemSceneSavedFileMenuSubmenuIndexCompare,
+        spi_mem_scene_saved_file_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Info",
+        SPIMemSceneSavedFileMenuSubmenuIndexInfo,
+        spi_mem_scene_saved_file_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Delete",
+        SPIMemSceneSavedFileMenuSubmenuIndexDelete,
+        spi_mem_scene_saved_file_menu_submenu_callback,
+        app);
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu));
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu);
+}
+
+bool spi_mem_scene_saved_file_menu_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu, event.event);
+        if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexWrite) {
+            app->mode = SPIMemModeWrite;
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect);
+            success = true;
+        }
+        if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexCompare) {
+            app->mode = SPIMemModeCompare;
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect);
+            success = true;
+        }
+        if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexDelete) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneDeleteConfirm);
+            success = true;
+        }
+        if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexInfo) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneFileInfo);
+            success = true;
+        }
+    }
+    return success;
+}
+
+void spi_mem_scene_saved_file_menu_on_exit(void* context) {
+    SPIMemApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 22 - 0
spi_mem_manager/scenes/spi_mem_scene_select_file.c

@@ -0,0 +1,22 @@
+#include "../spi_mem_app_i.h"
+#include "../spi_mem_files.h"
+
+void spi_mem_scene_select_file_on_enter(void* context) {
+    SPIMemApp* app = context;
+    if(spi_mem_file_select(app)) {
+        scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu, 0);
+        scene_manager_next_scene(app->scene_manager, SPIMemSceneSavedFileMenu);
+    } else {
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, SPIMemSceneStart);
+    }
+}
+
+bool spi_mem_scene_select_file_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void spi_mem_scene_select_file_on_exit(void* context) {
+    UNUSED(context);
+}

+ 45 - 0
spi_mem_manager/scenes/spi_mem_scene_select_model.c

@@ -0,0 +1,45 @@
+#include "../spi_mem_app_i.h"
+
+static void spi_mem_scene_select_model_submenu_callback(void* context, uint32_t index) {
+    SPIMemApp* app = context;
+    spi_mem_chip_copy_chip_info(app->chip_info, *found_chips_get(app->found_chips, index));
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void spi_mem_scene_select_model_on_enter(void* context) {
+    SPIMemApp* app = context;
+    size_t models_on_vendor = 0;
+    for(size_t index = 0; index < found_chips_size(app->found_chips); index++) {
+        if(spi_mem_chip_get_vendor_enum(*found_chips_get(app->found_chips, index)) !=
+           app->chip_vendor_enum)
+            continue;
+        submenu_add_item(
+            app->submenu,
+            spi_mem_chip_get_model_name(*found_chips_get(app->found_chips, index)),
+            index,
+            spi_mem_scene_select_model_submenu_callback,
+            app);
+        models_on_vendor++;
+    }
+    if(models_on_vendor == 1) spi_mem_scene_select_model_submenu_callback(context, 0);
+    submenu_set_header(app->submenu, "Choose chip model");
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSelectVendor));
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu);
+}
+
+bool spi_mem_scene_select_model_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, event.event);
+        scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetected);
+        success = true;
+    }
+    return success;
+}
+
+void spi_mem_scene_select_model_on_exit(void* context) {
+    SPIMemApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 70 - 0
spi_mem_manager/scenes/spi_mem_scene_select_vendor.c

@@ -0,0 +1,70 @@
+#include "../spi_mem_app_i.h"
+#include <m-array.h>
+#include <m-algo.h>
+
+ARRAY_DEF(vendors, uint32_t)
+ALGO_DEF(vendors, ARRAY_OPLIST(vendors))
+
+static void spi_mem_scene_select_vendor_submenu_callback(void* context, uint32_t index) {
+    SPIMemApp* app = context;
+    app->chip_vendor_enum = index;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+static void spi_mem_scene_select_vendor_sort_vendors(SPIMemApp* app, vendors_t vendors_arr) {
+    for(size_t index = 0; index < found_chips_size(app->found_chips); index++) {
+        vendors_push_back(
+            vendors_arr, spi_mem_chip_get_vendor_enum(*found_chips_get(app->found_chips, index)));
+    }
+    vendors_uniq(vendors_arr);
+}
+
+void spi_mem_scene_select_vendor_on_enter(void* context) {
+    SPIMemApp* app = context;
+    vendors_t vendors_arr;
+    vendors_init(vendors_arr);
+    spi_mem_scene_select_vendor_sort_vendors(app, vendors_arr);
+    size_t vendors_arr_size = vendors_size(vendors_arr);
+    if(vendors_arr_size == 1)
+        spi_mem_scene_select_vendor_submenu_callback(context, *vendors_get(vendors_arr, 0));
+    for(size_t index = 0; index < vendors_arr_size; index++) {
+        uint32_t vendor_enum = *vendors_get(vendors_arr, index);
+        submenu_add_item(
+            app->submenu,
+            spi_mem_chip_get_vendor_name_by_enum(vendor_enum),
+            vendor_enum,
+            spi_mem_scene_select_vendor_submenu_callback,
+            app);
+    }
+    vendors_clear(vendors_arr);
+    submenu_set_header(app->submenu, "Choose chip vendor");
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSelectVendor));
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu);
+}
+
+static void spi_mem_scene_select_vendor_set_previous_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneStart;
+    if(app->mode == SPIMemModeCompare || app->mode == SPIMemModeWrite)
+        scene = SPIMemSceneSavedFileMenu;
+    scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene);
+}
+
+bool spi_mem_scene_select_vendor_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        spi_mem_scene_select_vendor_set_previous_scene(app);
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, event.event);
+        scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectModel);
+        success = true;
+    }
+    return success;
+}
+
+void spi_mem_scene_select_vendor_on_exit(void* context) {
+    SPIMemApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 84 - 0
spi_mem_manager/scenes/spi_mem_scene_start.c

@@ -0,0 +1,84 @@
+#include "../spi_mem_app_i.h"
+
+typedef enum {
+    SPIMemSceneStartSubmenuIndexRead,
+    SPIMemSceneStartSubmenuIndexSaved,
+    SPIMemSceneStartSubmenuIndexErase,
+    SPIMemSceneStartSubmenuIndexWiring,
+    SPIMemSceneStartSubmenuIndexAbout
+} SPIMemSceneStartSubmenuIndex;
+
+static void spi_mem_scene_start_submenu_callback(void* context, uint32_t index) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void spi_mem_scene_start_on_enter(void* context) {
+    SPIMemApp* app = context;
+    submenu_add_item(
+        app->submenu,
+        "Read",
+        SPIMemSceneStartSubmenuIndexRead,
+        spi_mem_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Saved",
+        SPIMemSceneStartSubmenuIndexSaved,
+        spi_mem_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Erase",
+        SPIMemSceneStartSubmenuIndexErase,
+        spi_mem_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Wiring",
+        SPIMemSceneStartSubmenuIndexWiring,
+        spi_mem_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "About",
+        SPIMemSceneStartSubmenuIndexAbout,
+        spi_mem_scene_start_submenu_callback,
+        app);
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneStart));
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu);
+}
+
+bool spi_mem_scene_start_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(app->scene_manager, SPIMemSceneStart, event.event);
+        if(event.event == SPIMemSceneStartSubmenuIndexRead) {
+            app->mode = SPIMemModeRead;
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect);
+            success = true;
+        } else if(event.event == SPIMemSceneStartSubmenuIndexSaved) {
+            furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile);
+            success = true;
+        } else if(event.event == SPIMemSceneStartSubmenuIndexErase) {
+            app->mode = SPIMemModeErase;
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect);
+            success = true;
+        } else if(event.event == SPIMemSceneStartSubmenuIndexWiring) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneWiring);
+            success = true;
+        } else if(event.event == SPIMemSceneStartSubmenuIndexAbout) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneAbout);
+            success = true;
+        }
+    }
+    return success;
+}
+
+void spi_mem_scene_start_on_exit(void* context) {
+    SPIMemApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 56 - 0
spi_mem_manager/scenes/spi_mem_scene_storage_error.c

@@ -0,0 +1,56 @@
+#include "../spi_mem_app_i.h"
+
+static void spi_mem_scene_storage_error_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    SPIMemApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void spi_mem_scene_storage_error_on_enter(void* context) {
+    SPIMemApp* app = context;
+    widget_add_button_element(
+        app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_storage_error_widget_callback, app);
+    widget_add_string_element(
+        app->widget, 85, 15, AlignCenter, AlignBottom, FontPrimary, "Storage error");
+    widget_add_string_multiline_element(
+        app->widget,
+        85,
+        52,
+        AlignCenter,
+        AlignBottom,
+        FontSecondary,
+        "Error while\nworking with\nfilesystem");
+    widget_add_icon_element(app->widget, 5, 6, &I_SDQuestion_35x43);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+static void spi_mem_scene_storage_error_set_previous_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneChipDetect;
+    if(app->mode == SPIMemModeRead) scene = SPIMemSceneStart;
+    if(app->mode == SPIMemModeErase) scene = SPIMemSceneStart;
+    if(app->mode == SPIMemModeDelete) scene = SPIMemSceneStart;
+    scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene);
+}
+
+bool spi_mem_scene_storage_error_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        spi_mem_scene_storage_error_set_previous_scene(app);
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == GuiButtonTypeLeft) {
+            spi_mem_scene_storage_error_set_previous_scene(app);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_storage_error_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 40 - 0
spi_mem_manager/scenes/spi_mem_scene_success.c

@@ -0,0 +1,40 @@
+#include "../spi_mem_app_i.h"
+
+static void spi_mem_scene_success_popup_callback(void* context) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventPopupBack);
+}
+
+void spi_mem_scene_success_on_enter(void* context) {
+    SPIMemApp* app = context;
+    popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(app->popup, "Success!", 5, 7, AlignLeft, AlignTop);
+    popup_set_callback(app->popup, spi_mem_scene_success_popup_callback);
+    popup_set_context(app->popup, app);
+    popup_set_timeout(app->popup, 1500);
+    popup_enable_timeout(app->popup);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewPopup);
+}
+
+static void spi_mem_scene_success_set_previous_scene(SPIMemApp* app) {
+    uint32_t scene = SPIMemSceneSelectFile;
+    if(app->mode == SPIMemModeErase) scene = SPIMemSceneStart;
+    scene_manager_search_and_switch_to_another_scene(app->scene_manager, scene);
+}
+
+bool spi_mem_scene_success_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == SPIMemCustomEventPopupBack) {
+            spi_mem_scene_success_set_previous_scene(app);
+        }
+    }
+    return success;
+}
+
+void spi_mem_scene_success_on_exit(void* context) {
+    SPIMemApp* app = context;
+    popup_reset(app->popup);
+}

+ 59 - 0
spi_mem_manager/scenes/spi_mem_scene_verify.c

@@ -0,0 +1,59 @@
+#include "../spi_mem_app_i.h"
+#include "../spi_mem_files.h"
+#include "../lib/spi/spi_mem_chip.h"
+#include "../lib/spi/spi_mem_tools.h"
+
+void spi_mem_scene_verify_view_result_callback(void* context) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewVerifySkip);
+}
+
+static void spi_mem_scene_verify_callback(void* context, SPIMemCustomEventWorker event) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void spi_mem_scene_verify_on_enter(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_view_progress_set_verify_callback(
+        app->view_progress, spi_mem_scene_verify_view_result_callback, app);
+    notification_message(app->notifications, &sequence_blink_start_cyan);
+    spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info));
+    spi_mem_view_progress_set_file_size(app->view_progress, spi_mem_file_get_size(app));
+    spi_mem_view_progress_set_block_size(
+        app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info));
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress);
+    spi_mem_worker_start_thread(app->worker);
+    spi_mem_worker_verify_start(app->chip_info, app->worker, spi_mem_scene_verify_callback, app);
+}
+
+bool spi_mem_scene_verify_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    UNUSED(app);
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == SPIMemCustomEventViewVerifySkip) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess);
+        } else if(event.event == SPIMemCustomEventWorkerBlockReaded) {
+            spi_mem_view_progress_inc_progress(app->view_progress);
+        } else if(event.event == SPIMemCustomEventWorkerChipFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError);
+        } else if(event.event == SPIMemCustomEventWorkerFileFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError);
+        } else if(event.event == SPIMemCustomEventWorkerDone) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess);
+        } else if(event.event == SPIMemCustomEventWorkerVerifyFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneVerifyError);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_verify_on_exit(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_worker_stop_thread(app->worker);
+    spi_mem_view_progress_reset(app->view_progress);
+    notification_message(app->notifications, &sequence_blink_stop);
+}

+ 43 - 0
spi_mem_manager/scenes/spi_mem_scene_verify_error.c

@@ -0,0 +1,43 @@
+#include "../spi_mem_app_i.h"
+
+static void spi_mem_scene_verify_error_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    SPIMemApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void spi_mem_scene_verify_error_on_enter(void* context) {
+    SPIMemApp* app = context;
+    widget_add_button_element(
+        app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_verify_error_widget_callback, app);
+    widget_add_string_element(
+        app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Verification error");
+    widget_add_string_element(
+        app->widget, 64, 21, AlignCenter, AlignBottom, FontSecondary, "Data mismatch");
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+bool spi_mem_scene_verify_error_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+        scene_manager_search_and_switch_to_previous_scene(
+            app->scene_manager, SPIMemSceneChipDetect);
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, SPIMemSceneChipDetect);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_verify_error_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 18 - 0
spi_mem_manager/scenes/spi_mem_scene_wiring.c

@@ -0,0 +1,18 @@
+#include "../spi_mem_app_i.h"
+#include "../lib/spi/spi_mem_chip.h"
+
+void spi_mem_scene_wiring_on_enter(void* context) {
+    SPIMemApp* app = context;
+    widget_add_icon_element(app->widget, 0, 0, &I_Wiring_SPI_128x64);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget);
+}
+
+bool spi_mem_scene_wiring_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+void spi_mem_scene_wiring_on_exit(void* context) {
+    SPIMemApp* app = context;
+    widget_reset(app->widget);
+}

+ 58 - 0
spi_mem_manager/scenes/spi_mem_scene_write.c

@@ -0,0 +1,58 @@
+#include "../spi_mem_app_i.h"
+#include "../spi_mem_files.h"
+#include "../lib/spi/spi_mem_chip.h"
+#include "../lib/spi/spi_mem_tools.h"
+
+void spi_mem_scene_write_progress_view_result_callback(void* context) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewReadCancel);
+}
+
+static void spi_mem_scene_write_callback(void* context, SPIMemCustomEventWorker event) {
+    SPIMemApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void spi_mem_scene_write_on_enter(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_view_progress_set_write_callback(
+        app->view_progress, spi_mem_scene_write_progress_view_result_callback, app);
+    notification_message(app->notifications, &sequence_blink_start_cyan);
+    spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info));
+    spi_mem_view_progress_set_file_size(app->view_progress, spi_mem_file_get_size(app));
+    spi_mem_view_progress_set_block_size(
+        app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info));
+    view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress);
+    spi_mem_worker_start_thread(app->worker);
+    spi_mem_worker_write_start(app->chip_info, app->worker, spi_mem_scene_write_callback, app);
+}
+
+bool spi_mem_scene_write_on_event(void* context, SceneManagerEvent event) {
+    SPIMemApp* app = context;
+    UNUSED(app);
+    bool success = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        success = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        success = true;
+        if(event.event == SPIMemCustomEventViewReadCancel) {
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, SPIMemSceneChipDetect);
+        } else if(event.event == SPIMemCustomEventWorkerBlockReaded) {
+            spi_mem_view_progress_inc_progress(app->view_progress);
+        } else if(event.event == SPIMemCustomEventWorkerDone) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneVerify);
+        } else if(event.event == SPIMemCustomEventWorkerChipFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError);
+        } else if(event.event == SPIMemCustomEventWorkerFileFail) {
+            scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError);
+        }
+    }
+    return success;
+}
+void spi_mem_scene_write_on_exit(void* context) {
+    SPIMemApp* app = context;
+    spi_mem_worker_stop_thread(app->worker);
+    spi_mem_view_progress_reset(app->view_progress);
+    notification_message(app->notifications, &sequence_blink_stop);
+}

+ 112 - 0
spi_mem_manager/spi_mem_app.c

@@ -0,0 +1,112 @@
+#include <furi_hal.h>
+#include "spi_mem_app_i.h"
+#include "spi_mem_files.h"
+#include "lib/spi/spi_mem_chip_i.h"
+
+static bool spi_mem_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    SPIMemApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool spi_mem_back_event_callback(void* context) {
+    furi_assert(context);
+    SPIMemApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+SPIMemApp* spi_mem_alloc(void) {
+    SPIMemApp* instance = malloc(sizeof(SPIMemApp)); //-V799
+
+    instance->file_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
+    instance->gui = furi_record_open(RECORD_GUI);
+    instance->notifications = furi_record_open(RECORD_NOTIFICATION);
+    instance->view_dispatcher = view_dispatcher_alloc();
+    instance->scene_manager = scene_manager_alloc(&spi_mem_scene_handlers, instance);
+    instance->submenu = submenu_alloc();
+    instance->dialog_ex = dialog_ex_alloc();
+    instance->popup = popup_alloc();
+    instance->worker = spi_mem_worker_alloc();
+    instance->dialogs = furi_record_open(RECORD_DIALOGS);
+    instance->storage = furi_record_open(RECORD_STORAGE);
+    instance->widget = widget_alloc();
+    instance->chip_info = malloc(sizeof(SPIMemChip));
+    found_chips_init(instance->found_chips);
+    instance->view_progress = spi_mem_view_progress_alloc();
+    instance->view_detect = spi_mem_view_detect_alloc();
+    instance->text_input = text_input_alloc();
+    instance->mode = SPIMemModeUnknown;
+
+    // Migrate data from old sd-card folder
+    storage_common_migrate(instance->storage, EXT_PATH("spimem"), STORAGE_APP_DATA_PATH_PREFIX);
+
+    view_dispatcher_enable_queue(instance->view_dispatcher);
+    view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
+    view_dispatcher_set_custom_event_callback(
+        instance->view_dispatcher, spi_mem_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        instance->view_dispatcher, spi_mem_back_event_callback);
+    view_dispatcher_attach_to_gui(
+        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SPIMemViewSubmenu, submenu_get_view(instance->submenu));
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SPIMemViewDialogEx, dialog_ex_get_view(instance->dialog_ex));
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SPIMemViewPopup, popup_get_view(instance->popup));
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SPIMemViewWidget, widget_get_view(instance->widget));
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        SPIMemViewProgress,
+        spi_mem_view_progress_get_view(instance->view_progress));
+    view_dispatcher_add_view(
+        instance->view_dispatcher,
+        SPIMemViewDetect,
+        spi_mem_view_detect_get_view(instance->view_detect));
+    view_dispatcher_add_view(
+        instance->view_dispatcher, SPIMemViewTextInput, text_input_get_view(instance->text_input));
+
+    furi_hal_power_enable_otg();
+    furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external);
+    scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart);
+    return instance;
+} //-V773
+
+void spi_mem_free(SPIMemApp* instance) {
+    view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu);
+    view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewDialogEx);
+    view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewPopup);
+    view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewWidget);
+    view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewProgress);
+    view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewDetect);
+    view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewTextInput);
+    spi_mem_view_progress_free(instance->view_progress);
+    spi_mem_view_detect_free(instance->view_detect);
+    submenu_free(instance->submenu);
+    dialog_ex_free(instance->dialog_ex);
+    popup_free(instance->popup);
+    widget_free(instance->widget);
+    text_input_free(instance->text_input);
+    view_dispatcher_free(instance->view_dispatcher);
+    scene_manager_free(instance->scene_manager);
+    spi_mem_worker_free(instance->worker);
+    free(instance->chip_info);
+    found_chips_clear(instance->found_chips);
+    furi_record_close(RECORD_STORAGE);
+    furi_record_close(RECORD_DIALOGS);
+    furi_record_close(RECORD_NOTIFICATION);
+    furi_record_close(RECORD_GUI);
+    furi_string_free(instance->file_path);
+    furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_external);
+    furi_hal_power_disable_otg();
+    free(instance);
+}
+
+int32_t spi_mem_app(void* p) {
+    UNUSED(p);
+    SPIMemApp* instance = spi_mem_alloc();
+    view_dispatcher_run(instance->view_dispatcher);
+    spi_mem_free(instance);
+    return 0;
+}

+ 3 - 0
spi_mem_manager/spi_mem_app.h

@@ -0,0 +1,3 @@
+#pragma once
+
+typedef struct SPIMemApp SPIMemApp;

+ 78 - 0
spi_mem_manager/spi_mem_app_i.h

@@ -0,0 +1,78 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal_spi.h>
+#include <furi_hal_spi_config.h>
+#include "spi_mem_app.h"
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/popup.h>
+#include <notification/notification_messages.h>
+#include <dialogs/dialogs.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/text_input.h>
+#include <storage/storage.h>
+#include <toolbox/path.h>
+#include <toolbox/name_generator.h>
+#include "scenes/spi_mem_scene.h"
+#include "lib/spi/spi_mem_worker.h"
+#include "spi_mem_manager_icons.h"
+#include "views/spi_mem_view_progress.h"
+#include "views/spi_mem_view_detect.h"
+
+#define TAG "SPIMem"
+#define SPI_MEM_FILE_EXTENSION ".bin"
+#define SPI_MEM_FILE_NAME_SIZE 100
+#define SPI_MEM_TEXT_BUFFER_SIZE 128
+
+typedef enum {
+    SPIMemModeRead,
+    SPIMemModeWrite,
+    SPIMemModeCompare,
+    SPIMemModeErase,
+    SPIMemModeDelete,
+    SPIMemModeUnknown
+} SPIMemMode;
+
+struct SPIMemApp {
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    SceneManager* scene_manager;
+    Submenu* submenu;
+    DialogEx* dialog_ex;
+    Popup* popup;
+    NotificationApp* notifications;
+    FuriString* file_path;
+    DialogsApp* dialogs;
+    Storage* storage;
+    File* file;
+    Widget* widget;
+    SPIMemWorker* worker;
+    SPIMemChip* chip_info;
+    found_chips_t found_chips;
+    uint32_t chip_vendor_enum;
+    SPIMemProgressView* view_progress;
+    SPIMemDetectView* view_detect;
+    TextInput* text_input;
+    SPIMemMode mode;
+    char text_buffer[SPI_MEM_TEXT_BUFFER_SIZE + 1];
+};
+
+typedef enum {
+    SPIMemViewSubmenu,
+    SPIMemViewDialogEx,
+    SPIMemViewPopup,
+    SPIMemViewWidget,
+    SPIMemViewTextInput,
+    SPIMemViewProgress,
+    SPIMemViewDetect
+} SPIMemView;
+
+typedef enum {
+    SPIMemCustomEventViewReadCancel,
+    SPIMemCustomEventViewVerifySkip,
+    SPIMemCustomEventTextEditResult,
+    SPIMemCustomEventPopupBack
+} SPIMemCustomEvent;

+ 68 - 0
spi_mem_manager/spi_mem_files.c

@@ -0,0 +1,68 @@
+#include "spi_mem_app_i.h"
+
+bool spi_mem_file_delete(SPIMemApp* app) {
+    return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path)));
+}
+
+bool spi_mem_file_select(SPIMemApp* app) {
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px);
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+    bool success =
+        dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
+    return success;
+}
+
+bool spi_mem_file_create_open(SPIMemApp* app) {
+    bool success = false;
+    app->file = storage_file_alloc(app->storage);
+    do {
+        if(furi_string_end_with(app->file_path, SPI_MEM_FILE_EXTENSION)) {
+            if(!spi_mem_file_delete(app)) break;
+            size_t filename_start = furi_string_search_rchar(app->file_path, '/');
+            furi_string_left(app->file_path, filename_start);
+        }
+        furi_string_cat_printf(app->file_path, "/%s%s", app->text_buffer, SPI_MEM_FILE_EXTENSION);
+        if(!storage_file_open(
+               app->file, furi_string_get_cstr(app->file_path), FSAM_WRITE, FSOM_CREATE_NEW))
+            break;
+        success = true;
+    } while(0);
+    if(!success) { //-V547
+        dialog_message_show_storage_error(app->dialogs, "Cannot save\nfile");
+    }
+    return success;
+}
+
+bool spi_mem_file_open(SPIMemApp* app) {
+    app->file = storage_file_alloc(app->storage);
+    if(!storage_file_open(
+           app->file, furi_string_get_cstr(app->file_path), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
+        dialog_message_show_storage_error(app->dialogs, "Cannot save\nfile");
+        return false;
+    }
+    return true;
+}
+
+bool spi_mem_file_write_block(SPIMemApp* app, uint8_t* data, size_t size) {
+    if(storage_file_write(app->file, data, size) != size) return false;
+    return true;
+}
+
+bool spi_mem_file_read_block(SPIMemApp* app, uint8_t* data, size_t size) {
+    if(storage_file_read(app->file, data, size) != size) return false;
+    return true;
+}
+
+void spi_mem_file_close(SPIMemApp* app) {
+    storage_file_close(app->file);
+    storage_file_free(app->file);
+}
+
+size_t spi_mem_file_get_size(SPIMemApp* app) {
+    FileInfo file_info;
+    if(storage_common_stat(app->storage, furi_string_get_cstr(app->file_path), &file_info) !=
+       FSE_OK)
+        return 0;
+    return file_info.size;
+}

+ 13 - 0
spi_mem_manager/spi_mem_files.h

@@ -0,0 +1,13 @@
+#pragma once
+#include "spi_mem_app.h"
+
+bool spi_mem_file_select(SPIMemApp* app);
+bool spi_mem_file_create(SPIMemApp* app, const char* file_name);
+bool spi_mem_file_delete(SPIMemApp* app);
+bool spi_mem_file_create_open(SPIMemApp* app);
+bool spi_mem_file_open(SPIMemApp* app);
+bool spi_mem_file_write_block(SPIMemApp* app, uint8_t* data, size_t size);
+bool spi_mem_file_read_block(SPIMemApp* app, uint8_t* data, size_t size);
+void spi_mem_file_close(SPIMemApp* app);
+void spi_mem_file_show_storage_error(SPIMemApp* app, const char* error_text);
+size_t spi_mem_file_get_size(SPIMemApp* app);

+ 7 - 0
spi_mem_manager/tools/README.md

@@ -0,0 +1,7 @@
+This utility can convert nofeletru's UsbAsp-flash's chiplist.xml to C array
+
+Usage:
+```bash
+    ./chiplist_convert.py chiplist/chiplist.xml
+    mv spi_mem_chip_arr.c ../lib/spi/spi_mem_chip_arr.c
+```

+ 22 - 0
spi_mem_manager/tools/chiplist/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 nofeletru
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 984 - 0
spi_mem_manager/tools/chiplist/chiplist.xml

@@ -0,0 +1,984 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!---
+    This file is downloaded from nofeletru's UsbAsp-flash repository
+    https://github.com/nofeletru/UsbAsp-flash/blob/master/chiplist.xml
+    And distributed under MIT license
+-->
+<!--- 
+  size - размер данных микросхемы памяти в байтах(DEC)
+  page - размер страницы микросхемы памяти в байтах(DEC). Для SST AAI Word programm - SSTW. Для SST AAI Byte programm - SSTB. 
+  id - индефикатор микросхемы памяти(HEX). Поддерживаются опкоды 9F, 90, AB, 15
+  spicmd - команды для серий микросхем памяти 25, 45, 95(EEPROM)
+  script - имя файла скрипта из папки scripts  
+ -->
+<chiplist>
+  <SPI>
+    <KB90XX>
+      <KB9012 id="0" page="128" size="131072" spicmd="KB"/>
+    </KB90XX>
+	<ADESTO>
+	  <AT25DN256 id="1F4000" page="256" size="32768"/>
+	</ADESTO>
+    <AMIC>
+      <A25L05PT id="372020" page="256" size="65536"/>
+      <A25L05PU id="372010" page="256" size="65536"/>
+      <A25L10PT id="372021" page="256" size="131072"/>
+      <A25L10PU id="372011" page="256" size="131072"/>
+      <A25L20PT id="372022" page="256" size="262144"/>
+      <A25L20PU id="372012" page="256" size="262144"/>
+      <A25L40PT id="372023" page="256" size="524288"/>
+      <A25L40PU id="372013" page="256" size="524288"/>
+      <A25L80PT id="372024" page="256" size="1048576"/>
+      <A25L80PU id="372014" page="256" size="1048576"/>
+      <A25L16PT id="372025" page="256" size="2097152"/>
+      <A25L16PU id="372015" page="256" size="2097152"/>
+      <A25L512 id="373010" page="256" size="65536"/>
+      <A25L010 id="373011" page="256" size="131072"/>
+      <A25L020 id="373012" page="256" size="262144"/>
+      <A25L040 id="373013" page="256" size="524288"/>
+      <A25L080 id="373014" page="256" size="1048576"/>
+      <A25L016 id="373015" page="256" size="2097152"/>
+      <A25L032 id="373016" page="256" size="4194304"/>
+      <A25LQ16 id="374015" page="256" size="2097152"/>
+      <A25LQ32A id="374016" page="256" size="4194304"/>
+    </AMIC>
+    <APLUS>
+      <AF25BC08 page="32" size="1024" spicmd="95"/>
+      <AF25BC16 page="32" size="2048" spicmd="95"/>
+      <AF25BC32 page="32" size="4096" spicmd="95"/>
+      <AF25BC64 page="32" size="8192" spicmd="95"/>
+      <AF25BC128 page="64" size="16384" spicmd="95"/>
+      <AF25BC256 page="64" size="32768" spicmd="95"/>
+    </APLUS>
+	<Boya>
+	  <BY25D80 id="684014" page="256" size="1048576"/>
+	</Boya>
+    <CATALYST_CSI>
+      <CAT25010 page="16" size="128" spicmd="95"/>
+      <CAT25020 page="16" size="256" spicmd="95"/>
+      <CAT25040 page="16" size="512" spicmd="95"/>
+      <CAT25080 page="32" size="1024" spicmd="95"/>
+      <CAT25160 page="32" size="2048" spicmd="95"/>
+      <CAT25320 page="32" size="4096" spicmd="95"/>
+      <CAT25640 page="64" size="8192" spicmd="95"/>
+      <CAT25128 page="64" size="16384" spicmd="95"/>
+      <CAT25256 page="64" size="32768" spicmd="95"/>
+      <CAT25C01 page="16" size="128" spicmd="95"/>
+      <CAT25C02 page="16" size="256" spicmd="95"/>
+      <CAT25C03 page="16" size="256" spicmd="95"/>
+      <CAT25C04 page="16" size="512" spicmd="95"/>
+      <CAT25C05 page="16" size="512" spicmd="95"/>
+      <CAT25C08 page="32" size="1024" spicmd="95"/>
+      <CAT25C09 page="32" size="1024" spicmd="95"/>
+      <CAT25C16 page="32" size="2048" spicmd="95"/>
+      <CAT25C17 page="32" size="2048" spicmd="95"/>
+      <CAT25C32 page="32" size="4096" spicmd="95"/>
+      <CAT25C33 page="32" size="4096" spicmd="95"/>
+      <CAT25C64 page="64" size="8192" spicmd="95"/>
+      <CAT25C65 page="64" size="8192" spicmd="95"/>
+      <CAT25C128 page="64" size="16384" spicmd="95"/>
+      <CAT25C256 page="64" size="32768" spicmd="95"/>
+    </CATALYST_CSI>
+    <EON>
+      <EN25B05 id="1C2010" page="256" size="65536"/>
+      <EN25B05T id="1C2010" page="256" size="65536"/>
+      <EN25B10 id="1C2011" page="256" size="131072"/>
+      <EN25B10T id="1C2011" page="256" size="131072"/>
+      <EN25B20 id="1C2012" page="256" size="262144"/>
+      <EN25B20T id="1C2012" page="256" size="262144"/>
+      <EN25B40 id="1C2013" page="256" size="524288"/>
+      <EN25B40T id="1C2013" page="256" size="524288"/>
+      <EN25B80 id="1C2014" page="256" size="1048576"/>
+      <EN25B80T id="1C2014" page="256" size="1048576"/>
+      <EN25B16 id="1C2015" page="256" size="2097152"/>
+      <EN25B16T id="1C2015" page="256" size="2097152"/>
+      <EN25B32 id="1C2016" page="256" size="4194304"/>
+      <EN25B32T id="1C2016" page="256" size="4194304"/>
+      <EN25B64 id="1C2017" page="256" size="8388608"/>
+      <EN25B64T id="1C2017" page="256" size="8388608"/>
+      <EN25F05 id="1C3110" otp="240" page="256" size="65536"/>
+      <EN25F10 id="1C3111" otp="496" page="256" size="131072"/>
+      <EN25F20 id="1C3112" otp="1008" page="256" size="262144"/>
+      <EN25F40 id="1C3113" otp="2032" page="256" size="524288"/>
+      <EN25F80 id="1C3114" otp="4080" page="256" size="1048576"/>
+      <EN25F16 id="1C3115" otp="8176" page="256" size="2097152"/>
+      <EN25F32 id="1C3116" otp="16368" page="256" size="4194304"/>
+      <EN25LF05 id="1C3110" page="256" size="65536"/>
+      <EN25LF10 id="1C3111" page="256" size="131072"/>
+      <EN25LF20 id="1C3112" page="256" size="262144"/>
+      <EN25LF40 id="1C3113" page="256" size="524288"/>
+      <EN25P05 id="1C2010" page="256" size="65536"/>
+      <EN25P10 id="1C2011" page="256" size="131072"/>
+      <EN25P20 id="1C2012" page="256" size="262144"/>
+      <EN25P40 id="1C2013" page="256" size="524288"/>
+      <EN25P80 id="1C2014" page="256" size="1048576"/>
+      <EN25P16 id="1C2015" page="256" size="2097152"/>
+      <EN25P32 id="1C2016" page="256" size="4194304"/>
+      <EN25P64 id="1C2017" page="256" size="8388608"/>
+      <EN25Q40 id="1C3013" otp="2032" page="256" size="524288"/>
+      <EN25Q80A id="1C3014" otp="4080" page="256" size="1048576"/>
+      <EN25Q16A id="1C3015" otp="8176" page="256" size="2097152"/>
+      <EN25Q32A id="1C3016" otp="16368" page="256" size="4194304"/>
+      <EN25Q32A id="1C7016" otp="16368" page="256" size="4194304"/>
+      <EN25Q32B id="1C3016" otp="16368" page="256" size="4194304"/>
+      <EN25Q64 id="1C3017" otp="32752" page="256" size="8388608"/>
+      <EN25Q128 id="1C3018" otp="65520" page="256" size="16777216"/>
+      <EN25QH16 id="1C7015" otp="8176" page="256" size="2097152"/>
+      <EN25QH32 id="1C7016" otp="16368" page="256" size="4194304"/>
+      <EN25QH64 id="1C7017" otp="32752" page="256" size="8388608"/>
+      <EN25QH128 id="1C7018" otp="65520" page="256" size="16777216"/>
+	  <EN25QH256 id="1C7019" otp="0" page="256" size="33554432"/>
+      <EN25T80 id="1C5114" otp="4080" page="256" size="1048576"/>
+      <EN25T16 id="1C5115" otp="8176" page="256" size="2097152"/>
+      <EN25F64 id="1C3117" otp="32752" page="256" size="8388608"/>
+    </EON>
+    <PMC>
+      <PM25LD256C id="9D2F" page="256" size="32768"/>
+      <PM25LD512 id="9D20" page="256" size="65536"/>
+      <PM25LD512C id="9D20" page="256" size="65536"/>
+      <PM25LD010 id="9D21" page="256" size="131072"/>
+      <PM25LD010C id="9D21" page="256" size="131072"/>
+      <PM25LD020 id="9D22" page="256" size="262144"/>
+      <PM25LD020C id="9D22" page="256" size="262144"/>
+      <PM25LD040 id="9D7E" page="256" size="524288"/>
+      <PM25LD040C id="9D7E" page="256" size="524288"/>
+      <PM25LV512 id="9D7B" page="256" size="65536"/>
+      <PM25LV512A id="9D7B" page="256" size="65536"/>
+      <PM25LV010 id="9D7C" page="256" size="131072"/>
+      <PM25LV010A id="9D7C" page="256" size="131072"/>
+      <PM25LV020 id="9D7D" page="256" size="262144"/>
+      <PM25LV040 id="9D7E" page="256" size="524288"/>
+      <PM25LV080B id="9D13" page="256" size="1048576"/>
+      <PM25LV016B id="9D14" page="256" size="2097152"/>
+      <PM25WD020 id="9D32" page="256" size="262144"/>
+      <PM25WD040 id="9D33" page="256" size="524288"/>
+    </PMC>
+	<PFLASH>
+	  <Pm25LV010 id="7F9D7C" page="256" size="131072"/>
+	  <Pm25LD010 id="7F9D21" page="256" size="131072"/>
+	  <Pm25LV020 id="7F9D22" page="256" size="262144"/>
+	  <Pm25W020 id="7F9D7D" page="256" size="262144"/>
+	  <Pm25LV040 id="7F9D7E" page="256" size="524288"/>
+	</PFLASH>
+	<TERRA>
+	  <TS25L512A id="373010" page="256" size="65536"/>
+	  <TS25L010A id="373011" page="256" size="131072"/>
+	  <TS25L020A id="373012" page="256" size="262144"/>
+	  <TS25L16AP id="202015" page="256" size="2097152"/>
+	  <TS25L16BP id="202015" page="256" size="2097152"/>
+	  <ZP25L16P id="202015" page="256" size="2097152"/>
+	  <TS25L16PE id="208015" page="256" size="2097152"/>
+	  <TS25L80PE id="208014" page="256" size="1048576"/>
+	  <TS25L032A id="373016" page="256" size="4194304"/>
+	  <TS25L40P id="202013" page="256" size="524288"/>
+	</TERRA>
+	<Generalplus> 
+	  <GPR25L005E id="C22010" page="256" size="65536"/>
+	  <GPR25L161B id="C22015" page="256" size="262144"/>
+	  <GPR25L020B id="C22012" page="256" size="262144"/>
+	  <GPR25L3203F id="C22016" page="256" size="4194304" script="GPR25L3203F_OTP.pas"/>
+	</Generalplus>
+    <DEUTRON>
+      <AC25LV512 id="9D7B00" page="256" size="65536"/>
+      <AC25LV010 id="9D7C00" page="256" size="131072"/>
+    </DEUTRON>
+    <EFST>
+      <EM25LV512 id="9D7B00" page="256" size="65536"/>
+      <EM25LV010 id="9D7C00" page="256" size="131072"/>
+      <F25L004A id="8C2013" page="256" size="524288"/>
+      <F25L008A id="8C2014" page="256" size="1048576"/>
+      <F25L016A id="8C2015" page="256" size="2097152"/>
+      <F25L04UA id="8C8C8C" page="256" size="524288"/>
+      <F25L04P id="8C2013" page="256" size="524288"/>
+      <F25S04P id="8C3013" page="256" size="524288"/>
+      <F25L08P id="8C2014" page="256" size="1048576"/>
+      <F25L16P id="8C2015" page="256" size="2097152"/>
+      <F25L32P id="8C2016" page="256" size="4194304"/>
+      <F25L32Q id="8C4016" page="256" size="4194304"/>
+    </EFST>
+    <EXCELSEMI>
+      <ES25P10 id="4A2011" page="256" size="131072"/>
+      <ES25P20 id="4A2012" page="256" size="262144"/>
+      <ES25P40 id="4A2013" page="256" size="524288"/>
+      <ES25P80 id="4A2014" page="256" size="1048576"/>
+      <ES25P16 id="4A2015" page="256" size="2097152"/>
+      <ES25P32 id="4A2016" page="256" size="4194304"/>
+      <ES25M40A id="4A3213" page="256" size="524288"/>
+      <ES25M80A id="4A3214" page="256" size="1048576"/>
+      <ES25M16A id="4A3215" page="256" size="2097152"/>
+    </EXCELSEMI>
+    <FIDELIX>
+      <FM25Q08A id="F83214" page="256" size="1048576"/>
+      <FM25Q16A id="F83215" page="256" size="2097152"/>
+      <FM25Q16B id="F83215" page="256" size="2097152"/>
+      <FM25Q32A id="F83216" page="256" size="4194304"/>
+      <FM25Q64A id="F83217" page="256" size="8388608"/>
+    </FIDELIX>
+    <GIANTEC>
+      <GT25C01 page="8" size="128" spicmd="95"/>
+      <GT25C02 page="8" size="256" spicmd="95"/>
+      <GT25C04 page="8" size="512" spicmd="95"/>
+      <GT25C08 page="32" size="1024" spicmd="95"/>
+      <GT25C16 page="32" size="2048" spicmd="95"/>
+      <GT25C32 page="32" size="4096" spicmd="95"/>
+      <GT25C32A page="32" size="4096" spicmd="95"/>
+      <GT25C64 page="32" size="8192" spicmd="95"/>
+      <GT25C128 page="64" size="16384" spicmd="95"/>
+      <GT25C128A page="64" size="16384" spicmd="95"/>
+      <GT25C256 page="64" size="32768" spicmd="95"/>
+    </GIANTEC>
+    <GIGADEVICE>
+      <GD25D40 id="C83013" page="256" size="524288"/>
+      <GD25D80 id="C83014" page="256" size="1048576"/>
+      <GD25F40 id="C82013" page="256" size="524288"/>
+      <GD25F80 id="C82014" page="256" size="1048576"/>
+      <GD25Q512 id="C84010" page="256" size="65536"/>
+      <GD25Q10 id="C84011" page="256" size="131072"/>
+      <GD25Q20 id="C84012" page="256" size="262144"/>
+	  <GD25LQ20C_1.8V id="C86012" page="256" size="262144"/>
+      <GD25Q40 id="C84013" page="256" size="524288"/>
+      <GD25Q80 id="C84014" page="256" size="1048576"/>
+      <GD25Q80B id="C84014" page="256" size="1048576"/>
+	  <GD25Q80C id="C84014" page="256" size="1048576"/>
+      <GD25Q16 id="C84015" page="256" size="2097152"/>
+      <GD25Q16B id="C84015" page="256" size="2097152"/>
+      <GD25Q32 id="C84016" page="256" size="4194304"/>
+      <GD25Q32B id="C84016" page="256" size="4194304"/>
+      <GD25Q64 id="C84017" page="256" size="8388608"/>
+      <GD25Q64B id="C84017" page="256" size="8388608"/>
+	  <GD25B64C id="C84017" page="256" size="8388608"/>
+      <GD25Q128B id="C84018" page="256" size="16777216"/>
+      <GD25Q128C id="C84018" page="256" size="16777216"/>
+	  <GD25LQ064C_1.8V id="C86017" page="256" size="8388608"/>
+	  <GD25LQ128C_1.8V id="C86018" page="256" size="16777216"/>
+	  <GD25LQ256C_1.8V id="C86019" page="256" size="33554432"/>
+      <MD25T80 id="C83114" page="256" size="1048576"/>
+      <MD25D20 id="514012" page="256" size="262144"/>
+      <MD25D40 id="514013" page="256" size="524288"/>
+      <MD25D80 id="514014" page="256" size="1048576"/>
+      <MD25D16 id="514015" page="256" size="2097152"/>
+    </GIGADEVICE>
+    <ICE>
+      <ICE25P05 id="1C2010" page="128" size="65536"/>
+    </ICE>
+    <ICMIC>
+      <X25020 page="4" size="256" spicmd="95"/>
+      <X25021 page="4" size="256" spicmd="95"/>
+      <X25040 page="4" size="512" spicmd="95"/>
+      <X25041 page="4" size="512" spicmd="95"/>
+      <X25080 page="32" size="1024" spicmd="95"/>
+      <X25128 page="32" size="16384" spicmd="95"/>
+      <X25160 page="32" size="2048" spicmd="95"/>
+      <X25170 page="32" size="2048" spicmd="95"/>
+      <X25320 page="32" size="4096" spicmd="95"/>
+      <X25330 page="32" size="4096" spicmd="95"/>
+      <X25640 page="32" size="8192" spicmd="95"/>
+      <X25642 page="32" size="8192" spicmd="95"/>
+      <X25650 page="32" size="8192" spicmd="95"/>
+    </ICMIC>
+    <INTEGRAL>
+      <IN25AA020 page="16" size="256" spicmd="95"/>
+      <IN25AA040 page="16" size="512" spicmd="95"/>
+      <IN25AA080 page="16" size="1024" spicmd="95"/>
+      <IN25AA160 page="16" size="2048" spicmd="95"/>
+    </INTEGRAL>
+    <INTEL>
+      <QB25F016S33B id="898911" page="256" size="2097152"/>
+      <QB25F160S33B id="898911" page="256" size="2097152"/>
+      <QB25F320S33B id="898912" page="256" size="4194304"/>
+      <QB25F640S33B id="898913" page="256" size="8388608"/>
+      <QH25F016S33B id="898911" page="256" size="2097152"/>
+      <QH25F160S33B id="898911" page="256" size="2097152"/>
+      <QH25F320S33B id="898912" page="256" size="4194304"/>
+    </INTEL>
+    <ISSI>
+      <IS25C01 page="8" size="128" spicmd="95"/>
+      <IS25C02 page="16" size="256" spicmd="95"/>
+      <IS25C04 page="16" size="512" spicmd="95"/>
+      <IS25C08 page="16" size="1024" spicmd="95"/>
+      <IS25C16 page="16" size="2048" spicmd="95"/>
+      <IS25C32 page="32" size="4096" spicmd="95"/>
+      <IS25C32A page="32" size="4096" spicmd="95"/>
+      <IS25C64 page="64" size="8192" spicmd="95"/>
+      <IS25C128 page="64" size="16384" spicmd="95"/>
+      <IS25C256 page="64" size="32768" spicmd="95"/>
+    </ISSI>
+    <KHIC>
+      <KH25L1005 id="C22011" page="256" size="131072"/>
+      <KH25L1005A id="C22011" page="256" size="131072"/>
+      <KH25L2005 id="C22012" page="256" size="262144"/>
+      <KH25L4005 id="C22013" page="256" size="524288"/>
+      <KH25L4005A id="C22013" page="256" size="524288"/>
+      <KH25L512 id="C22010" page="256" size="65536"/>
+      <KH25L512A id="C22010" page="256" size="65536"/>
+      <KH25L8005 id="C22014" page="256" size="1048576"/>
+      <KH25L8036D id="C22615" page="256" size="1048576"/>
+    </KHIC>
+    <MACRONIX>
+      <MX25L1005 id="C22011" page="256" size="131072"/>
+      <MX25L1005A id="C22011" page="256" size="131072"/>
+      <MX25L1005C id="C22011" page="256" size="131072"/>
+      <MX25L1006E id="C22011" page="256" size="131072"/>
+      <MX25L1021E id="C22211" page="32" size="131072"/>
+      <MX25L1025C id="C22011" page="256" size="131072"/>
+      <MX25L1026E id="C22011" page="256" size="131072"/>
+      <MX25L12805D id="C22018" page="256" size="16777216"/>
+      <MX25L12835E id="C22018" page="256" size="16777216"/>
+      <MX25L12835F id="C22018" page="256" size="16777216"/>
+      <MX25L12836E id="C22018" page="256" size="16777216"/>
+      <MX25L12839F id="C22018" page="256" size="16777216"/>
+      <MX25L12845E id="C22018" page="256" size="16777216"/>
+      <MX25L12845G id="C22018" page="256" size="16777216"/>
+      <MX25L12845F id="C22018" page="256" size="16777216"/>
+      <MX25L12865E id="C22018" page="256" size="16777216"/>
+      <MX25L12865F id="C22018" page="256" size="16777216"/>
+      <MX25L12873F id="C22018" page="256" size="16777216"/>
+      <MX25L12875F id="C22018" page="256" size="16777216"/>
+	  <MX25L25635E id="C22019" page="256" size="33554432"/>
+      <MX25L1605 id="C22015" page="256" size="2097152"/>
+      <MX25L1605A id="C22015" page="256" size="2097152"/>
+      <MX25L1605D id="C22015" page="256" size="2097152"/>
+      <MX25L1606E id="C22015" page="256" size="2097152"/>
+      <MX25L1633E id="C22415" page="256" size="2097152"/>
+      <MX25L1635D id="C22415" page="256" size="2097152"/>
+      <MX25L1635E id="C22515" page="256" size="2097152"/>
+      <MX25L1636D id="C22415" page="256" size="2097152"/>
+      <MX25L1636E id="C22515" page="256" size="2097152"/>
+      <MX25L1673E id="C22415" page="256" size="2097152"/>
+      <MX25L1675E id="C22415" page="256" size="2097152"/>
+      <MX25L2005 id="C22012" page="256" size="262144"/>
+      <MX25L2005C id="C22012" page="256" size="262144"/>
+      <MX25L2006E id="C22012" page="256" size="262144"/>
+      <MX25L2026C id="C22012" page="256" size="262144"/>
+      <MX25L2026E id="C22012" page="256" size="262144"/>
+      <MX25L3205 id="C22016" page="256" size="4194304"/>
+      <MX25L3205A id="C22016" page="256" size="4194304"/>
+      <MX25L3205D id="C22016" page="256" size="4194304"/>
+      <MX25L3206E id="C22016" page="256" size="4194304"/>
+      <MX25L3208E id="C22016" page="256" size="4194304"/>
+      <MX25L3225D id="C25E16" page="256" size="4194304"/>
+      <MX25L3233F id="C22016" page="256" size="4194304"/>
+      <MX25L3235D id="C25E16" page="256" size="4194304"/>
+      <MX25L3235E id="C22016" page="256" size="4194304"/>
+      <MX25L3236D id="C25E16" page="256" size="4194304"/>
+      <MX25L3237D id="C25E16" page="256" size="4194304"/>
+      <MX25L3239E id="C22536" page="256" size="4194304"/>
+      <MX25L3273E id="C22016" page="256" size="4194304"/>
+      <MX25L3273F id="C22016" page="256" size="4194304"/>
+      <MX25L3275E id="C22016" page="256" size="4194304"/>
+      <MX25L4005 id="C22013" page="256" size="524288"/>
+      <MX25L4005A id="C22013" page="256" size="524288"/>
+      <MX25L4005C id="C22013" page="256" size="524288"/>
+      <MX25L4006E id="C22013" page="256" size="524288"/>
+      <MX25L4026E id="C22013" page="256" size="524288"/>
+      <MX25L512 id="C22010" page="256" size="65536"/>
+      <MX25L512A id="C22010" page="256" size="65536"/>
+      <MX25L512C id="C22010" page="256" size="65536"/>
+      <MX25L5121E id="C22210" page="32" size="65536"/>
+      <MX25L6405 id="C22017" page="256" size="8388608"/>
+      <MX25L6405D id="C22017" page="256" size="8388608"/>
+      <MX25L6406E id="C22017" page="256" size="8388608"/>
+      <MX25L6408E id="C22017" page="256" size="8388608"/>
+      <MX25L6433F id="C22017" page="256" size="8388608"/>
+      <MX25L6435E id="C22017" page="256" size="8388608"/>
+      <MX25L6436E id="C22017" page="256" size="8388608"/>
+	  <MX25L6436F id="C22017" page="256" size="8388608"/>
+      <MX25L6439E id="C22537" page="256" size="8388608"/>
+      <MX25L6445E id="C22017" page="256" size="8388608"/>
+      <MX25L6465E id="C22017" page="256" size="8388608"/>
+      <MX25L6473E id="C22017" page="256" size="8388608"/>
+      <MX25L6473F id="C22017" page="256" size="8388608"/>
+      <MX25L6475E id="C22017" page="256" size="8388608"/>
+      <MX25L8005 id="C22014" page="256" size="1048576"/>
+      <MX25L8006E id="C22014" page="256" size="1048576"/>
+      <MX25L8008E id="C22014" page="256" size="1048576"/>
+      <MX25L8035E id="C22014" page="256" size="1048576"/>
+      <MX25L8036E id="C22014" page="256" size="1048576"/>
+      <MX25L8073E id="C22014" page="256" size="1048576"/>
+      <MX25L8075E id="C22014" page="256" size="1048576"/>
+	  <MX25L25673G id="C22019" page="256" size="33554432"/>
+      <MX25R512F id="C22810" page="256" size="65536"/>
+      <MX25R1035F id="C22811" page="256" size="131072"/>
+      <MX25R1635F id="C22815" page="256" size="2097152"/>
+      <MX25R2035F id="C22812" page="256" size="262144"/>
+      <MX25R3235F id="C22816" page="256" size="4194304"/>
+      <MX25R4035F id="C22813" page="256" size="524288"/>
+      <MX25R6435F id="C22817" page="256" size="8388608"/>
+      <MX25R8035F id="C22814" page="256" size="1048576"/>
+      <MX25U1001E_1.8V id="C22531" page="256" size="131072"/>
+      <MX25U12835F_1.8V id="C22518" page="256" size="16777216"/>
+	  <MX25U25673G_1.8V id="C22539" page="256" size="33554432"/>
+	  <MX25U25645G_1.8V id="C22539" page="256" size="33554432"/>
+      <MX25U1635E_1.8V id="C22535" page="256" size="2097152"/>
+      <MX25U1635F_1.8V id="C22535" page="256" size="2097152"/>
+      <MX25U2032E_1.8V id="C22532" page="256" size="262144"/>
+      <MX25U2033E_1.8V id="C22532" page="256" size="262144"/>
+      <MX25U3235E_1.8V id="C22536" page="256" size="4194304"/>
+      <MX25U3235F_1.8V id="C22536" page="256" size="4194304"/>
+      <MX25U4032E_1.8V id="C22533" page="256" size="524288"/>
+      <MX25U4033E_1.8V id="C22533" page="256" size="524288"/>
+      <MX25U4035_1.8V id="C22533" page="256" size="524288"/>
+      <MX25U5121E_1.8V id="C22530" page="256" size="65536"/>
+      <MX25U6435F_1.8V id="C22537" page="256" size="8388608"/>
+      <MX25U6473F_1.8V id="C22537" page="256" size="8388608"/>
+      <MX25U8032E_1.8V id="C22534" page="256" size="1048576"/>
+      <MX25U8033E_1.8V id="C22534" page="256" size="1048576"/>
+      <MX25U8035_1.8V id="C22534" page="256" size="1048576"/>
+      <MX25U8035E_1.8V id="C22534" page="256" size="1048576"/>
+	  <MX25U12873F_1.8V id="C22538" page="256" size="16777216"/>
+      <MX25V1006E id="C22011" page="256" size="131072"/>
+      <MX25V1035F id="C22311" page="256" size="131072"/>
+      <MX25V2006E id="C22012" page="256" size="262144"/>
+      <MX25V2035F id="C22312" page="256" size="262144"/>
+      <MX25V512 id="C22010" page="256" size="65536"/>
+      <MX25V512C id="C22010" page="256" size="65536"/>
+      <MX25V512E id="C22010" page="256" size="65536"/>
+      <MX25V512F id="C22310" page="256" size="65536"/>
+      <MX25V4005 id="C22013" page="256" size="524288"/>
+      <MX25V4006E id="C22013" page="256" size="524288"/>
+      <MX25V4035 id="C22553" page="256" size="524288"/>
+      <MX25V4035F id="C22313" page="256" size="524288"/>
+      <MX25V8005 id="C22014" page="256" size="1048576"/>
+      <MX25V8006E id="C22014" page="256" size="1048576"/>
+      <MX25V8035 id="C22554" page="256" size="1048576"/>
+      <MX25V8035F id="C22314" page="256" size="1048576"/>
+	  <MX66U51235F_1.8V id="C2253A" page="256" size="67108864"/>
+	  <MX66U1G45G_1.8V id="C2253B" page="256" size="134217728"/>
+    </MACRONIX>
+    <MICROCHIP>
+      <_25AA010A page="16" size="128" spicmd="95"/>
+      <_25AA020A page="16" size="256" spicmd="95"/>
+      <_25AA040 page="16" size="512" spicmd="95"/>
+      <_25AA040A page="16" size="512" spicmd="95"/>
+      <_25AA080 page="16" size="1024" spicmd="95"/>
+      <_25AA080A page="16" size="1024" spicmd="95"/>
+      <_25AA080B page="32" size="1024" spicmd="95"/>
+      <_25AA080C page="16" size="1024" spicmd="95"/>
+      <_25AA080D page="32" size="1024" spicmd="95"/>
+      <_25AA1024 page="256" size="131072" spicmd="95"/>
+      <_25AA128 page="64" size="16384" spicmd="95"/>
+      <_25AA160 page="16" size="2048" spicmd="95"/>
+      <_25AA160A page="16" size="2048" spicmd="95"/>
+      <_25AA160B page="32" size="2048" spicmd="95"/>
+      <_25AA256 page="64" size="32768" spicmd="95"/>
+      <_25AA320 page="32" size="4096" spicmd="95"/>
+      <_25AA512 page="128" size="65536" spicmd="95"/>
+      <_25AA640 page="32" size="8192" spicmd="95"/>
+      <_25C040 page="16" size="512" spicmd="95"/>
+      <_25C080 page="16" size="1024" spicmd="95"/>
+      <_25C160 page="16" size="2048" spicmd="95"/>
+      <_25C320 page="32" size="4096" spicmd="95"/>
+      <_25C640 page="32" size="8192" spicmd="95"/>
+      <_25LC010A page="16" size="128" spicmd="95"/>
+      <_25LC020A page="16" size="256" spicmd="95"/>
+      <_25LC040 page="16" size="512" spicmd="95"/>
+      <_25LC040A page="16" size="512" spicmd="95"/>
+      <_25LC080 page="16" size="1024" spicmd="95"/>
+      <_25LC080A page="16" size="1024" spicmd="95"/>
+      <_25LC080B page="32" size="1024" spicmd="95"/>
+      <_25LC080C page="16" size="1024" spicmd="95"/>
+      <_25LC080D page="32" size="1024" spicmd="95"/>
+      <_25LC1024 page="256" size="131072" spicmd="95"/>
+      <_25LC128 page="64" size="16384" spicmd="95"/>
+      <_25LC160 page="16" size="2048" spicmd="95"/>
+      <_25LC160A page="16" size="2048" spicmd="95"/>
+      <_25LC160B page="32" size="2048" spicmd="95"/>
+      <_25LC256 page="64" size="32768" spicmd="95"/>
+      <_25LC320 page="32" size="4096" spicmd="95"/>
+      <_25LC512 page="128" size="65536" spicmd="95"/>
+      <_25LC640 page="32" size="8192" spicmd="95"/>
+    </MICROCHIP>
+    <MICRON>
+      <N25Q032A id="20BA16" page="256" size="4194304"/>
+      <N25Q064A id="20BA17" page="256" size="8388608"/>
+	  <N25Q256A13 id="20BA19" page="256" size="33554432"/>
+	  <N25Q512A83 id="20BA20" page="256" size="67108864"/>
+	  <N25W256A11 id="2CCB19" page="256" size="33554432"/>
+	  <MT25QL128AB id="20BA18" page="256" size="16777216"/>
+      <MT25QL256A id="20BA19" page="256" size="33554432"/>
+      <MT25QL512A id="20BA20" page="256" size="67108864"/>
+      <MT25QL02GC id="20BA22" page="256" size="268435456"/>
+      <MT25QU256 id="20BB19" page="256" size="33554432"/>  
+	  <N25Q00AA13G id="20BA21" page="256" size="134217728"/>
+    </MICRON>
+    <MSHINE>
+      <MS25X512 id="373010" page="256" size="65536"/>
+      <MS25X10 id="373011" page="256" size="131072"/>
+      <MS25X20 id="373012" page="256" size="262144"/>
+      <MS25X40 id="373013" page="256" size="524288"/>
+      <MS25X80 id="373014" page="256" size="1048576"/>
+      <MS25X16 id="373015" page="256" size="2097152"/>
+      <MS25X32 id="373016" page="256" size="4194304"/>
+    </MSHINE>
+    <NANTRONICS>
+      <N25S10 id="D53011" page="256" size="131072"/>
+      <N25S20 id="D53012" page="256" size="262144"/>
+      <N25S40 id="D53013" page="256" size="524288"/>
+      <N25S16 id="D53015" page="256" size="2097152"/>
+      <N25S32 id="D53016" page="256" size="4194304"/>
+      <N25S80 id="D53014" page="256" size="1048576"/>
+    </NANTRONICS>
+    <NEXFLASH>
+      <NX25P10 id="9D7F7C" page="256" size="131072"/>
+      <NX25P16 id="EF2015" page="256" size="2097152"/>
+      <NX25P20 id="9D7F7D" page="256" size="262144"/>
+      <NX25P32 id="EF2016" page="256" size="4194304"/>
+      <NX25P40 id="9D7F7E" page="256" size="524288"/>
+      <NX25P80 id="9D7F13" page="256" size="1048576"/>
+    </NEXFLASH>
+    <NUMONYX>
+	  <M45PE16 id="204015" page="256" size="2097152" script="blockerase.pas"/>
+      <M25P05 id="202010" page="128" size="65536"/>
+      <M25P05A id="202010" page="256" size="65536"/>
+      <M25P10 id="202011" page="128" size="131072"/>
+      <M25P10A id="202011" page="256" size="131072"/>
+      <M25P20 id="202012" page="256" size="262144"/>
+      <M25P40 id="202013" page="256" size="524288"/>
+      <M25P80 id="202014" page="256" size="1048576"/>
+      <M25P16 id="202015" page="256" size="2097152"/>
+      <M25P32 id="202016" page="256" size="4194304"/>
+      <M25P64 id="202017" page="256" size="8388608"/>
+      <M25P128_ST25P28V6G id="202018" page="256" size="16777216"/>
+      <M25PE10 id="208011" page="256" size="131072"/>
+      <M25PE16 id="208015" page="256" size="2097152"/>
+      <M25PE20 id="208012" page="256" size="262144"/>
+      <M25PE40 id="208013" page="256" size="524288"/>
+      <M25PE80 id="208014" page="256" size="1048576"/>
+    </NUMONYX>
+    <PCT>
+      <PCT25LF020A id="BF4300" page="256" size="262144"/>
+      <PCT25VF010A id="BF4900" page="256" size="131072"/>
+      <PCT25VF016B id="BF2541" page="256" size="2097152"/>
+      <PCT25VF020A id="BF4300" page="256" size="262144"/>
+      <PCT25VF032B id="BF254A" page="256" size="4194304"/>
+      <PCT25VF040A id="BF4400" page="256" size="524288"/>
+      <PCT25VF040B id="BF258D" page="256" size="524288"/>
+      <PCT25VF080B id="BF258E" page="256" size="1048576"/>
+    </PCT>
+    <RAMTRON>
+      <FM25040 page="32" size="512" spicmd="95"/>
+      <FM25C160 page="32" size="2048" spicmd="95"/>
+      <FM25640 page="32" size="8192" spicmd="95"/>
+      <FM25CL04 page="32" size="512" spicmd="95"/>
+      <FM25CL64 page="32" size="8192" spicmd="95"/>
+      <FM25L16 page="32" size="2048" spicmd="95"/>
+      <FM25L256 page="32" size="32768" spicmd="95"/>
+      <FM25L512 page="32" size="65536" spicmd="95"/>
+      <FM25W256 page="32" size="32768" spicmd="95"/>
+    </RAMTRON>
+    <RENESAS>
+      <HN58X2502 page="16" size="256" spicmd="95"/>
+      <HN58X2504 page="16" size="512" spicmd="95"/>
+      <HN58X2508 page="16" size="1024" spicmd="95"/>
+      <HN58X25128 page="64" size="16384" spicmd="95"/>
+      <HN58X2516 page="16" size="2048" spicmd="95"/>
+      <HN58X25256 page="64" size="32768" spicmd="95"/>
+      <HN58X2532 page="32" size="4096" spicmd="95"/>
+      <HN58X2564 page="32" size="8192" spicmd="95"/>
+      <R1EX25002A page="16" size="256" spicmd="95"/>
+      <R1EX25004A page="16" size="512" spicmd="95"/>
+      <R1EX25008A page="16" size="1024" spicmd="95"/>
+      <R1EX25016A page="16" size="2048" spicmd="95"/>
+      <R1EX25032A page="32" size="4096" spicmd="95"/>
+      <R1EX25064A page="32" size="8192" spicmd="95"/>
+    </RENESAS>
+    <ROHM>
+      <BR25010 page="16" size="128" spicmd="95"/>
+      <BR25020 page="16" size="256" spicmd="95"/>
+      <BR25040 page="16" size="512" spicmd="95"/>
+      <BR25080 page="16" size="1024" spicmd="95"/>
+      <BR25160 page="16" size="2048" spicmd="95"/>
+      <BR25320 page="32" size="4096" spicmd="95"/>
+      <BR25H010 page="16" size="128" spicmd="95"/>
+      <BR25H020 page="16" size="256" spicmd="95"/>
+      <BR25H040 page="16" size="512" spicmd="95"/>
+      <BR25H080 page="16" size="1024" spicmd="95"/>
+      <BR25H160 page="16" size="2048" spicmd="95"/>
+      <BR25H320 page="32" size="4096" spicmd="95"/>
+      <BR25L010 page="16" size="128" spicmd="95"/>
+      <BR25L010 page="16" size="128" spicmd="95"/>
+      <BR25L020 page="16" size="256" spicmd="95"/>
+      <BR25L040 page="16" size="512" spicmd="95"/>
+      <BR25L080 page="16" size="1024" spicmd="95"/>
+      <BR25L160 page="16" size="2048" spicmd="95"/>
+      <BR25L320 page="32" size="4096" spicmd="95"/>
+      <BR25L640 page="32" size="8192" spicmd="95"/>
+      <BR25S320 page="32" size="4096" spicmd="95"/>
+      <BR25S640 page="32" size="8192" spicmd="95"/>
+      <BR25S128 page="64" size="16384" spicmd="95"/>
+      <BR25S256 page="64" size="32768" spicmd="95"/>
+      <BR95010 page="8" size="128" spicmd="95"/>
+      <BR95020 page="8" size="256" spicmd="95"/>
+      <BR95040 page="8" size="512" spicmd="95"/>
+      <BR95080 page="32" size="1024" spicmd="95"/>
+      <BR95160 page="32" size="2048" spicmd="95"/>
+    </ROHM>
+    <SAIFUN>
+      <SA25C1024H page="128" size="131072" spicmd="95"/>
+      <SA25C1024L page="128" size="131072" spicmd="95"/>
+      <SA25C512H page="128" size="65536" spicmd="95"/>
+      <SA25C512L page="128" size="65536" spicmd="95"/>
+    </SAIFUN>
+    <SANYO>
+      <LE25FU106BMA id="621D" page="256" size="131072"/>
+      <LE25FU206MA id="6244" page="256" size="262144"/>
+      <LE25FU406BMA id="621E" page="256" size="524288"/>
+      <LE25FW206M id="6226" page="256" size="262144"/>
+      <LE25FW406M id="6207" page="256" size="524288"/>
+      <LE25FW406AM id="621A" page="256" size="524288"/>
+      <LE25FW806M id="6226" page="256" size="1048576"/>
+    </SANYO>
+    <SEIKO>
+      <S-25A010A page="8" size="128" spicmd="95"/>
+      <S-25A020A page="8" size="256" spicmd="95"/>
+      <S-25A040A page="8" size="512" spicmd="95"/>
+      <S-25A080A page="32" size="1024" spicmd="95"/>
+      <S-25A160A page="32" size="2048" spicmd="95"/>
+      <S-25A320A page="32" size="4096" spicmd="95"/>
+      <S-25A640A page="32" size="8192" spicmd="95"/>
+      <S-25C010A page="8" size="128" spicmd="95"/>
+      <S-25C020A page="8" size="256" spicmd="95"/>
+      <S-25C040A page="8" size="512" spicmd="95"/>
+      <S-25C080A page="32" size="1024" spicmd="95"/>
+      <S-25C160A page="32" size="2048" spicmd="95"/>
+      <S-25C320A page="32" size="4096" spicmd="95"/>
+      <S-25C640A page="32" size="8192" spicmd="95"/>
+    </SEIKO>
+    <SIEMENS>
+      <SLA25010 page="16" size="128" spicmd="95"/>
+      <SLA25020 page="16" size="256" spicmd="95"/>
+      <SLA25040 page="16" size="512" spicmd="95"/>
+      <SLA25080 page="32" size="1024" spicmd="95"/>
+      <SLA25160 page="32" size="2048" spicmd="95"/>
+      <SLA25320 page="32" size="4096" spicmd="95"/>
+      <SLE25010 page="16" size="128" spicmd="95"/>
+      <SLE25020 page="16" size="256" spicmd="95"/>
+      <SLE25040 page="16" size="512" spicmd="95"/>
+      <SLE25080 page="32" size="1024" spicmd="95"/>
+      <SLE25160 page="32" size="2048" spicmd="95"/>
+      <SLE25320 page="32" size="4096" spicmd="95"/>
+    </SIEMENS>
+    <SPANSION>
+      <S25FL001D id="010210" page="256" size="131072"/>
+      <S25FL002D id="010211" page="256" size="262144"/>
+      <S25FL004A id="010212" page="256" size="524288"/>
+      <S25FL004D id="010212" page="256" size="524288"/>
+      <S25FL004K id="EF4013" page="256" size="524288"/>
+      <S25FL008A id="010213" page="256" size="1048576"/>
+      <S25FL008D id="010213" page="256" size="1048576"/>
+      <S25FL008K id="EF4014" page="256" size="1048576"/>
+      <S25FL016A id="010214" page="256" size="2097152"/>
+      <S25FL016K id="EF4015" page="256" size="2097152"/>
+      <S25FL032A id="010215" page="256" size="4194304"/>
+      <S25FL032K id="EF4016" page="256" size="4194304"/>
+      <S25FL032P id="010215" page="256" size="4194304"/>
+      <S25FL040A id="010212" page="256" size="524288"/>
+      <S25FL040A_BOT id="010226" page="256" size="524288"/>
+      <S25FL040A_TOP id="010225" page="256" size="524288"/>
+      <S25FL064A id="010216" page="256" size="8388608"/>
+      <S25FL064K id="EF4017" page="256" size="8388608"/>
+      <S25FL064P id="010216" page="256" size="8388608"/>
+      <S25FL116K id="014015" page="256" size="2097152"/>
+      <S25FL128K id="EF4018" page="256" size="16777216"/>
+      <S25FL128P id="012018" page="256" size="16777216"/>
+      <S25FL128S id="012018" page="256" size="16777216"/>
+      <S25FL132K id="014016" page="256" size="4194304"/>
+      <S25FL164K id="014017" page="256" size="8388608"/>
+	  <S25FL256S id="010219" page="256" size="33554432"/>
+    </SPANSION>
+    <SST>
+      <SST25LF020A id="BF43" page="SSTB" size="262144"/>
+      <SST25LF040A id="BF44" page="SSTB" size="524288"/>
+      <SST25LF080A id="BF80" page="SSTB" size="1048576"/>
+      <SST25VF010 id="BF49" page="SSTB" size="131072"/>
+      <SST25VF010A id="BF49" page="SSTB" size="131072"/>
+      <SST25VF016B id="BF2541" page="SSTW" size="2097152"/>
+      <SST25VF020 id="BF43" page="SSTB" size="262144"/>
+      <SST25VF020A id="BF43" page="SSTB" size="262144"/>
+      <SST25VF020B id="BF258C" page="SSTW" size="262144"/>
+      <SST25VF032B id="BF254A" page="SSTW" size="4194304"/>
+      <SST25VF064C id="BF254B" page="256" size="8388608"/>
+      <SST25VF040 id="BF44" page="SSTB" size="524288"/>
+      <SST25VF040A id="BF44" page="SSTB" size="524288"/>
+      <SST25VF040B id="BF258D" page="SSTW" size="524288"/>
+      <SST25VF080B id="BF258E" page="SSTW" size="1048576"/>
+      <SST25VF512 id="BF48" page="SSTB" size="65536"/>
+      <SST25VF512A id="BF48" page="SSTB" size="65536"/>
+    </SST>
+    <ST>
+      <M25C16 page="16" size="2048" spicmd="95"/>
+      <M25PX16 id="207115" page="256" size="2097152"/>
+      <M25PX32 id="207116" page="256" size="4194304"/>
+      <M25PX64 id="207117" page="256" size="8388608"/>
+      <M25PX80 id="207114" page="256" size="1048576"/>
+      <M25W16 page="16" size="2048" spicmd="95"/>
+      <M35080-3 page="32" size="1024" spicmd="95"/>
+      <M35080-6 page="32" size="1024" spicmd="95"/>
+      <M35080V6 page="32" size="1024" spicmd="95"/>
+      <M35080VP page="32" size="1024" spicmd="95"/>
+      <M95010 page="8" size="128" spicmd="95"/>
+      <M95010R page="8" size="128" spicmd="95"/>
+      <M95010W page="8" size="128" spicmd="95"/>
+      <M95020 page="8" size="256" spicmd="95"/>
+      <M95020R page="8" size="256" spicmd="95"/>
+      <M95020W page="8" size="256" spicmd="95"/>
+      <M95040 page="8" size="512" spicmd="95"/>
+      <M95040R page="8" size="512" spicmd="95"/>
+      <M95040W page="8" size="512" spicmd="95"/>
+      <M95080 page="32" size="1024" spicmd="95"/>
+      <M95080R page="32" size="1024" spicmd="95"/>
+      <M95080W page="32" size="1024" spicmd="95"/>
+      <M95128 page="64" size="16384" spicmd="95"/>
+      <M95128R page="64" size="16384" spicmd="95"/>
+      <M95128W page="64" size="16384" spicmd="95"/>
+      <M95160 page="32" size="2048" spicmd="95"/>
+      <M95160R page="32" size="2048" spicmd="95"/>
+      <M95160W page="32" size="2048" spicmd="95"/>
+      <M95256 page="64" size="32768" spicmd="95"/>
+      <M95256R page="64" size="32768" spicmd="95"/>
+      <M95256W page="64" size="32768" spicmd="95"/>
+      <M95320 page="32" size="4096" spicmd="95"/>
+      <M95320R page="32" size="4096" spicmd="95"/>
+      <M95320W page="32" size="4096" spicmd="95"/>
+      <M95512R page="128" size="65536" spicmd="95"/>
+      <M95512W page="128" size="65536" spicmd="95"/>
+      <M95640 page="32" size="8192" spicmd="95"/>
+      <M95640R page="32" size="8192" spicmd="95"/>
+      <M95640W page="32" size="8192" spicmd="95"/>
+      <M95M01R page="256" size="131072" spicmd="95"/>
+      <M95M01W page="256" size="131072" spicmd="95"/>
+      <ST25C16 page="16" size="2048" spicmd="95"/>
+      <ST25P05 id="202010" page="128" size="65536"/>
+      <ST25P05A id="202010" page="256" size="65536"/>
+      <ST25P10 id="202011" page="128" size="131072"/>
+      <ST25P10A id="202011" page="256" size="131072"/>
+      <ST25P16 id="202015" page="256" size="2097152"/>
+      <ST25P20 id="202012" page="256" size="262144"/>
+      <ST25P32 id="202016" page="256" size="4194304"/>
+      <ST25P40 id="202013" page="256" size="524288"/>
+      <ST25P64 id="202017" page="256" size="8388608"/>
+      <ST25P80 id="202014" page="256" size="1048576"/>
+    </ST>
+    <WINBOND>
+      <W25P10 id="EF1000" page="256" size="131072"/>
+      <W25P16 id="EF2015" page="256" size="2097152"/>
+      <W25P20 id="EF1100" page="256" size="262144"/>
+      <W25P32 id="EF2016" page="256" size="4194304"/>
+      <W25P40 id="EF1200" page="256" size="524288"/>
+      <W25P64 id="EF2017" page="256" size="8388608"/>
+      <W25P80 id="EF2014" page="256" size="1048576"/>
+      <W25Q10EW_1.8V id="EF6011" page="256" size="131072"/>
+      <W25Q128BV id="EF4018" page="256" size="16777216"/>
+      <W25Q128FV id="EF4018" page="256" size="16777216"/>
+	  <W25Q128JV id="EF7018" page="256" size="16777216"/>
+	  <W25Q256FV id="EF4019" page="256" size="33554432"/>
+	  <W25Q256JV id="EF4019" page="256" size="33554432"/>
+	  <W25Q256JV id="EF7019" page="256" size="33554432"/>
+      <W25Q128FW_1.8V id="EF6018" page="256" size="16777216"/>
+      <W25Q16 id="EF4015" page="256" size="2097152"/>
+      <W25Q16BV id="EF4015" page="256" size="2097152"/>
+      <W25Q16CL id="EF4015" page="256" size="2097152"/>
+      <W25Q16CV id="EF4015" page="256" size="2097152"/>
+      <W25Q16DV id="EF4015" page="256" size="2097152"/>
+      <W25Q16FW_1.8V id="EF6015" page="256" size="2097152"/>
+      <W25Q16V id="EF4015" page="256" size="2097152"/>
+      <W25Q20CL id="EF4012" page="256" size="262144"/>
+      <W25Q20EW_1.8V id="EF6012" page="256" size="262144"/>
+      <W25Q32 id="EF4016" page="256" size="4194304"/>
+      <W25Q32BV id="EF4016" page="256" size="4194304"/>
+      <W25Q32FV id="EF4016" page="256" size="4194304"/>
+      <W25Q32FW_1.8V id="EF6016" page="256" size="4194304"/>
+      <W25Q32V id="EF4016" page="256" size="4194304"/>
+      <W25Q40BL id="EF4013" page="256" size="524288"/>
+      <W25Q40BV id="EF4013" page="256" size="524288"/>
+      <W25Q40CL id="EF4013" page="256" size="524288"/>
+      <W25Q40EW_1.8V id="EF6013" page="256" size="524288"/>
+      <W25Q64BV id="EF4017" page="256" size="8388608"/>
+      <W25Q64CV id="EF4017" page="256" size="8388608"/>
+      <W25Q64FV id="EF4017" page="256" size="8388608"/>
+	  <W25Q64JV id="EF4017" page="256" size="8388608"/>
+      <W25Q64FW_1.8V id="EF6017" page="256" size="8388608"/>
+      <W25Q80BL id="EF4014" page="256" size="1048576"/>
+      <W25Q80BV id="EF4014" page="256" size="1048576"/>
+	  <W25Q80BW_1.8V id="EF5014" page="256" size="1048576"/>
+      <W25Q80DV id="EF4014" page="256" size="1048576"/>
+      <W25Q80EW_1.8V id="EF6014" page="256" size="1048576"/>
+      <W25X05 id="EF3010" page="256" size="65536"/>
+      <W25X05CL id="EF3010" page="256" size="65536"/>
+      <W25X10AV id="EF3011" page="256" size="131072"/>
+      <W25X10BL id="EF3011" page="256" size="131072"/>
+      <W25X10BV id="EF3011" page="256" size="131072"/>
+      <W25X10CL id="EF3011" page="256" size="131072"/>
+      <W25X10L id="EF3011" page="256" size="131072"/>
+      <W25X10V id="EF3011" page="256" size="131072"/>
+      <W25X16 id="EF3015" page="256" size="2097152"/>
+      <W25X16AL id="EF3015" page="256" size="2097152"/>
+      <W25X16AV id="EF3015" page="256" size="2097152"/>
+      <W25X16BV id="EF3015" page="256" size="2097152"/>
+      <W25X16V id="EF3015" page="256" size="2097152"/>
+      <W25X20AL id="EF3012" page="256" size="262144"/>
+      <W25X20AV id="EF3012" page="256" size="262144"/>
+      <W25X20BL id="EF3012" page="256" size="262144"/>
+      <W25X20BV id="EF3012" page="256" size="262144"/>
+      <W25X20CL id="EF3012" page="256" size="262144"/>
+      <W25X20L id="EF3012" page="256" size="262144"/>
+      <W25X20V id="EF3012" page="256" size="262144"/>
+      <W25X32 id="EF3016" page="256" size="4194304"/>
+      <W25X32AV id="EF3016" page="256" size="4194304"/>
+      <W25X32BV id="EF3016" page="256" size="4194304"/>
+      <W25X32V id="EF3016" page="256" size="4194304"/>
+      <W25X40AL id="EF3013" page="256" size="524288"/>
+      <W25X40AV id="EF3013" page="256" size="524288"/>
+      <W25X40BL id="EF3013" page="256" size="524288"/>
+      <W25X40BV id="EF3013" page="256" size="524288"/>
+      <W25X40CL id="EF3013" page="256" size="524288"/>
+      <W25X40L id="EF3013" page="256" size="524288"/>
+      <W25X40V id="EF3013" page="256" size="524288"/>
+      <W25X64 id="EF3017" page="256" size="8388608"/>
+      <W25X64BV id="EF3017" page="256" size="8388608"/>
+      <W25X64V id="EF3017" page="256" size="8388608"/>
+      <W25X80AL id="EF3014" page="256" size="1048576"/>
+      <W25X80AV id="EF3014" page="256" size="1048576"/>
+      <W25X80BV id="EF3014" page="256" size="1048576"/>
+      <W25X80L id="EF3014" page="256" size="1048576"/>
+      <W25X80V id="EF3014" page="256" size="1048576"/>
+	  <W25M512JV id="EF7119" page="256" size="67108864"/>
+	  <W25R256JV id="EF4019" page="256" size="33554432"/>
+    </WINBOND>
+    <XICOR>
+      <X25010 page="4" size="128" spicmd="95"/>
+      <X25043 page="4" size="512" spicmd="95"/>
+      <X25045 page="4" size="512" spicmd="95"/>
+      <X25F008 page="32" size="1024" spicmd="95"/>
+      <X25F016 page="32" size="2048" spicmd="95"/>
+      <X25F032 page="32" size="4096" spicmd="95"/>
+      <X25F064 page="32" size="8192" spicmd="95"/>
+      <X5043 page="16" size="512" spicmd="95"/>
+      <X5045 page="16" size="512" spicmd="95"/>
+    </XICOR>
+    <ZEMPRO>
+      <TS25L512A id="372010" page="256" size="65536"/>
+      <TS25L010A id="373011" page="256" size="131072"/>
+      <TS25L020A id="373012" page="256" size="262144"/>
+      <TS25L16AP id="202015" page="256" size="2097152"/>
+      <TS25L16BP id="202015" page="256" size="2097152"/>
+      <TS25L16P id="372015" page="256" size="2097152"/>
+    </ZEMPRO>
+	<Zbit>
+	  <ZB25D16 id="5E4015" page="256" size="2097152"/>
+	</Zbit>
+    <Berg_Micro>
+      <BG25Q40A id="E04013" page="256" size="524288"/>
+      <BG25Q80A id="E04014" page="256" size="1048576"/>
+      <BG25Q16A id="E04015" page="256" size="2097152"/>
+	  <BG25Q32A id="E04016" page="256" size="4194304"/>
+    </Berg_Micro>
+    <ATMEL>
+	  <AT45DB021D id="1F2300" page="264" size="270336" spicmd="45"/>
+      <AT45DB041D id="1F2400" page="264" size="540672" spicmd="45"/>
+	  <AT45DB161D id="1F2600" page="528" size="2162688" spicmd="45"/>
+	  <AT45DB321D id="1F2701" page="528" size="4325376" spicmd="45"/>
+      <AT25010 page="8" size="128" spicmd="95"/>
+      <AT25010A page="8" size="128" spicmd="95"/>
+      <AT25020 page="8" size="256" spicmd="95"/>
+      <AT25020A page="8" size="256" spicmd="95"/>
+      <AT25040 page="8" size="512" spicmd="95"/>
+      <AT25040A page="8" size="512" spicmd="95"/>
+      <AT25080 page="32" size="1024" spicmd="95"/>
+      <AT25080A page="32" size="1024" spicmd="95"/>
+      <AT25080B page="32" size="1024" spicmd="95"/>
+      <AT25160 page="32" size="2048" spicmd="95"/>
+      <AT25160A page="32" size="2048" spicmd="95"/>
+      <AT25160B page="32" size="2048" spicmd="95"/>
+      <AT25320 page="32" size="4096" spicmd="95"/>
+      <AT25320A page="32" size="4096" spicmd="95"/>
+      <AT25320B page="32" size="4096" spicmd="95"/>
+      <AT25640 page="32" size="8192" spicmd="95"/>
+      <AT25640A page="32" size="8192" spicmd="95"/>
+      <AT25640B page="32" size="8192" spicmd="95"/>
+      <AT25128 page="64" size="16384" spicmd="95"/>
+      <AT25128A page="64" size="16384" spicmd="95"/>
+      <AT25128B page="64" size="16384" spicmd="95"/>
+      <AT25256 page="64" size="32768" spicmd="95"/>
+      <AT25256A page="64" size="32768" spicmd="95"/>
+      <AT25256B page="64" size="32768" spicmd="95"/>
+      <AT25512 page="128" size="65536" spicmd="95"/>
+      <AT25DF021 id="1F4300" page="256" size="262144"/>
+      <AT25DF041 id="1F4400" page="256" size="524288"/>
+      <AT25DF041A id="1F4400" page="256" size="524288"/>
+	  <AT25SF041 id="1F8400" page="256" size="524288"/>
+      <AT25DF081 id="1F4500" page="256" size="1048576"/>
+      <AT25DF081A id="1F4500" page="256" size="1048576"/>
+      <AT25DF161 id="1F4600" page="256" size="2097152"/>
+      <AT25DF321 id="1F4700" page="256" size="4194304"/>
+      <AT25DF321A id="1F4700" page="256" size="4194304"/>
+      <AT25DF641 id="1F4800" page="256" size="8388608"/>
+      <AT25F512 id="1F65" page="256" size="65536"/>
+      <AT25F512A id="1F65" page="128" size="65536"/>
+	  <AT25F512B id="1F6500" page="256" size="65536"/>
+      <AT25F1024 id="1F60" page="256" size="131072"/>
+      <AT25F1024A id="1F60" page="256" size="131072"/>
+      <AT25F2048 id="1F63" page="256" size="262144"/>
+      <AT25F2048A id="1F63" page="256" size="262144"/>
+      <AT25F4096 id="1F64" page="256" size="524288"/>
+      <AT25F4096A id="1F64" page="256" size="524288"/>
+      <AT25HP256 page="128" size="32768" spicmd="95"/>
+      <AT25HP512 page="128" size="65536" spicmd="95"/>
+      <AT26DF081 id="1F4500" page="256" size="1048576"/>
+      <AT26DF081A id="1F4500" page="256" size="1048576"/>
+      <AT26DF161 id="1F4600" page="256" size="2097152"/>
+      <AT26DF161A id="1F4600" page="256" size="2097152"/>
+      <AT26DF321 id="1F4700" page="256" size="4194304"/>
+      <AT26DF321A id="1F4700" page="256" size="4194304"/>
+      <AT26F004 id="1F0400" page="256" size="524288"/>
+    </ATMEL>
+	<ACE>
+      <ACE25A128G_1.8V id="E06018" page="256" size="16777216"/>
+    </ACE>
+    <ATO>
+      <ATO25Q32 id="9B3216" page="256" size="4194304"/>
+    </ATO>
+	<DOUQI>
+      <DQ25Q64A id="544017" page="256" size="8388608"/>
+    </DOUQI>
+    <Fremont>
+      <FT25H16 id="0E4015" page="256" size="2097152"/>
+    </Fremont>
+    <Fudan>
+	  <FM25Q04A id="A14013" page="256" size="524288"/>
+      <FM25Q32 id="A14016" page="256" size="4194304"/>
+    </Fudan>
+    <Genitop>
+      <GT25Q80A id="E04014" page="256" size="1048576"/>
+    </Genitop>
+    <Paragon>
+      <PN25F04A id="E04013" page="256" size="524288"/>
+    </Paragon>
+  </SPI>
+  <I2C>
+    <_24Cxxx>
+      <AT24C01 page="1" size="128" addrtype="0"/>
+      <_24C01 page="1" size="128" addrtype="1"/>
+      <_24C02 page="1" size="256" addrtype="1"/>
+      <_24C04 page="1" size="512" addrtype="2"/>
+      <_24C08 page="16" size="1024" addrtype="3"/>
+      <_24C16 page="16" size="2048" addrtype="4"/>
+      <_24C32 page="32" size="4096" addrtype="5"/>
+      <_24C64 page="32" size="8192" addrtype="5"/>
+      <_24C128 page="64" size="16384" addrtype="5"/>
+      <_24C256 page="64" size="32768" addrtype="5"/>
+      <_24C512 page="128" size="65536" addrtype="5"/>
+      <_24C1024 page="128" size="131072" addrtype="6"/>
+    </_24Cxxx>
+  </I2C>
+  <Microwire>
+    <Microchip>
+      <M93C86 size="2048" addrbitlen="10"/>
+      <M93C76 size="1024" addrbitlen="10"/>
+      <M93C66 size="512" addrbitlen="8"/>
+      <M93C56 size="256" addrbitlen="8"/>
+      <M93C46 size="128" addrbitlen="6"/>
+      <M93C06 size="16" addrbitlen="6"/>
+    </Microchip>
+  </Microwire>
+</chiplist>

+ 109 - 0
spi_mem_manager/tools/chiplist_convert.py

@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+
+import argparse
+import xml.etree.ElementTree as XML
+import sys
+
+
+def getArgs():
+    parser = argparse.ArgumentParser(
+        description="chiplist.xml to C array converter",
+    )
+    parser.add_argument("file", help="chiplist.xml file")
+    return parser.parse_args()
+
+
+def getXML(file):
+    tree = XML.parse(file)
+    root = tree.getroot()
+    return root
+
+
+def parseChip(cur, arr, vendor, vendorCodeArr):
+    chip = {}
+    chipAttr = cur.attrib
+    if "page" not in chipAttr:  # chip without page size not supported
+        return
+    if "id" not in chipAttr:  # I2C not supported yet
+        return
+    if len(chipAttr["id"]) < 6:  # ID wihout capacity id not supported yet
+        return
+    chip["modelName"] = cur.tag
+    chip["vendorEnum"] = "SPIMemChipVendor" + vendor
+    chip["vendorID"] = "0x" + chipAttr["id"][0] + chipAttr["id"][1]
+    chip["typeID"] = chipAttr["id"][2] + chipAttr["id"][3]
+    chip["capacityID"] = chipAttr["id"][4] + chipAttr["id"][5]
+    chip["size"] = chipAttr["size"]
+    if chipAttr["page"] == "SSTW":
+        chip["writeMode"] = "SPIMemChipWriteModeAAIWord"
+        chip["pageSize"] = "1"
+    elif chipAttr["page"] == "SSTB":
+        chip["writeMode"] = "SPIMemChipWriteModeAAIByte"
+        chip["pageSize"] = "1"
+    else:
+        chip["writeMode"] = "SPIMemChipWriteModePage"
+        chip["pageSize"] = chipAttr["page"]
+    arr.append(chip)
+    vendorCodeArr[vendor].add(chip["vendorID"])
+
+
+def cleanEmptyVendors(vendors):
+    for cur in list(vendors):
+        if not vendors[cur]:
+            vendors.pop(cur)
+
+
+def getVendors(xml, interface):
+    arr = {}
+    for cur in xml.find(interface):
+        arr[cur.tag] = set()
+    return arr
+
+
+def parseXML(xml, interface, vendorCodeArr):
+    arr = []
+    for vendor in xml.find(interface):
+        for cur in vendor:
+            parseChip(cur, arr, vendor.tag, vendorCodeArr)
+    return arr
+
+
+def getVendorNameEnum(vendorID):
+    try:
+        return vendors[vendorID]
+    except:
+        print("Unknown vendor: " + vendorID)
+        sys.exit(1)
+
+
+def generateCArr(arr, filename):
+    with open(filename, "w") as out:
+        print('#include "spi_mem_chip_i.h"', file=out)
+        print("const SPIMemChip SPIMemChips[] = {", file=out)
+        for cur in arr:
+            print("    {" + cur["vendorID"] + ",", file=out, end="")
+            print(" 0x" + cur["typeID"] + ",", file=out, end="")
+            print(" 0x" + cur["capacityID"] + ",", file=out, end="")
+            print(' "' + cur["modelName"] + '",', file=out, end="")
+            print(" " + cur["size"] + ",", file=out, end="")
+            print(" " + cur["pageSize"] + ",", file=out, end="")
+            print(" " + cur["vendorEnum"] + ",", file=out, end="")
+            if cur == arr[-1]:
+                print(" " + cur["writeMode"] + "}};", file=out)
+            else:
+                print(" " + cur["writeMode"] + "},", file=out)
+
+def main():
+    filename = "spi_mem_chip_arr.c"
+    args = getArgs()
+    xml = getXML(args.file)
+    vendors = getVendors(xml, "SPI")
+    chipArr = parseXML(xml, "SPI", vendors)
+    cleanEmptyVendors(vendors)
+    for cur in vendors:
+        print('    {"' + cur + '", SPIMemChipVendor' + cur + "},")
+    generateCArr(chipArr, filename)
+
+
+if __name__ == "__main__":
+    main()

+ 64 - 0
spi_mem_manager/views/spi_mem_view_detect.c

@@ -0,0 +1,64 @@
+#include "spi_mem_view_detect.h"
+#include "spi_mem_manager_icons.h"
+#include <gui/elements.h>
+
+struct SPIMemDetectView {
+    View* view;
+    IconAnimation* icon;
+    SPIMemDetectViewCallback callback;
+    void* cb_ctx;
+};
+
+typedef struct {
+    IconAnimation* icon;
+} SPIMemDetectViewModel;
+
+View* spi_mem_view_detect_get_view(SPIMemDetectView* app) {
+    return app->view;
+}
+
+static void spi_mem_view_detect_draw_callback(Canvas* canvas, void* context) {
+    SPIMemDetectViewModel* model = context;
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_icon_animation(canvas, 0, 0, model->icon);
+    canvas_draw_str_aligned(canvas, 64, 26, AlignLeft, AlignCenter, "Detecting");
+    canvas_draw_str_aligned(canvas, 64, 36, AlignLeft, AlignCenter, "SPI chip...");
+}
+
+static void spi_mem_view_detect_enter_callback(void* context) {
+    SPIMemDetectView* app = context;
+    with_view_model(
+        app->view, SPIMemDetectViewModel * model, { icon_animation_start(model->icon); }, false);
+}
+
+static void spi_mem_view_detect_exit_callback(void* context) {
+    SPIMemDetectView* app = context;
+    with_view_model(
+        app->view, SPIMemDetectViewModel * model, { icon_animation_stop(model->icon); }, false);
+}
+
+SPIMemDetectView* spi_mem_view_detect_alloc() {
+    SPIMemDetectView* app = malloc(sizeof(SPIMemDetectView));
+    app->view = view_alloc();
+    view_set_context(app->view, app);
+    view_allocate_model(app->view, ViewModelTypeLocking, sizeof(SPIMemDetectViewModel));
+    with_view_model(
+        app->view,
+        SPIMemDetectViewModel * model,
+        {
+            model->icon = icon_animation_alloc(&A_ChipLooking_64x64);
+            view_tie_icon_animation(app->view, model->icon);
+        },
+        false);
+    view_set_draw_callback(app->view, spi_mem_view_detect_draw_callback);
+    view_set_enter_callback(app->view, spi_mem_view_detect_enter_callback);
+    view_set_exit_callback(app->view, spi_mem_view_detect_exit_callback);
+    return app;
+}
+
+void spi_mem_view_detect_free(SPIMemDetectView* app) {
+    with_view_model(
+        app->view, SPIMemDetectViewModel * model, { icon_animation_free(model->icon); }, false);
+    view_free(app->view);
+    free(app);
+}

+ 9 - 0
spi_mem_manager/views/spi_mem_view_detect.h

@@ -0,0 +1,9 @@
+#pragma once
+#include <gui/view.h>
+
+typedef struct SPIMemDetectView SPIMemDetectView;
+typedef void (*SPIMemDetectViewCallback)(void* context);
+
+View* spi_mem_view_detect_get_view(SPIMemDetectView* app);
+SPIMemDetectView* spi_mem_view_detect_alloc();
+void spi_mem_view_detect_free(SPIMemDetectView* app);

+ 230 - 0
spi_mem_manager/views/spi_mem_view_progress.c

@@ -0,0 +1,230 @@
+#include "spi_mem_view_progress.h"
+#include <gui/elements.h>
+
+struct SPIMemProgressView {
+    View* view;
+    SPIMemProgressViewCallback callback;
+    void* cb_ctx;
+};
+
+typedef enum {
+    SPIMemProgressViewTypeRead,
+    SPIMemProgressViewTypeVerify,
+    SPIMemProgressViewTypeWrite,
+    SPIMemProgressViewTypeUnknown
+} SPIMemProgressViewType;
+
+typedef struct {
+    size_t chip_size;
+    size_t file_size;
+    size_t blocks_written;
+    size_t block_size;
+    float progress;
+    SPIMemProgressViewType view_type;
+} SPIMemProgressViewModel;
+
+View* spi_mem_view_progress_get_view(SPIMemProgressView* app) {
+    return app->view;
+}
+
+static void spi_mem_view_progress_draw_progress(Canvas* canvas, float progress) {
+    FuriString* progress_str = furi_string_alloc();
+    if(progress > 1.0) progress = 1.0;
+    furi_string_printf(progress_str, "%d %%", (int)(progress * 100));
+    elements_progress_bar(canvas, 13, 35, 100, progress);
+    canvas_draw_str_aligned(
+        canvas, 64, 25, AlignCenter, AlignTop, furi_string_get_cstr(progress_str));
+    furi_string_free(progress_str);
+}
+
+static void
+    spi_mem_view_progress_read_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) {
+    canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Reading dump");
+    spi_mem_view_progress_draw_progress(canvas, model->progress);
+    elements_button_left(canvas, "Cancel");
+}
+
+static void
+    spi_mem_view_progress_draw_size_warning(Canvas* canvas, SPIMemProgressViewModel* model) {
+    if(model->file_size > model->chip_size) {
+        canvas_draw_str_aligned(canvas, 64, 13, AlignCenter, AlignTop, "Size clamped to chip!");
+    }
+    if(model->chip_size > model->file_size) {
+        canvas_draw_str_aligned(canvas, 64, 13, AlignCenter, AlignTop, "Size clamped to file!");
+    }
+}
+
+static void
+    spi_mem_view_progress_verify_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) {
+    canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Verifying dump");
+    spi_mem_view_progress_draw_size_warning(canvas, model);
+    spi_mem_view_progress_draw_progress(canvas, model->progress);
+    elements_button_center(canvas, "Skip");
+}
+
+static void
+    spi_mem_view_progress_write_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) {
+    canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Writing dump");
+    spi_mem_view_progress_draw_size_warning(canvas, model);
+    spi_mem_view_progress_draw_progress(canvas, model->progress);
+    elements_button_left(canvas, "Cancel");
+}
+
+static void spi_mem_view_progress_draw_callback(Canvas* canvas, void* context) {
+    SPIMemProgressViewModel* model = context;
+    SPIMemProgressViewType view_type = model->view_type;
+    if(view_type == SPIMemProgressViewTypeRead) {
+        spi_mem_view_progress_read_draw_callback(canvas, model);
+    } else if(view_type == SPIMemProgressViewTypeVerify) {
+        spi_mem_view_progress_verify_draw_callback(canvas, model);
+    } else if(view_type == SPIMemProgressViewTypeWrite) {
+        spi_mem_view_progress_write_draw_callback(canvas, model);
+    }
+}
+
+static bool
+    spi_mem_view_progress_read_write_input_callback(InputEvent* event, SPIMemProgressView* app) {
+    bool success = false;
+    if(event->type == InputTypeShort && event->key == InputKeyLeft) {
+        if(app->callback) {
+            app->callback(app->cb_ctx);
+        }
+        success = true;
+    }
+    return success;
+}
+
+static bool
+    spi_mem_view_progress_verify_input_callback(InputEvent* event, SPIMemProgressView* app) {
+    bool success = false;
+    if(event->type == InputTypeShort && event->key == InputKeyOk) {
+        if(app->callback) {
+            app->callback(app->cb_ctx);
+        }
+        success = true;
+    }
+    return success;
+}
+
+static bool spi_mem_view_progress_input_callback(InputEvent* event, void* context) {
+    SPIMemProgressView* app = context;
+    bool success = false;
+    SPIMemProgressViewType view_type;
+    with_view_model(
+        app->view, SPIMemProgressViewModel * model, { view_type = model->view_type; }, true);
+    if(view_type == SPIMemProgressViewTypeRead) {
+        success = spi_mem_view_progress_read_write_input_callback(event, app);
+    } else if(view_type == SPIMemProgressViewTypeVerify) {
+        success = spi_mem_view_progress_verify_input_callback(event, app);
+    } else if(view_type == SPIMemProgressViewTypeWrite) {
+        success = spi_mem_view_progress_read_write_input_callback(event, app);
+    }
+    return success;
+}
+
+SPIMemProgressView* spi_mem_view_progress_alloc() {
+    SPIMemProgressView* app = malloc(sizeof(SPIMemProgressView));
+    app->view = view_alloc();
+    view_allocate_model(app->view, ViewModelTypeLocking, sizeof(SPIMemProgressViewModel));
+    view_set_context(app->view, app);
+    view_set_draw_callback(app->view, spi_mem_view_progress_draw_callback);
+    view_set_input_callback(app->view, spi_mem_view_progress_input_callback);
+    spi_mem_view_progress_reset(app);
+    return app;
+}
+
+void spi_mem_view_progress_free(SPIMemProgressView* app) {
+    view_free(app->view);
+    free(app);
+}
+
+void spi_mem_view_progress_set_read_callback(
+    SPIMemProgressView* app,
+    SPIMemProgressViewCallback callback,
+    void* cb_ctx) {
+    app->callback = callback;
+    app->cb_ctx = cb_ctx;
+    with_view_model(
+        app->view,
+        SPIMemProgressViewModel * model,
+        { model->view_type = SPIMemProgressViewTypeRead; },
+        true);
+}
+
+void spi_mem_view_progress_set_verify_callback(
+    SPIMemProgressView* app,
+    SPIMemProgressViewCallback callback,
+    void* cb_ctx) {
+    app->callback = callback;
+    app->cb_ctx = cb_ctx;
+    with_view_model(
+        app->view,
+        SPIMemProgressViewModel * model,
+        { model->view_type = SPIMemProgressViewTypeVerify; },
+        true);
+}
+
+void spi_mem_view_progress_set_write_callback(
+    SPIMemProgressView* app,
+    SPIMemProgressViewCallback callback,
+    void* cb_ctx) {
+    app->callback = callback;
+    app->cb_ctx = cb_ctx;
+    with_view_model(
+        app->view,
+        SPIMemProgressViewModel * model,
+        { model->view_type = SPIMemProgressViewTypeWrite; },
+        true);
+}
+
+void spi_mem_view_progress_set_chip_size(SPIMemProgressView* app, size_t chip_size) {
+    with_view_model(
+        app->view, SPIMemProgressViewModel * model, { model->chip_size = chip_size; }, true);
+}
+
+void spi_mem_view_progress_set_file_size(SPIMemProgressView* app, size_t file_size) {
+    with_view_model(
+        app->view, SPIMemProgressViewModel * model, { model->file_size = file_size; }, true);
+}
+
+void spi_mem_view_progress_set_block_size(SPIMemProgressView* app, size_t block_size) {
+    with_view_model(
+        app->view, SPIMemProgressViewModel * model, { model->block_size = block_size; }, true);
+}
+
+static size_t spi_mem_view_progress_set_total_size(SPIMemProgressViewModel* model) {
+    size_t total_size = model->chip_size;
+    if((model->chip_size > model->file_size) && model->view_type != SPIMemProgressViewTypeRead) {
+        total_size = model->file_size;
+    }
+    return total_size;
+}
+
+void spi_mem_view_progress_inc_progress(SPIMemProgressView* app) {
+    with_view_model(
+        app->view,
+        SPIMemProgressViewModel * model,
+        {
+            size_t total_size = spi_mem_view_progress_set_total_size(model);
+            if(total_size == 0) total_size = 1;
+            model->blocks_written++;
+            model->progress =
+                ((float)model->block_size * (float)model->blocks_written) / ((float)total_size);
+        },
+        true);
+}
+
+void spi_mem_view_progress_reset(SPIMemProgressView* app) {
+    with_view_model(
+        app->view,
+        SPIMemProgressViewModel * model,
+        {
+            model->blocks_written = 0;
+            model->block_size = 0;
+            model->chip_size = 0;
+            model->file_size = 0;
+            model->progress = 0;
+            model->view_type = SPIMemProgressViewTypeUnknown;
+        },
+        true);
+}

+ 26 - 0
spi_mem_manager/views/spi_mem_view_progress.h

@@ -0,0 +1,26 @@
+#pragma once
+#include <gui/view.h>
+
+typedef struct SPIMemProgressView SPIMemProgressView;
+typedef void (*SPIMemProgressViewCallback)(void* context);
+
+View* spi_mem_view_progress_get_view(SPIMemProgressView* app);
+SPIMemProgressView* spi_mem_view_progress_alloc();
+void spi_mem_view_progress_free(SPIMemProgressView* app);
+void spi_mem_view_progress_set_read_callback(
+    SPIMemProgressView* app,
+    SPIMemProgressViewCallback callback,
+    void* cb_ctx);
+void spi_mem_view_progress_set_verify_callback(
+    SPIMemProgressView* app,
+    SPIMemProgressViewCallback callback,
+    void* cb_ctx);
+void spi_mem_view_progress_set_write_callback(
+    SPIMemProgressView* app,
+    SPIMemProgressViewCallback callback,
+    void* cb_ctx);
+void spi_mem_view_progress_set_chip_size(SPIMemProgressView* app, size_t chip_size);
+void spi_mem_view_progress_set_file_size(SPIMemProgressView* app, size_t file_size);
+void spi_mem_view_progress_set_block_size(SPIMemProgressView* app, size_t block_size);
+void spi_mem_view_progress_inc_progress(SPIMemProgressView* app);
+void spi_mem_view_progress_reset(SPIMemProgressView* app);