use std::convert::TryInto;
use std::time::{Duration, Instant};
use indicatif::{ProgressBar, ProgressStyle};
pub mod sfdp;
pub mod sreg;
pub mod id;
pub mod erase_plan;
pub use sfdp::{FlashParams, SFDPAddressBytes, SFDPEraseInst, SFDPStatus1Volatility, SFDPTiming};
pub use sreg::{StatusRegister1, StatusRegister2, StatusRegister3};
pub use id::FlashID;
use sfdp::SFDPHeader;
use erase_plan::ErasePlan;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Mismatch during flash readback verification.")]
ReadbackError { address: u32, wrote: u8, read: u8 },
#[error("Invalid manufacturer ID detected.")]
InvalidManufacturer,
#[error("Invalid SFDP header.")]
InvalidSFDPHeader,
#[error("Invalid parameter in SFDP parameter table.")]
InvalidSFDPParams,
#[error("Address out of range for memory: 0x{address:08X}.")]
InvalidAddress { address: u32 },
#[error("No supported reset instruction is available.")]
NoResetInstruction,
#[error("No erase instruction has been specified.")]
NoEraseInstruction,
#[error(transparent)]
Access(#[from] anyhow::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub trait FlashAccess {
fn write(&mut self, data: &[u8]) -> anyhow::Result<()> {
self.exchange(data)?;
Ok(())
}
fn exchange(&mut self, data: &[u8]) -> anyhow::Result<Vec<u8>>;
}
pub struct Flash<'a, A: FlashAccess> {
access: &'a mut A,
id: Option<FlashID>,
params: Option<FlashParams>,
address_bytes: u8,
capacity: Option<usize>,
page_size: Option<usize>,
erase_size: Option<usize>,
erase_opcode: u8,
}
impl<'a, A: FlashAccess> Flash<'a, A> {
const DATA_PROGRESS_TPL: &'static str =
" {msg} [{bar:40}] {bytes}/{total_bytes} ({bytes_per_sec}; {eta_precise})";
const DATA_PROGRESS_CHARS: &'static str = "=> ";
pub fn new(access: &'a mut A) -> Self {
Flash {
access,
id: None,
params: None,
address_bytes: 3,
capacity: None,
page_size: None,
erase_size: None,
erase_opcode: 0x20,
}
}
pub fn address_bytes(&self) -> u8 {
self.address_bytes
}
pub fn set_address_bytes(&mut self, n: u8) {
assert!(n >= 1, "set_address_bytes: n must be at least 1");
assert!(n <= 4, "set_address_bytes: n must not exceed 4");
self.address_bytes = n;
}
pub fn capacity(&self) -> Option<usize> {
self.capacity
}
pub fn set_capacity(&mut self, n: usize) {
self.capacity = Some(n);
}
pub fn page_size(&self) -> Option<usize> {
self.page_size
}
pub fn set_page_size(&mut self, n: usize) {
self.page_size = Some(n);
}
pub fn erase_size(&self) -> Option<usize> {
self.erase_size
}
pub fn set_erase_size(&mut self, n: usize) {
self.erase_size = Some(n);
}
pub fn erase_opcode(&self) -> u8 {
self.erase_opcode
}
pub fn set_erase_opcode(&mut self, opcode: u8) {
self.erase_opcode = opcode;
}
pub fn get_id(&self) -> Option<FlashID> {
self.id
}
pub fn get_params(&self) -> Option<FlashParams> {
self.params
}
pub fn read_id(&mut self) -> Result<FlashID> {
log::debug!("Reading SPI Flash ID");
let legacy_id = self.release_power_down()?;
self.reset()?;
let (bank_long, mfn_id_long, device_id_long) = self.read_jedec_id()?;
let (bank_short, mfn_id_short, mut device_id_short) = self.read_device_id()?;
let unique_id = self.read_unique_id()?;
let manufacturer_bank;
let manufacturer_id;
if mfn_id_long != 0x00 && mfn_id_long != 0xFF {
manufacturer_bank = bank_long;
manufacturer_id = mfn_id_long;
} else if mfn_id_short != 0x00 && mfn_id_short != 0xFF {
manufacturer_bank = bank_short;
manufacturer_id = mfn_id_short;
} else {
log::warn!("No valid manufacturer ID found");
if legacy_id == 0x00 || legacy_id == 0xFF {
log::error!("No device or manufacturer ID found");
return Err(Error::InvalidManufacturer);
} else {
device_id_short = legacy_id;
manufacturer_bank = 0;
manufacturer_id = 0;
}
}
let id = FlashID {
manufacturer_bank, manufacturer_id, device_id_short, device_id_long, unique_id
};
log::debug!("Read ID: {:?}", id);
self.id = Some(id);
Ok(id)
}
pub fn read_params(&mut self) -> Result<Option<FlashParams>> {
log::debug!("Reading SFDP data");
let data = self.read_sfdp(0, 8)?;
let nph = data[6] as usize + 1;
let data = self.read_sfdp(0, 8 + nph*8)?;
let header = match SFDPHeader::from_bytes(&data) {
Ok(header) => header,
Err(_) => return Ok(None),
};
let params = header.params[0];
if params.parameter_id != 0xFF00 || params.major != 0x01 {
log::error!("Unexpected first SFDP parameter header: expected 0xFF00 version 1.");
return Err(Error::InvalidSFDPHeader);
}
let data = self.read_sfdp(params.ptp, params.plen * 4)?;
let params = FlashParams::from_bytes(params.major, params.minor, &data)?;
self.params = Some(params);
self.address_bytes = match params.address_bytes {
SFDPAddressBytes::Three => 3,
SFDPAddressBytes::ThreeOrFour => 3,
SFDPAddressBytes::Four => 4,
_ => 3,
};
self.capacity = Some(params.capacity_bytes());
if let Some(page_size) = params.page_size {
self.page_size = Some(page_size as usize);
}
if let Some((size, opcode)) = params.sector_erase() {
self.erase_size = Some(size);
self.erase_opcode = opcode;
} else if params.legacy_4kb_erase_supported && params.legacy_4kb_erase_inst != 0xFF {
self.erase_size = Some(4096);
self.erase_opcode = params.legacy_4kb_erase_inst;
}
log::debug!("Updated settings from parameters:");
log::debug!("Address bytes: {}, capacity: {:?} bytes",
self.address_bytes, self.capacity);
log::debug!("Page size: {:?}, erase size: {:?}, erase op: {}",
self.page_size, self.erase_size, self.erase_opcode);
Ok(Some(params))
}
pub fn read(&mut self, address: u32, length: usize) -> Result<Vec<u8>> {
self.check_address_length(address, length)?;
let mut param = self.make_address(address);
param.push(0);
self.exchange(Command::FastRead, ¶m, length)
}
pub fn legacy_read(&mut self, address: u32, length: usize) -> Result<Vec<u8>> {
self.check_address_length(address, length)?;
let param = self.make_address(address);
self.exchange(Command::ReadData, ¶m, length)
}
pub fn read_cb<F: Fn(usize)>(&mut self, address: u32, length: usize, cb: F)
-> Result<Vec<u8>>
{
self.check_address_length(address, length)?;
let chunk_size = usize::max(1024, length / 128);
let start = address as usize;
let end = start + length;
let mut data = Vec::new();
cb(0);
for addr in (start..end).step_by(chunk_size) {
let size = usize::min(chunk_size, end - addr);
let mut param = self.make_address(addr as u32);
param.push(0);
data.append(&mut self.exchange(Command::FastRead, ¶m, size)?);
cb(data.len());
}
cb(data.len());
Ok(data)
}
pub fn read_progress(&mut self, address: u32, length: usize) -> Result<Vec<u8>> {
let pb = ProgressBar::new(length as u64).with_style(ProgressStyle::default_bar()
.template(Self::DATA_PROGRESS_TPL).progress_chars(Self::DATA_PROGRESS_CHARS));
pb.set_message("Reading");
let result = self.read_cb(address, length, |n| pb.set_position(n as u64));
pb.finish();
result
}
pub fn erase(&mut self) -> Result<()> {
self.write_enable()?;
self.command(Command::ChipErase)?;
self.wait_while_busy()?;
Ok(())
}
pub fn erase_progress(&mut self) -> Result<()> {
let time = self.params.map(|p| p.timing.map(|t| t.chip_erase_time_typ));
let pb = if let Some(Some(time)) = time {
ProgressBar::new(time.as_millis() as u64).with_style(ProgressStyle::default_bar()
.template(" {msg} [{bar:40}] {elapsed} < {eta}")
.progress_chars("=> "))
} else {
ProgressBar::new_spinner()
};
pb.set_message("Erasing");
let t0 = Instant::now();
self.write_enable()?;
self.command(Command::ChipErase)?;
while self.is_busy()? {
let t = t0.elapsed().as_millis() as u64;
pb.set_position(t);
}
pb.finish();
Ok(())
}
pub fn program(&mut self, address: u32, data: &[u8], verify: bool) -> Result<()> {
self.check_address_length(address, data.len())?;
let erase_plan = self.make_erase_plan(address, data.len())?;
let full_data = self.make_restore_data(address, data, &erase_plan)?;
self.run_erase_plan(&erase_plan, |_| {})?;
let start_addr = erase_plan.0[0].2;
self.program_data(start_addr, &full_data)?;
if verify {
let programmed = self.read(start_addr, full_data.len())?;
self.verify_readback(start_addr, &full_data, &programmed)?;
}
Ok(())
}
pub fn program_progress(&mut self, address: u32, data: &[u8], verify: bool) -> Result<()> {
self.check_address_length(address, data.len())?;
let erase_plan = self.make_erase_plan(address, data.len())?;
let full_data = self.make_restore_data(address, data, &erase_plan)?;
self.run_erase_plan_progress(&erase_plan)?;
let start_addr = erase_plan.0[0].2;
self.program_data_progress(start_addr, &full_data)?;
if verify {
let programmed = self.read_progress(start_addr, full_data.len())?;
self.verify_readback(start_addr, &full_data, &programmed)?;
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
let mut do_f0 = false;
let mut do_66_99 = true;
if let Some(params) = self.params {
if let Some(op_66_99) = params.reset_inst_66_99 {
do_66_99 = op_66_99;
}
if let Some(op_f0) = params.reset_inst_f0 {
do_f0 = op_f0;
}
}
if do_66_99 {
self.command(Command::EnableReset)?;
self.command(Command::Reset)
} else if do_f0 {
self.command(0xF0)
} else {
log::error!("No reset instruction available.");
Err(Error::NoResetInstruction)
}
}
pub fn is_protected(&mut self) -> Result<bool> {
log::debug!("Checking if BP bits are set");
let status1 = self.read_status1()?;
let (bp0, bp1, bp2) = status1.get_block_protect();
log::debug!("BP0: {}, BP1: {}, BP2: {}", bp0, bp1, bp2);
Ok(bp0 || bp1 || bp2)
}
pub fn protect(&mut self, bp0: bool, bp1: bool, bp2: bool) -> Result<()> {
log::debug!("Setting block protection bits to BP0={}, BP1={}, BP2={}", bp0, bp1, bp2);
let mut status1 = self.read_status1()?;
status1.set_block_protect(bp0, bp1, bp2);
self.write_status1(status1)?;
self.wait_while_busy()?;
Ok(())
}
pub fn unprotect(&mut self) -> Result<()> {
log::debug!("Checking if BP bits are set before clearing them");
let mut status1 = self.read_status1()?;
let (bp0, bp1, bp2) = status1.get_block_protect();
if bp0 || bp1 || bp2 {
log::debug!("Block protect bits are currently set, clearing.");
status1.set_block_protect(false, false, false);
self.write_status1(status1)?;
self.wait_while_busy()?;
}
Ok(())
}
pub fn unprotect_wps(&mut self) -> Result<()> {
let mut status3 = self.read_status3()?;
if status3.get_wps() {
log::debug!("WPS bit set, clearing.");
status3.set_wps(false);
self.write_status3(status3)?;
self.wait_while_busy()?;
}
Ok(())
}
pub fn power_down(&mut self) -> Result<()> {
log::debug!("Sending Powerdown command");
self.command(Command::Powerdown)
}
pub fn release_power_down(&mut self) -> Result<u8> {
log::debug!("Sending Release Powerdown command");
let data = self.exchange(Command::ReleasePowerdown, &[0, 0, 0], 1)?;
Ok(data[0])
}
pub fn program_data(&mut self, address: u32, data: &[u8]) -> Result<()> {
self.program_data_cb(address, data, |_| {})
}
pub fn program_data_progress(&mut self, address: u32, data: &[u8]) -> Result<()> {
let pb = ProgressBar::new(data.len() as u64).with_style(ProgressStyle::default_bar()
.template(Self::DATA_PROGRESS_TPL).progress_chars(Self::DATA_PROGRESS_CHARS));
pb.set_message("Writing");
self.program_data_cb(address, &data, |n| pb.set_position(n as u64))?;
pb.finish();
Ok(())
}
pub fn program_data_cb<F: Fn(usize)>(&mut self, address: u32, mut data: &[u8], cb: F)
-> Result<()>
{
let page_size = match self.page_size {
Some(page_size) => page_size,
None => {
log::info!("Page size not known. Using a default of 256 bytes.");
log::info!("Set a specific page size using `set_page_size()`.");
256
}
};
log::trace!("Programming data to 0x{:08X}, page size {} bytes", address, page_size);
let mut total_bytes = 0;
cb(total_bytes);
let first_write = page_size - ((address as usize) % page_size);
if first_write != page_size {
log::trace!("Programming partial first page of {} bytes", first_write);
self.page_program(address, &data[..first_write])?;
total_bytes += first_write;
data = &data[first_write..];
cb(total_bytes);
}
for page_data in data.chunks(page_size) {
self.page_program(address + total_bytes as u32, page_data)?;
total_bytes += page_data.len();
cb(total_bytes);
}
Ok(())
}
pub fn write_enable(&mut self) -> Result<()> {
self.command(Command::WriteEnable)
}
pub fn page_program(&mut self, address: u32, data: &[u8]) -> Result<()> {
let mut tx = self.make_address(address);
tx.extend(data);
self.write_enable()?;
self.exchange(Command::PageProgram, &tx, 0)?;
if let Some(params) = self.params {
if let Some(timing) = params.timing {
if timing.page_prog_time_typ > Duration::from_millis(1) {
std::thread::sleep(timing.page_prog_time_typ / 2);
}
}
}
self.wait_while_busy()?;
Ok(())
}
pub fn read_jedec_id(&mut self) -> Result<(u8, u8, u16)> {
let data = self.exchange(Command::ReadJEDECID, &[], 3)?;
if data[0] != 0x7F {
Ok((0, data[0], u16::from_be_bytes([data[1], data[2]])))
} else {
let data = self.exchange(Command::ReadJEDECID, &[], 16)?;
for n in 1..=13 {
if data[n] != 0x7F {
return Ok((n as u8, data[n], u16::from_be_bytes([data[n+1], data[n+2]])));
}
}
log::error!("Found more than 11 continuation bytes in manufacturer ID");
Err(Error::InvalidManufacturer)
}
}
pub fn read_device_id(&mut self) -> Result<(u8, u8, u8)> {
let data = self.exchange(Command::ReadDeviceID, &[0, 0, 0], 2)?;
if data[0] != 0x7F {
Ok((0, data[0], data[1]))
} else {
let data = self.exchange(Command::ReadJEDECID, &[0, 0, 0], 15)?;
for n in 1..=13 {
if data[n] != 0x7F {
return Ok((n as u8, data[n], data[n+1]))
}
}
log::error!("Found more than 11 continuation bytes in manufacturer ID");
Err(Error::InvalidManufacturer)
}
}
pub fn read_unique_id(&mut self) -> Result<u64> {
self.exchange(Command::ReadUniqueID, &[0, 0, 0, 0], 8)
.map(|data| u64::from_be_bytes(data.try_into().unwrap()))
}
pub fn read_status1(&mut self) -> Result<StatusRegister1> {
self.exchange(Command::ReadStatusRegister1, &[], 1).map(|data| StatusRegister1(data[0]))
}
pub fn read_status2(&mut self) -> Result<StatusRegister2> {
self.exchange(Command::ReadStatusRegister2, &[], 1).map(|data| StatusRegister2(data[0]))
}
pub fn read_status3(&mut self) -> Result<StatusRegister3> {
self.exchange(Command::ReadStatusRegister3, &[], 1).map(|data| StatusRegister3(data[0]))
}
fn write_status1(&mut self, status1: StatusRegister1) -> Result<()> {
let we_opcode = if let Some(params) = self.params {
match params.status_1_vol {
Some(SFDPStatus1Volatility::NonVolatile06) => 0x06,
Some(SFDPStatus1Volatility::Volatile06) => 0x06,
Some(SFDPStatus1Volatility::Volatile50) => 0x50,
Some(SFDPStatus1Volatility::NonVolatile06Volatile50) => 0x06,
Some(SFDPStatus1Volatility::Mixed06) => 0x06,
_ => if params.legacy_block_protect_volatile {
params.legacy_volatile_write_en_inst
} else {
Command::WriteEnable.into()
}
}
} else {
Command::WriteEnable.into()
};
self.command(we_opcode)?;
let s1 = self.read_status1()?;
log::debug!("Set WEL, s1 now: {:02X}", s1.0);
self.write(Command::WriteStatusRegister1, &[status1.0])
}
pub fn write_status2(&mut self, status2: StatusRegister2) -> Result<()> {
self.write_enable()?;
self.write(Command::WriteStatusRegister2, &[status2.0])
}
pub fn write_status3(&mut self, status3: StatusRegister3) -> Result<()> {
self.write_enable()?;
self.write(Command::WriteStatusRegister3, &[status3.0])
}
pub fn is_busy(&mut self) -> Result<bool> {
if let Some(params) = self.params {
if let Some(busy_poll_flag) = params.busy_poll_flag {
if busy_poll_flag {
let fsr = self.exchange(Command::ReadFlagStatusRegister, &[], 1)?[0];
return Ok(fsr & 0b1000_0000 == 0);
}
}
}
self.read_status1().map(|status| status.get_busy())
}
pub fn wait_while_busy(&mut self) -> Result<()> {
while self.is_busy()? {}
Ok(())
}
pub fn read_sfdp(&mut self, addr: u32, len: usize) -> Result<Vec<u8>> {
let bytes = addr.to_be_bytes();
self.exchange(Command::ReadSFDPRegister, &bytes[1..], 1+len)
.map(|data| data[1..].to_vec())
}
pub fn exchange<C: Into<u8>>(&mut self, command: C, data: &[u8], nbytes: usize)
-> Result<Vec<u8>>
{
let mut tx = vec![command.into()];
tx.extend(data);
log::trace!("SPI exchange: write {:02X?}, read {} bytes", &tx, nbytes);
tx.extend(vec![0u8; nbytes]);
let rx = self.access.exchange(&tx)?;
log::trace!("SPI exchange: read {:02X?}", &rx[1+data.len()..]);
Ok(rx[1+data.len()..].to_vec())
}
pub fn write<C: Into<u8>>(&mut self, command: C, data: &[u8]) -> Result<()> {
let mut tx = vec![command.into()];
tx.extend(data);
log::trace!("SPI write: {:02X?}", &tx);
self.access.write(&tx)?;
Ok(())
}
pub fn command<C: Into<u8>>(&mut self, command: C) -> Result<()> {
self.write(command, &[])?;
Ok(())
}
fn check_address_length(&self, address: u32, length: usize) -> Result<()> {
log::trace!("Checking address={:08X} length={}", address, length);
let start = address as usize;
let end = (address as usize) + length - 1;
let max_addr = 1 << (self.address_bytes * 8);
if (end & (max_addr - 1)) < start {
log::error!("Operation would wrap");
Err(Error::InvalidAddress { address: end as u32 })
} else if end > max_addr {
log::error!("Operation would exceed largest address");
Err(Error::InvalidAddress { address: end as u32 })
} else {
match self.capacity {
Some(capacity) if (end >= capacity) => {
log::error!("Operation would exceed flash capacity");
Err(Error::InvalidAddress { address: end as u32 })
},
_ => Ok(()),
}
}
}
fn make_address(&self, addr: u32) -> Vec<u8> {
let bytes = addr.to_be_bytes();
bytes[(4 - self.address_bytes as usize)..].to_vec()
}
fn make_erase_plan(&self, address: u32, length: usize) -> Result<ErasePlan> {
log::debug!("Creating erase plan for address={} length={}", address, length);
let mut insts = Vec::new();
if let Some(params) = self.params {
if params.erase_insts.iter().any(|&inst| inst.is_some()) {
log::trace!("Using SFDP erase instructions.");
for inst in params.erase_insts.iter() {
if let Some(inst) = inst {
insts.push((inst.size as usize, inst.opcode, inst.time_typ));
}
}
} else if params.legacy_4kb_erase_supported {
log::trace!("No erase instructions in SFDP, using legacy 4kB erase.");
insts.push((4096, params.legacy_4kb_erase_inst, None));
} else {
log::trace!("SFDP indicates no erase instructions available.");
}
}
if insts.is_empty() {
if let Some(erase_size) = self.erase_size {
log::trace!("No SFDP erase instructions found, using `erase_size` parameter.");
insts.push((erase_size, self.erase_opcode, None));
} else {
log::warn!("No erase instructions could be found.");
log::warn!("Try setting one manually using `Flash::set_erase_size()`.");
return Err(Error::NoEraseInstruction);
}
}
insts.sort();
Ok(ErasePlan::new(&insts, address as usize, length))
}
fn read_erase_preamble(&mut self, address: u32, plan: &ErasePlan) -> Result<Vec<u8>> {
let base = plan.0[0].2;
let len = address - base;
if len > 0 {
log::debug!("Reading erase preamble: base={} len={}", base, len);
self.read(base, len as usize)
} else {
Ok(Vec::new())
}
}
fn read_erase_postamble(&mut self, address: u32, length: usize, plan: &ErasePlan)
-> Result<Vec<u8>>
{
let (_, size, base, _) = plan.0.last().unwrap();
let start = address + (length as u32);
let len = (*base as usize + *size) - start as usize;
if len > 0 {
log::debug!("Reading erase postamble: addr={} len={}", start, len);
let data = self.read(start, len)?;
if data.iter().all(|x| *x == 0xFF) {
Ok(Vec::new())
} else {
Ok(data)
}
} else {
Ok(Vec::new())
}
}
fn make_restore_data(&mut self, address: u32, data: &[u8], erase_plan: &ErasePlan)
-> Result<Vec<u8>>
{
let preamble = self.read_erase_preamble(address, &erase_plan)?;
let postamble = self.read_erase_postamble(address, data.len(), &erase_plan)?;
let mut full_data = preamble;
full_data.extend(data);
full_data.extend(&postamble);
Ok(full_data)
}
fn run_erase_plan<F: Fn(usize)>(&mut self, plan: &ErasePlan, cb: F) -> Result<()> {
let mut total_erased = 0;
cb(total_erased);
for (opcode, size, base, duration) in plan.0.iter() {
log::trace!("Executing erase plan: Erase 0x{:02X} ({} bytes) from 0x{:08X}",
opcode, size, base);
let addr = self.make_address(*base);
self.write_enable()?;
self.write(*opcode, &addr)?;
if let Some(duration) = duration {
std::thread::sleep(*duration / 2);
}
self.wait_while_busy()?;
total_erased += size;
cb(total_erased);
}
cb(total_erased);
Ok(())
}
fn run_erase_plan_progress(&mut self, plan: &ErasePlan) -> Result<()> {
let erase_size = plan.total_size() as u64;
let pb = ProgressBar::new(erase_size).with_style(ProgressStyle::default_bar()
.template(Self::DATA_PROGRESS_TPL).progress_chars(Self::DATA_PROGRESS_CHARS));
pb.set_message("Erasing");
self.run_erase_plan(&plan, |n| pb.set_position(n as u64))?;
pb.finish();
Ok(())
}
fn verify_readback(&mut self, address: u32, data: &[u8], new_data: &[u8]) -> Result<()> {
let mismatch = data.iter().zip(new_data).enumerate().find(|(_, (a, b))| a != b);
match mismatch {
Some((idx, (a, b))) => {
let addr = address + idx as u32;
log::error!("Readback mismatch at 0x{:08X}: Wrote 0x{:02X}, read 0x{:02X}",
addr, a, b);
if self.is_protected()? {
log::error!("Flash write protection appears to be enabled, try unprotecting.");
}
Err(Error::ReadbackError { address: addr, wrote: *a, read: *b })
},
None => Ok(()),
}
}
}
#[derive(Copy, Clone, Debug, num_enum::IntoPrimitive)]
#[allow(unused)]
#[repr(u8)]
enum Command {
WriteEnable = 0x06,
WriteDisable = 0x04,
ReadData = 0x03,
PageProgram = 0x02,
ReadStatusRegister1 = 0x05,
WriteStatusRegister1 = 0x01,
ReadJEDECID = 0x9F,
FastRead = 0x0B,
Powerdown = 0xB9,
ReleasePowerdown = 0xAB,
ReadDeviceID = 0x90,
ChipErase = 0xC7,
ReadUniqueID = 0x4B,
ReadSFDPRegister = 0x5A,
ReadStatusRegister2 = 0x35,
ReadStatusRegister3 = 0x15,
ReadFlagStatusRegister = 0x70,
WriteStatusRegister2 = 0x31,
WriteStatusRegister3 = 0x11,
WriteEnableVolatile = 0x50,
EnableReset = 0x66,
Reset = 0x99,
ProgramSuspend = 0x75,
ProgramResume = 0x7A,
SectorErase = 0x20,
BlockErase1 = 0x52,
BlockErase2 = 0xD8,
EraseSecurityRegisters = 0x44,
ProgramSecurityRegisters = 0x42,
ReadSecurityRegisters = 0x48,
IndividualBlockLock = 0x36,
IndividualBlockUnlock = 0x39,
ReadBlockLock = 0x3D,
GlobalBlockLock = 0x7E,
GlobalBlockUnlock = 0x98,
}