/* mbed Microcontroller Library * Copyright (c) 2018 ARM Limited * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "blockdevice/internal/SFDP.h" #include "SPIFBlockDevice.h" #include "rtos/ThisThread.h" #include "mbed_critical.h" #include <string.h> #include <inttypes.h> #include "mbed_trace.h" #define TRACE_GROUP "SPIF" using namespace std::chrono; using namespace mbed; /* Default SPIF Parameters */ /****************************/ #define SPIF_DEFAULT_READ_SIZE 1 #define SPIF_DEFAULT_PROG_SIZE 1 #define SPIF_DEFAULT_SE_SIZE 4096 #define SPI_MAX_STATUS_REGISTER_SIZE 2 #ifndef UINT64_MAX #define UINT64_MAX -1 #endif #define SPI_NO_ADDRESS_COMMAND UINT64_MAX // Status Register Bits #define SPIF_STATUS_BIT_WIP 0x1 //Write In Progress #define SPIF_STATUS_BIT_WEL 0x2 // Write Enable Latch /* Basic Parameters Table Parsing */ /**********************************/ //READ Instruction support according to BUS Configuration #define SPIF_BASIC_PARAM_TABLE_FAST_READ_SUPPORT_BYTE 2 #define SPIF_BASIC_PARAM_TABLE_QPI_READ_SUPPORT_BYTE 16 #define SPIF_BASIC_PARAM_TABLE_222_READ_INST_BYTE 23 #define SPIF_BASIC_PARAM_TABLE_122_READ_INST_BYTE 15 #define SPIF_BASIC_PARAM_TABLE_112_READ_INST_BYTE 13 // Address Length #define SPIF_ADDR_SIZE_3_BYTES 3 #define SPIF_ADDR_SIZE_4_BYTES 4 // Default read/legacy erase instructions #define SPIF_INST_READ_DEFAULT 0x03 #define SPIF_INST_LEGACY_ERASE_DEFAULT (-1) #define IS_MEM_READY_MAX_RETRIES 10000 enum spif_default_instructions { SPIF_NOP = 0x00, // No operation SPIF_PP = 0x02, // Page Program data SPIF_READ = 0x03, // Read data SPIF_SE = 0x20, // 4KB Sector Erase SPIF_SFDP = 0x5A, // Read SFDP SPIF_WRSR = 0x01, // Write Status/Configuration Register SPIF_WRDI = 0x04, // Write Disable SPIF_RDSR = 0x05, // Read Status Register SPIF_WREN = 0x06, // Write Enable SPIF_RSTEN = 0x66, // Reset Enable SPIF_RST = 0x99, // Reset SPIF_RDID = 0x9F, // Read Manufacturer and JDEC Device ID SPIF_ULBPR = 0x98, // Clears all write-protection bits in the Block-Protection register, SPIF_4BEN = 0xB7, // Enable 4-byte address mode SPIF_4BDIS = 0xE9, // Disable 4-byte address mode }; // Mutex is used for some SPI Driver commands that must be done sequentially with no other commands in between // e.g. (1)Set Write Enable, (2)Program, (3)Wait Memory Ready SingletonPtr<rtos::Mutex> SPIFBlockDevice::_mutex; //*********************** // SPIF Block Device APIs //*********************** SPIFBlockDevice::SPIFBlockDevice(PinName mosi, PinName miso, PinName sclk, PinName csel, int freq) : _spi(mosi, miso, sclk, csel, use_gpio_ssel), _prog_instruction(0), _erase_instruction(0), _page_size_bytes(0), _init_ref_count(0), _is_initialized(false) { // Initial SFDP read tables are read with 8 dummy cycles // Default Bus Setup 1_1_1 with 0 dummy and mode cycles _read_dummy_and_mode_cycles = 8; _write_dummy_and_mode_cycles = 0; _dummy_and_mode_cycles = _read_dummy_and_mode_cycles; _sfdp_info.bptbl.device_size_bytes = 0; _sfdp_info.bptbl.legacy_erase_instruction = SPIF_INST_LEGACY_ERASE_DEFAULT; _sfdp_info.smptbl.regions_min_common_erase_size = 0; _sfdp_info.smptbl.region_cnt = 1; _sfdp_info.smptbl.region_erase_types_bitfld[0] = SFDP_ERASE_BITMASK_NONE; // Set default read/erase instructions _read_instruction = SPIF_INST_READ_DEFAULT; if (SPIF_BD_ERROR_OK != _spi_set_frequency(freq)) { tr_error("SPI Set Frequency Failed"); } } int SPIFBlockDevice::init() { int status = SPIF_BD_ERROR_OK; _mutex->lock(); if (!_is_initialized) { _init_ref_count = 0; } _init_ref_count++; if (_init_ref_count != 1) { goto exit_point; } _address_size = SPIF_ADDR_SIZE_3_BYTES; // Set to 3-bytes since SFDP always use only A23 ~ A0 // Soft Reset if (-1 == _reset_flash_mem()) { tr_error("init - Unable to initialize flash memory, tests failed"); status = SPIF_BD_ERROR_DEVICE_ERROR; goto exit_point; } else { tr_debug("Initialize flash memory OK"); } if (_handle_vendor_quirks() < 0) { tr_error("Init - Could not read vendor id"); status = SPIF_BD_ERROR_DEVICE_ERROR; goto exit_point; } // Synchronize Device if (false == _is_mem_ready()) { tr_error("init - _is_mem_ready Failed"); status = SPIF_BD_ERROR_READY_FAILED; goto exit_point; } /**************************** Parse SFDP headers and tables ***********************************/ { _sfdp_info.bptbl.addr = 0x0; _sfdp_info.bptbl.size = 0; _sfdp_info.smptbl.addr = 0x0; _sfdp_info.smptbl.size = 0; if (sfdp_parse_headers(callback(this, &SPIFBlockDevice::_spi_send_read_sfdp_command), _sfdp_info) < 0) { tr_error("init - Parse SFDP Headers Failed"); status = SPIF_BD_ERROR_PARSING_FAILED; goto exit_point; } if (_sfdp_parse_basic_param_table(callback(this, &SPIFBlockDevice::_spi_send_read_sfdp_command), _sfdp_info) < 0) { tr_error("init - Parse Basic Param Table Failed"); status = SPIF_BD_ERROR_PARSING_FAILED; goto exit_point; } if (sfdp_parse_sector_map_table(callback(this, &SPIFBlockDevice::_spi_send_read_sfdp_command), _sfdp_info) < 0) { tr_error("init - Parse Sector Map Table Failed"); status = SPIF_BD_ERROR_PARSING_FAILED; goto exit_point; } } // Configure BUS Mode to 1_1_1 for all commands other than Read // Dummy And Mode Cycles Back default 0 _dummy_and_mode_cycles = _write_dummy_and_mode_cycles; _is_initialized = true; tr_debug("Device size: %llu Kbytes", _sfdp_info.bptbl.device_size_bytes / 1024); if (_sfdp_info.bptbl.device_size_bytes > (1 << 24)) { tr_debug("Size is bigger than 16MB and thus address does not fit in 3 byte, switch to 4 byte address mode"); _spi_send_general_command(SPIF_4BEN, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0); _address_size = SPIF_ADDR_SIZE_4_BYTES; } exit_point: _mutex->unlock(); return status; } int SPIFBlockDevice::deinit() { spif_bd_error status = SPIF_BD_ERROR_OK; _mutex->lock(); if (!_is_initialized) { _init_ref_count = 0; goto exit_point; } _init_ref_count--; if (_init_ref_count) { goto exit_point; } // Disable Device for Writing status = _spi_send_general_command(SPIF_WRDI, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0); if (status != SPIF_BD_ERROR_OK) { tr_error("Write Disable failed"); } _is_initialized = false; exit_point: _mutex->unlock(); return status; } int SPIFBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) { if (!_is_initialized) { return BD_ERROR_DEVICE_ERROR; } int status = SPIF_BD_ERROR_OK; tr_debug("Read - Inst: 0x%xh", _read_instruction); _mutex->lock(); // Set Dummy Cycles for Specific Read Command Mode _dummy_and_mode_cycles = _read_dummy_and_mode_cycles; status = _spi_send_read_command(_read_instruction, static_cast<uint8_t *>(buffer), addr, size); // Set Dummy Cycles for all other command modes _dummy_and_mode_cycles = _write_dummy_and_mode_cycles; _mutex->unlock(); return status; } int SPIFBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size) { if (!_is_initialized) { return BD_ERROR_DEVICE_ERROR; } bool program_failed = false; int status = SPIF_BD_ERROR_OK; uint32_t offset = 0; uint32_t chunk = 0; tr_debug("program - Buff: 0x%" PRIx32 "h, addr: %llu, size: %llu", (uint32_t)buffer, addr, size); while (size > 0) { // Write on _page_size_bytes boundaries (Default 256 bytes a page) offset = addr % _page_size_bytes; chunk = (offset + size < _page_size_bytes) ? size : (_page_size_bytes - offset); _mutex->lock(); //Send WREN if (_set_write_enable() != 0) { tr_error("Write Enabe failed"); program_failed = true; status = SPIF_BD_ERROR_WREN_FAILED; goto exit_point; } _spi_send_program_command(_prog_instruction, buffer, addr, chunk); buffer = static_cast<const uint8_t *>(buffer) + chunk; addr += chunk; size -= chunk; if (false == _is_mem_ready()) { tr_error("Device not ready after write, failed"); program_failed = true; status = SPIF_BD_ERROR_READY_FAILED; goto exit_point; } _mutex->unlock(); } exit_point: if (program_failed) { _mutex->unlock(); } return status; } int SPIFBlockDevice::erase(bd_addr_t addr, bd_size_t size) { if (!_is_initialized) { return BD_ERROR_DEVICE_ERROR; } int type = 0; int cur_erase_inst = _erase_instruction; unsigned int curr_erase_size = 0; bool erase_failed = false; int status = SPIF_BD_ERROR_OK; // Find region of erased address int region = sfdp_find_addr_region(addr, _sfdp_info); if (region < 0) { tr_error("no region found for address %llu", addr); return SPIF_BD_ERROR_INVALID_ERASE_PARAMS; } // Erase Types of selected region uint8_t bitfield = _sfdp_info.smptbl.region_erase_types_bitfld[region]; tr_debug("erase - addr: %llu, size: %llu", addr, size); if ((addr + size) > _sfdp_info.bptbl.device_size_bytes) { tr_error("erase exceeds flash device size"); return SPIF_BD_ERROR_INVALID_ERASE_PARAMS; } if (((addr % get_erase_size(addr)) != 0) || (((addr + size) % get_erase_size(addr + size - 1)) != 0)) { tr_error("invalid erase - unaligned address and size"); return SPIF_BD_ERROR_INVALID_ERASE_PARAMS; } // For each iteration erase the largest section supported by current region while (size > 0) { // iterate to find next Largest erase type ( a. supported by region, b. smaller than size) // find the matching instruction and erase size chunk for that type. type = sfdp_iterate_next_largest_erase_type(bitfield, size, addr, region, _sfdp_info.smptbl); cur_erase_inst = _sfdp_info.smptbl.erase_type_inst_arr[type]; curr_erase_size = _sfdp_info.smptbl.erase_type_size_arr[type]; if (addr % curr_erase_size != 0 || addr + size < curr_erase_size) { // Should not happen if the erase table parsing // and alignment checks were performed correctly tr_error("internal error: address %llu not aligned to erase size %u (type %d)", addr, curr_erase_size, type); } tr_debug("erase - addr: %llu, size:%llu, Inst: 0x%xh, erase size: %u", addr, size, cur_erase_inst, curr_erase_size); tr_debug("erase - Region: %d, Type:%d", region, type); _mutex->lock(); if (_set_write_enable() != 0) { tr_error("SPI Erase Device not ready - failed"); erase_failed = true; status = SPIF_BD_ERROR_READY_FAILED; goto exit_point; } _spi_send_erase_command(cur_erase_inst, addr, size); addr += curr_erase_size; size -= curr_erase_size; if ((size > 0) && (addr > _sfdp_info.smptbl.region_high_boundary[region])) { // erase crossed to next region region++; bitfield = _sfdp_info.smptbl.region_erase_types_bitfld[region]; } if (false == _is_mem_ready()) { tr_error("SPI After Erase Device not ready - failed"); erase_failed = true; status = SPIF_BD_ERROR_READY_FAILED; goto exit_point; } _mutex->unlock(); } exit_point: if (erase_failed) { _mutex->unlock(); } return status; } bd_size_t SPIFBlockDevice::get_read_size() const { // Assuming all devices support 1byte read granularity return SPIF_DEFAULT_READ_SIZE; } bd_size_t SPIFBlockDevice::get_program_size() const { // Assuming all devices support 1byte program granularity return SPIF_DEFAULT_PROG_SIZE; } bd_size_t SPIFBlockDevice::get_erase_size() const { // return minimal erase size supported by all regions (0 if none exists) return _sfdp_info.smptbl.regions_min_common_erase_size; } // Find minimal erase size supported by the region to which the address belongs to bd_size_t SPIFBlockDevice::get_erase_size(bd_addr_t addr) const { // Find region of current address int region = sfdp_find_addr_region(addr, _sfdp_info); unsigned int min_region_erase_size = _sfdp_info.smptbl.regions_min_common_erase_size; int8_t type_mask = SFDP_ERASE_BITMASK_TYPE1; int i_ind = 0; if (region != -1) { type_mask = 0x01; for (i_ind = 0; i_ind < 4; i_ind++) { // loop through erase types bitfield supported by region if (_sfdp_info.smptbl.region_erase_types_bitfld[region] & type_mask) { min_region_erase_size = _sfdp_info.smptbl.erase_type_size_arr[i_ind]; break; } type_mask = type_mask << 1; } if (i_ind == 4) { tr_error("no erase type was found for region addr"); } } return (bd_size_t)min_region_erase_size; } bd_size_t SPIFBlockDevice::size() const { if (!_is_initialized) { return 0; } return _sfdp_info.bptbl.device_size_bytes; } int SPIFBlockDevice::get_erase_value() const { return 0xFF; } const char *SPIFBlockDevice::get_type() const { return "SPIF"; } /***************************************************/ /*********** SPI Driver API Functions **************/ /***************************************************/ spif_bd_error SPIFBlockDevice::_spi_set_frequency(int freq) { _spi.frequency(freq); return SPIF_BD_ERROR_OK; } spif_bd_error SPIFBlockDevice::_spi_send_read_command(int read_inst, uint8_t *buffer, bd_addr_t addr, bd_size_t size) { uint32_t dummy_bytes = _dummy_and_mode_cycles / 8; int dummy_byte = 0; _spi.select(); // Write 1 byte Instruction _spi.write(read_inst); // Write Address (can be either 3 or 4 bytes long) for (int address_shift = ((_address_size - 1) * 8); address_shift >= 0; address_shift -= 8) { _spi.write((addr >> address_shift) & 0xFF); } // Write Dummy Cycles Bytes for (uint32_t i = 0; i < dummy_bytes; i++) { _spi.write(dummy_byte); } // Read Data for (bd_size_t i = 0; i < size; i++) { buffer[i] = _spi.write(0); } _spi.deselect(); return SPIF_BD_ERROR_OK; } int SPIFBlockDevice::_spi_send_read_sfdp_command(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size, uint8_t inst, uint8_t dummy_cycles, void *rx_buffer, mbed::bd_size_t rx_length) { switch (addr_size) { case SFDP_CMD_ADDR_3_BYTE: _address_size = SPIF_ADDR_SIZE_3_BYTES; break; case SFDP_CMD_ADDR_4_BYTE: _address_size = SPIF_ADDR_SIZE_4_BYTES; break; case SFDP_CMD_ADDR_SIZE_VARIABLE: // use current setting break; case SFDP_CMD_ADDR_NONE: // no address in command addr = static_cast<int>(SPI_NO_ADDRESS_COMMAND); break; default: tr_error("Invalid SFDP command address size: 0x%02X", addr_size); return -1; } if (dummy_cycles != SFDP_CMD_DUMMY_CYCLES_VARIABLE) { _dummy_and_mode_cycles = dummy_cycles; } int status = _spi_send_read_command(inst, static_cast<uint8_t *>(rx_buffer), addr, rx_length); if (status < 0) { tr_error("_spi_send_read_sfdp_command failed"); } return status; } spif_bd_error SPIFBlockDevice::_spi_send_program_command(int prog_inst, const void *buffer, bd_addr_t addr, bd_size_t size) { // Send Program (write) command to device driver uint32_t dummy_bytes = _dummy_and_mode_cycles / 8; int dummy_byte = 0; uint8_t *data = (uint8_t *)buffer; _spi.select(); // Write 1 byte Instruction _spi.write(prog_inst); // Write Address (can be either 3 or 4 bytes long) for (int address_shift = ((_address_size - 1) * 8); address_shift >= 0; address_shift -= 8) { _spi.write((addr >> address_shift) & 0xFF); } // Write Dummy Cycles Bytes for (uint32_t i = 0; i < dummy_bytes; i++) { _spi.write(dummy_byte); } // Write Data for (bd_size_t i = 0; i < size; i++) { _spi.write(data[i]); } _spi.deselect(); return SPIF_BD_ERROR_OK; } spif_bd_error SPIFBlockDevice::_spi_send_erase_command(int erase_inst, bd_addr_t addr, bd_size_t size) { tr_debug("Erase Inst: 0x%xh, addr: %llu, size: %llu", erase_inst, addr, size); addr = (((int)addr) & 0xFFFFF000); _spi_send_general_command(erase_inst, addr, NULL, 0, NULL, 0); return SPIF_BD_ERROR_OK; } spif_bd_error SPIFBlockDevice::_spi_send_general_command(int instruction, bd_addr_t addr, char *tx_buffer, size_t tx_length, char *rx_buffer, size_t rx_length) { // Send a general command Instruction to driver uint32_t dummy_bytes = _dummy_and_mode_cycles / 8; uint8_t dummy_byte = 0x00; _spi.select(); // Write 1 byte Instruction _spi.write(instruction); // Reading SPI Bus registers does not require Flash Address if (addr != SPI_NO_ADDRESS_COMMAND) { // Write Address (can be either 3 or 4 bytes long) for (int address_shift = ((_address_size - 1) * 8); address_shift >= 0; address_shift -= 8) { _spi.write((addr >> address_shift) & 0xFF); } // Write Dummy Cycles Bytes for (uint32_t i = 0; i < dummy_bytes; i++) { _spi.write(dummy_byte); } } // Read/Write Data _spi.write(tx_buffer, (int)tx_length, rx_buffer, (int)rx_length); _spi.deselect(); return SPIF_BD_ERROR_OK; } /*********************************************************/ /********** SFDP Parsing and Detection Functions *********/ /*********************************************************/ int SPIFBlockDevice::_sfdp_parse_basic_param_table(Callback<int(bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info) { uint8_t param_table[SFDP_BASIC_PARAMS_TBL_SIZE]; /* Up To 20 DWORDS = 80 Bytes */ int status = sfdp_reader( sfdp_info.bptbl.addr, SFDP_READ_CMD_ADDR_TYPE, SFDP_READ_CMD_INST, SFDP_READ_CMD_DUMMY_CYCLES, param_table, sfdp_info.bptbl.size ); if (status != SPIF_BD_ERROR_OK) { tr_error("init - Read SFDP First Table Failed"); return -1; } // Check address size, currently only supports 3byte addresses if (sfdp_detect_addressability(param_table, _sfdp_info.bptbl) < 0) { tr_error("Verify 3byte addressing failed"); return -1; } if (sfdp_detect_device_density(param_table, _sfdp_info.bptbl) < 0) { tr_error("Detecting device density failed"); return -1; } // Set Default read/program/erase Instructions _read_instruction = SPIF_READ; _prog_instruction = SPIF_PP; _erase_instruction = SPIF_SE; // Set Page Size (SPI write must be done on Page limits) _page_size_bytes = sfdp_detect_page_size(param_table, sfdp_info.bptbl.size); // Detect and Set Erase Types if (sfdp_detect_erase_types_inst_and_size(param_table, sfdp_info) < 0) { tr_error("Init - Detecting erase types instructions/sizes failed"); return -1; } _erase_instruction = sfdp_info.bptbl.legacy_erase_instruction; // Detect and Set fastest Bus mode (default 1-1-1) _sfdp_detect_best_bus_read_mode(param_table, sfdp_info.bptbl.size, _read_instruction); return 0; } int SPIFBlockDevice::_sfdp_detect_best_bus_read_mode(uint8_t *basic_param_table_ptr, int basic_param_table_size, int &read_inst) { do { // TBD - SPIF Dual Read Modes Require SPI driver support _read_dummy_and_mode_cycles = 0; tr_debug("Read Bus Mode set to 1-1-1, Instruction: 0x%xh", read_inst); } while (false); return 0; } int SPIFBlockDevice::_reset_flash_mem() { // Perform Soft Reset of the Device prior to initialization int status = 0; char status_value[2] = {0}; tr_info("_reset_flash_mem:"); // Read the Status Register from device if (SPIF_BD_ERROR_OK == _spi_send_general_command(SPIF_RDSR, SPI_NO_ADDRESS_COMMAND, NULL, 0, status_value, 1)) { // store received values in status_value tr_debug("Reading Status Register Success: value = 0x%x", (int)status_value[0]); } else { tr_error("Reading Status Register failed"); status = -1; } if (0 == status) { // Send Reset Enable if (SPIF_BD_ERROR_OK == _spi_send_general_command(SPIF_RSTEN, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0)) { // store received values in status_value tr_debug("Sending RSTEN Success"); } else { tr_error("Sending RSTEN failed"); status = -1; } if (0 == status) { // Send Reset if (SPIF_BD_ERROR_OK == _spi_send_general_command(SPIF_RST, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0)) { // store received values in status_value tr_debug("Sending RST Success"); } else { tr_error("Sending RST failed"); status = -1; } if (false == _is_mem_ready()) { tr_error("Device not ready, write failed"); status = -1; } } } return status; } bool SPIFBlockDevice::_is_mem_ready() { // Check Status Register Busy Bit to Verify the Device isn't Busy char status_value[2]; int retries = 0; bool mem_ready = true; do { rtos::ThisThread::sleep_for(1ms); retries++; // Read the Status Register from device if (SPIF_BD_ERROR_OK != _spi_send_general_command(SPIF_RDSR, SPI_NO_ADDRESS_COMMAND, NULL, 0, status_value, 1)) { // store received values in status_value tr_error("Reading Status Register failed"); } } while ((status_value[0] & SPIF_STATUS_BIT_WIP) != 0 && retries < IS_MEM_READY_MAX_RETRIES); if ((status_value[0] & SPIF_STATUS_BIT_WIP) != 0) { tr_error("_is_mem_ready FALSE"); mem_ready = false; } return mem_ready; } int SPIFBlockDevice::_set_write_enable() { // Check Status Register Busy Bit to Verify the Device isn't Busy char status_value[2]; int status = -1; do { if (SPIF_BD_ERROR_OK != _spi_send_general_command(SPIF_WREN, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0)) { tr_error("Sending WREN command FAILED"); break; } if (false == _is_mem_ready()) { tr_error("Device not ready, write failed"); break; } memset(status_value, 0, 2); if (SPIF_BD_ERROR_OK != _spi_send_general_command(SPIF_RDSR, SPI_NO_ADDRESS_COMMAND, NULL, 0, status_value, 1)) { // store received values in status_value tr_error("Reading Status Register failed"); break; } if ((status_value[0] & SPIF_STATUS_BIT_WEL) == 0) { tr_error("_set_write_enable failed"); break; } status = 0; } while (false); return status; } int SPIFBlockDevice::_handle_vendor_quirks() { uint8_t vendor_device_ids[4]; size_t data_length = 3; // Read Manufacturer ID (1byte), and Device ID (2bytes) spif_bd_error spi_status = _spi_send_general_command(SPIF_RDID, SPI_NO_ADDRESS_COMMAND, NULL, 0, (char *)vendor_device_ids, data_length); if (spi_status != SPIF_BD_ERROR_OK) { tr_error("Read Vendor ID Failed"); return -1; } tr_debug("Vendor device ID = 0x%x 0x%x 0x%x", vendor_device_ids[0], vendor_device_ids[1], vendor_device_ids[2]); switch (vendor_device_ids[0]) { case 0xBF: // SST devices come preset with block protection // enabled for some regions, issue global protection unlock to clear _set_write_enable(); _spi_send_general_command(SPIF_ULBPR, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0); break; } return 0; }