blob: eda6f4e27ad7537655f41a56da62348697b63f95 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use crate::io::eeprom::{AddressMode, Transaction, MODE_111};
use crate::io::spi::Target;
use crate::spiflash::sfdp::{BlockEraseSize, Sfdp, SupportedAddressModes};
use anyhow::{ensure, Result};
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("address out of bounds: {0} >= {1}")]
AddressOutOfBounds(u32, u32),
#[error("erase at address {0} not aligned on {1}-byte boundary")]
BadEraseAddress(u32, u32),
#[error("erase length {0} not a multiple of {1} bytes")]
BadEraseLength(u32, u32),
#[error("bad sequence length: {0}")]
BadSequenceLength(usize),
}
impl From<SupportedAddressModes> for AddressMode {
fn from(mode: SupportedAddressModes) -> Self {
match mode {
SupportedAddressModes::Mode4b => AddressMode::Mode4b,
_ => AddressMode::Mode3b,
}
}
}
pub struct SpiFlash {
pub size: u32,
pub erase_size: u32,
pub erase_opcode: u8,
pub program_size: u32,
pub address_mode: AddressMode,
pub sfdp: Option<Sfdp>,
}
impl Default for SpiFlash {
fn default() -> Self {
SpiFlash {
size: 16 * 1024 * 1024,
erase_size: 4 * 1024,
erase_opcode: 0x20,
program_size: SpiFlash::LEGACY_PAGE_SIZE,
address_mode: AddressMode::default(),
sfdp: None,
}
}
}
impl SpiFlash {
// Well known SPI Flash opcodes.
pub const READ: u8 = 0x03;
pub const READ4: u8 = 0x13;
pub const PAGE_PROGRAM: u8 = 0x02;
pub const SECTOR_ERASE: u8 = 0x20;
pub const BLOCK_ERASE_3B: u8 = 0xd8;
pub const BLOCK_ERASE_4B: u8 = 0xdc;
pub const CHIP_ERASE: u8 = 0xc7;
pub const WRITE_ENABLE: u8 = 0x06;
pub const WRITE_DISABLE: u8 = 0x04;
pub const READ_STATUS: u8 = 0x05;
// Winbond parts use 0x35 and 0x15 for extended status reads.
pub const READ_STATUS2: u8 = 0x35;
pub const READ_STATUS3: u8 = 0x15;
pub const WRITE_STATUS: u8 = 0x01;
// Winbond parts use 0x31 and 0x11 for extended status writes.
pub const WRITE_STATUS2: u8 = 0x31;
pub const WRITE_STATUS3: u8 = 0x11;
pub const READ_ID: u8 = 0x9f;
pub const ENTER_4B: u8 = 0xb7;
pub const EXIT_4B: u8 = 0xe9;
pub const READ_SFDP: u8 = 0x5a;
pub const NOP: u8 = 0x00;
pub const RESET_ENABLE: u8 = 0x66;
pub const RESET: u8 = 0x99;
/// The legacy JEDEC page size for programming operations is 256 bytes.
pub const LEGACY_PAGE_SIZE: u32 = 256;
/// Status register bits:
/// The `WIP` bit indicates a write in progress (sometimes called the busy bit).
pub const STATUS_WIP: u8 = 0x01;
/// The `WEL` bit is the write enable latch.
pub const STATUS_WEL: u8 = 0x02;
/// Read `length` bytes of the JEDEC ID from the `spi` target.
pub fn read_jedec_id(spi: &dyn Target, length: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; length];
spi.run_eeprom_transactions(&mut [Transaction::Read(
MODE_111.cmd(SpiFlash::READ_ID),
&mut buf,
)])?;
Ok(buf)
}
/// Read status register from the `spi` target.
pub fn read_status(spi: &dyn Target) -> Result<u8> {
let mut buf = [0u8; 1];
spi.run_eeprom_transactions(&mut [Transaction::Read(
MODE_111.cmd(SpiFlash::READ_STATUS),
&mut buf,
)])?;
Ok(buf[0])
}
/// Read the extended status register from the `spi` target.
pub fn read_status_ex(spi: &dyn Target, seq: Option<&[u8]>) -> Result<u32> {
let seq = seq.unwrap_or(&[Self::READ_STATUS, Self::READ_STATUS2, Self::READ_STATUS3]);
ensure!(
seq.len() > 0 && seq.len() <= 3,
Error::BadSequenceLength(seq.len())
);
let mut buf = [0u8; 4];
for (op, byte) in seq.iter().zip(buf.iter_mut()) {
spi.run_eeprom_transactions(&mut [Transaction::Read(
MODE_111.cmd(*op),
std::slice::from_mut(byte),
)])?;
}
Ok(u32::from_le_bytes(buf))
}
/// Poll the status register waiting for the busy bit to clear.
pub fn wait_for_busy_clear(spi: &dyn Target) -> Result<()> {
spi.run_eeprom_transactions(&mut [Transaction::WaitForBusyClear])?;
Ok(())
}
/// Send the WRITE_ENABLE opcode to the `spi` target.
pub fn set_write_enable(spi: &dyn Target) -> Result<()> {
spi.run_eeprom_transactions(&mut [Transaction::Command(
MODE_111.cmd(SpiFlash::WRITE_ENABLE),
)])?;
Ok(())
}
/// Send the WRITE_DISABLE opcode to the `spi` target.
pub fn set_write_disable(spi: &dyn Target) -> Result<()> {
spi.run_eeprom_transactions(&mut [Transaction::Command(
MODE_111.cmd(SpiFlash::WRITE_DISABLE),
)])?;
Ok(())
}
/// Read and parse the SFDP table from the `spi` target.
pub fn read_sfdp(spi: &dyn Target) -> Result<Sfdp> {
let mut buf = vec![0u8; 256];
let mut tries = 0;
loop {
// READ_SFDP always takes a 3-byte address followed by a dummy byte regardless of
// address mode.
spi.run_eeprom_transactions(&mut [Transaction::Read(
MODE_111
.dummy_cycles(8)
.cmd_addr(SpiFlash::READ_SFDP, 0, AddressMode::Mode3b),
&mut buf,
)])?;
// We only want to give SFDP parsing one extra chance for length
// extension. If parsing fails a second time, just return the error.
let result = Sfdp::try_from(&buf[..]);
if result.is_ok() || tries > 0 {
return result;
}
buf.resize(Sfdp::length_required(&buf)?, 0);
tries += 1;
}
}
/// Create a new `SpiFlash` instance from an SFDP table.
pub fn from_sfdp(sfdp: Sfdp) -> Self {
let (erase_sz, erase_op) = match sfdp.jedec.block_erase_size {
BlockEraseSize::Block4KiB => (4096, sfdp.jedec.erase_opcode_4kib),
_ => (sfdp.jedec.erase[0].size, sfdp.jedec.erase[0].opcode),
};
SpiFlash {
size: sfdp.jedec.density,
erase_size: erase_sz,
erase_opcode: erase_op,
program_size: SpiFlash::LEGACY_PAGE_SIZE,
address_mode: AddressMode::from(sfdp.jedec.address_modes),
sfdp: Some(sfdp),
}
}
/// Create a new `SpiFlash` instance by reading an SFDP table from the `spi` Target.
pub fn from_spi(spi: &dyn Target) -> Result<Self> {
let sfdp = SpiFlash::read_sfdp(spi)?;
Ok(SpiFlash::from_sfdp(sfdp))
}
/// Set the SPI flash addressing mode to either 3b or 4b mode.
pub fn set_address_mode(&mut self, spi: &dyn Target, mode: AddressMode) -> Result<()> {
let opcode = match mode {
AddressMode::Mode3b => SpiFlash::EXIT_4B,
AddressMode::Mode4b => SpiFlash::ENTER_4B,
};
spi.run_eeprom_transactions(&mut [Transaction::Command(MODE_111.cmd(opcode))])?;
self.address_mode = mode;
Ok(())
}
/// Automatically set the addressing mode based on the size of the SPI flash.
pub fn set_address_mode_auto(&mut self, spi: &dyn Target) -> Result<()> {
self.set_address_mode(
spi,
if self.size <= 16 * 1024 * 1024 {
AddressMode::Mode3b
} else {
AddressMode::Mode4b
},
)
}
/// Read into `buffer` from the SPI flash starting at `address`.
pub fn read(&self, spi: &dyn Target, address: u32, buffer: &mut [u8]) -> Result<&Self> {
self.read_with_progress(spi, address, buffer, |_, _| {})
}
/// Read into `buffer` from the SPI flash starting at `address`.
/// The `progress` callback will be invoked after each chunk of the read operation.
pub fn read_with_progress(
&self,
spi: &dyn Target,
mut address: u32,
buffer: &mut [u8],
progress: impl Fn(u32, u32),
) -> Result<&Self> {
// Break the read up according to the maximum chunksize the backend can handle.
for chunk in buffer.chunks_mut(spi.max_chunk_size()?) {
let opcode = match self.address_mode {
AddressMode::Mode3b => SpiFlash::READ,
AddressMode::Mode4b => SpiFlash::READ4,
};
spi.run_eeprom_transactions(&mut [Transaction::Read(
MODE_111.cmd_addr(opcode, address, self.address_mode),
chunk,
)])?;
address += chunk.len() as u32;
progress(address, chunk.len() as u32);
}
Ok(self)
}
/// Erase the entire EEPROM via the CHIP_ERASE opcode.
pub fn chip_erase(&self, spi: &dyn Target) -> Result<&Self> {
spi.run_eeprom_transactions(&mut [
Transaction::Command(MODE_111.cmd(SpiFlash::WRITE_ENABLE)),
Transaction::Command(MODE_111.cmd(SpiFlash::CHIP_ERASE)),
Transaction::WaitForBusyClear,
])?;
Ok(self)
}
// Erase in 256kB blocks.
pub fn block_erase(&self, spi: &dyn Target, address: u32, length: u32) -> Result<()> {
self.block_erase_with_progress(spi, address, length, |_, _| {})
}
/// Erase a segment of the SPI flash starting at `address` for `length` bytes.
/// The address and length must be sector aligned.
pub fn erase(&self, spi: &dyn Target, address: u32, length: u32) -> Result<&Self> {
self.erase_with_progress(spi, address, length, |_, _| {})
}
/// Block erase, and a progress bar.
pub fn block_erase_with_progress(
&self,
spi: &dyn Target,
address: u32,
length: u32,
progress: impl Fn(u32, u32),
) -> Result<()> {
// 256kB sectors
let sector_size = 256 * 1024;
if address % sector_size != 0 {
return Err(Error::BadEraseAddress(address, sector_size).into());
}
if length % sector_size != 0 {
return Err(Error::BadEraseAddress(length, sector_size).into());
}
let end = address + length;
for addr in (address..end).step_by(sector_size as usize) {
let opcode = match self.address_mode {
AddressMode::Mode3b => SpiFlash::BLOCK_ERASE_3B,
AddressMode::Mode4b => SpiFlash::BLOCK_ERASE_4B,
};
spi.run_eeprom_transactions(&mut [
Transaction::Command(MODE_111.cmd(SpiFlash::WRITE_ENABLE)),
Transaction::Command(MODE_111.cmd_addr(opcode, addr, self.address_mode)),
Transaction::WaitForBusyClear,
])?;
progress(addr, sector_size);
}
Ok(())
}
/// Erase a segment of the SPI flash starting at `address` for `length` bytes.
/// The address and length must be sector aligned.
/// The `progress` callback will be invoked after each chunk of the erase operation.
pub fn erase_with_progress(
&self,
spi: &dyn Target,
address: u32,
length: u32,
progress: impl Fn(u32, u32),
) -> Result<&Self> {
if address % self.erase_size != 0 {
return Err(Error::BadEraseAddress(address, self.erase_size).into());
}
if length % self.erase_size != 0 {
return Err(Error::BadEraseLength(length, self.erase_size).into());
}
let end = address + length;
for addr in (address..end).step_by(self.erase_size as usize) {
spi.run_eeprom_transactions(&mut [
Transaction::Command(MODE_111.cmd(SpiFlash::WRITE_ENABLE)),
Transaction::Command(MODE_111.cmd_addr(
SpiFlash::SECTOR_ERASE,
addr,
self.address_mode,
)),
Transaction::WaitForBusyClear,
])?;
progress(addr, self.erase_size);
}
Ok(self)
}
/// Program a segment of the SPI flash starting at `address` with the contents of `buffer`.
/// The address and buffer length may be arbitrary. This function will not
/// erase the segment first.
pub fn program(&self, spi: &dyn Target, address: u32, buffer: &[u8]) -> Result<&Self> {
self.program_with_progress(spi, address, buffer, |_, _| {})
}
/// Program a segment of the SPI flash starting at `address` with the contents of `buffer`.
/// The address and buffer length may be arbitrary. This function will not
/// erase the segment first.
/// The `progress` callback will be invoked after each chunk of the program operation.
pub fn program_with_progress(
&self,
spi: &dyn Target,
mut address: u32,
buffer: &[u8],
progress: impl Fn(u32, u32),
) -> Result<&Self> {
let mut remain = buffer.len();
let mut chunk_start = 0usize;
while remain != 0 {
// If the address isn't program-page-size aligned, adjust the first
// chunk so that subsequent writes will be so-aligned.
// This is necessary because the SPI eeprom will wrap within the
// programming page and the resultant data in the eeprom will not
// be what you intended.
let chunk_size = (self.program_size - (address % self.program_size)) as usize;
let chunk_size = std::cmp::min(chunk_size, remain);
let chunk_end = chunk_start + chunk_size;
let chunk = &buffer[chunk_start..chunk_end];
// Skip this chunk if all bytes are 0xff.
if !chunk.iter().all(|&x| x == 0xff) {
spi.run_eeprom_transactions(&mut [
Transaction::Command(MODE_111.cmd(SpiFlash::WRITE_ENABLE)),
Transaction::Write(
MODE_111.cmd_addr(SpiFlash::PAGE_PROGRAM, address, self.address_mode),
chunk,
),
Transaction::WaitForBusyClear,
])?;
}
address += chunk_size as u32;
chunk_start += chunk_size;
remain -= chunk_size;
progress(address, chunk_size as u32);
}
Ok(self)
}
/// Send the software reset sequence to the `spi` target.
pub fn chip_reset(spi: &dyn Target) -> Result<()> {
spi.run_eeprom_transactions(&mut [
Transaction::Command(MODE_111.cmd(SpiFlash::RESET_ENABLE)),
Transaction::Command(MODE_111.cmd(SpiFlash::RESET)),
])?;
Ok(())
}
}