use once_cell::sync::OnceCell;
use portable_atomic::{AtomicU8, Ordering};
use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray};
use crate::save::asm_utils::*;
use crate::save::utils::Timeout;
use crate::save::{MediaInfo, MediaType, RawSaveAccess, StorageError};
use core::cmp;
const FLASH_PORT_BANK: MemoryMapped<u8> = unsafe { MemoryMapped::new(0x0E000000) };
const FLASH_PORT_A: MemoryMapped<u8> = unsafe { MemoryMapped::new(0x0E005555) };
const FLASH_PORT_B: MemoryMapped<u8> = unsafe { MemoryMapped::new(0x0E002AAA) };
const FLASH_DATA: MemoryMapped1DArray<u8, 65536> = unsafe { MemoryMapped1DArray::new(0x0E000000) };
const BANK_SHIFT: usize = 16; const BANK_LEN: usize = 1 << BANK_SHIFT;
const BANK_MASK: usize = BANK_LEN - 1;
const CMD_SET_BANK: u8 = 0xB0;
const CMD_READ_CHIP_ID: u8 = 0x90;
const CMD_READ_CONTENTS: u8 = 0xF0;
const CMD_WRITE: u8 = 0xA0;
const CMD_ERASE_SECTOR_BEGIN: u8 = 0x80;
const CMD_ERASE_SECTOR_CONFIRM: u8 = 0x30;
const CMD_ERASE_SECTOR_ALL: u8 = 0x10;
fn start_flash_command() {
FLASH_PORT_A.set(0xAA);
FLASH_PORT_B.set(0x55);
}
fn issue_flash_command(c2: u8) {
start_flash_command();
FLASH_PORT_A.set(c2);
}
fn set_bank(bank: u8) -> Result<(), StorageError> {
static CURRENT_BANK: AtomicU8 = AtomicU8::new(!0);
if bank == 0xFF {
Err(StorageError::OutOfBounds)
} else if bank != CURRENT_BANK.load(Ordering::SeqCst) {
issue_flash_command(CMD_SET_BANK);
FLASH_PORT_BANK.set(bank);
CURRENT_BANK.store(bank, Ordering::SeqCst);
Ok(())
} else {
Ok(())
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
#[repr(u8)]
pub enum FlashChipType {
Sst64K,
Macronix64K,
Panasonic64K,
Atmel64K,
Sanyo128K,
Macronix128K,
Unknown,
}
impl FlashChipType {
pub fn detect() -> Result<Self, StorageError> {
Ok(Self::from_id(detect_chip_id()?))
}
pub fn from_id(id: u16) -> Self {
match id {
0xD4BF => FlashChipType::Sst64K,
0x1CC2 => FlashChipType::Macronix64K,
0x1B32 => FlashChipType::Panasonic64K,
0x3D1F => FlashChipType::Atmel64K,
0x1362 => FlashChipType::Sanyo128K,
0x09C2 => FlashChipType::Macronix128K,
_ => FlashChipType::Unknown,
}
}
}
pub fn detect_chip_id() -> Result<u16, StorageError> {
issue_flash_command(CMD_READ_CHIP_ID);
let high = unsafe { read_raw_byte(0x0E000001) };
let low = unsafe { read_raw_byte(0x0E000000) };
let id = ((high as u16) << 8) | low as u16;
issue_flash_command(CMD_READ_CONTENTS);
Ok(id)
}
#[allow(dead_code)]
struct ChipInfo {
read_wait: u8,
write_wait: u8,
write_timeout: u16,
erase_sector_timeout: u16,
erase_chip_timeout: u16,
bank_count: u8,
uses_atmel_api: bool,
requires_cancel_command: bool,
info: &'static MediaInfo,
}
static INFO_64K: MediaInfo = MediaInfo {
media_type: MediaType::Flash64K,
sector_shift: 12, sector_count: 16, uses_prepare_write: true,
};
static INFO_64K_ATMEL: MediaInfo = MediaInfo {
media_type: MediaType::Flash64K,
sector_shift: 7, sector_count: 512, uses_prepare_write: false,
};
static INFO_128K: MediaInfo = MediaInfo {
media_type: MediaType::Flash128K,
sector_shift: 12,
sector_count: 32, uses_prepare_write: true,
};
static CHIP_INFO_SST_64K: ChipInfo = ChipInfo {
read_wait: 2, write_wait: 1, write_timeout: 10,
erase_sector_timeout: 40,
erase_chip_timeout: 200,
bank_count: 1,
uses_atmel_api: false,
requires_cancel_command: false,
info: &INFO_64K,
};
static CHIP_INFO_MACRONIX_64K: ChipInfo = ChipInfo {
read_wait: 1, write_wait: 3, write_timeout: 10,
erase_sector_timeout: 2000,
erase_chip_timeout: 2000,
bank_count: 1,
uses_atmel_api: false,
requires_cancel_command: true,
info: &INFO_64K,
};
static CHIP_INFO_PANASONIC_64K: ChipInfo = ChipInfo {
read_wait: 2, write_wait: 0, write_timeout: 10,
erase_sector_timeout: 500,
erase_chip_timeout: 500,
bank_count: 1,
uses_atmel_api: false,
requires_cancel_command: false,
info: &INFO_64K,
};
static CHIP_INFO_ATMEL_64K: ChipInfo = ChipInfo {
read_wait: 3, write_wait: 3, write_timeout: 40,
erase_sector_timeout: 40,
erase_chip_timeout: 40,
bank_count: 1,
uses_atmel_api: true,
requires_cancel_command: false,
info: &INFO_64K_ATMEL,
};
static CHIP_INFO_GENERIC_64K: ChipInfo = ChipInfo {
read_wait: 3, write_wait: 3, write_timeout: 40,
erase_sector_timeout: 2000,
erase_chip_timeout: 2000,
bank_count: 1,
uses_atmel_api: false,
requires_cancel_command: true,
info: &INFO_128K,
};
static CHIP_INFO_GENERIC_128K: ChipInfo = ChipInfo {
read_wait: 1, write_wait: 3, write_timeout: 10,
erase_sector_timeout: 2000,
erase_chip_timeout: 2000,
bank_count: 2,
uses_atmel_api: false,
requires_cancel_command: false,
info: &INFO_128K,
};
impl FlashChipType {
fn chip_info(self) -> &'static ChipInfo {
match self {
FlashChipType::Sst64K => &CHIP_INFO_SST_64K,
FlashChipType::Macronix64K => &CHIP_INFO_MACRONIX_64K,
FlashChipType::Panasonic64K => &CHIP_INFO_PANASONIC_64K,
FlashChipType::Atmel64K => &CHIP_INFO_ATMEL_64K,
FlashChipType::Sanyo128K => &CHIP_INFO_GENERIC_128K,
FlashChipType::Macronix128K => &CHIP_INFO_GENERIC_128K,
FlashChipType::Unknown => &CHIP_INFO_GENERIC_64K,
}
}
}
fn cached_chip_info() -> Result<&'static ChipInfo, StorageError> {
static CHIP_INFO: OnceCell<&'static ChipInfo> = OnceCell::new();
for _ in 0..100 {
unsafe { core::arch::asm!("nop") };
}
CHIP_INFO
.get_or_try_init(|| -> Result<_, StorageError> { Ok(FlashChipType::detect()?.chip_info()) })
.cloned()
}
impl ChipInfo {
fn total_len(&self) -> usize {
self.info.sector_count << self.info.sector_shift
}
fn check_len(&self, offset: usize, len: usize) -> Result<(), StorageError> {
if offset.checked_add(len).is_some() && offset + len <= self.total_len() {
Ok(())
} else {
Err(StorageError::OutOfBounds)
}
}
fn check_sector_len(&self, offset: usize, len: usize) -> Result<(), StorageError> {
if offset.checked_add(len).is_some() && offset + len <= self.info.sector_count {
Ok(())
} else {
Err(StorageError::OutOfBounds)
}
}
fn set_bank(&self, bank: usize) -> Result<(), StorageError> {
if bank >= self.bank_count as usize {
Err(StorageError::OutOfBounds)
} else if self.bank_count > 1 {
set_bank(bank as u8)
} else {
Ok(())
}
}
fn read_buffer(&self, mut offset: usize, mut buf: &mut [u8]) -> Result<(), StorageError> {
while !buf.is_empty() {
self.set_bank(offset >> BANK_SHIFT)?;
let start = offset & BANK_MASK;
let end_len = cmp::min(BANK_LEN - start, buf.len());
unsafe {
read_raw_buf(&mut buf[..end_len], 0x0E000000 + start);
}
buf = &mut buf[end_len..];
offset += end_len;
}
Ok(())
}
fn verify_buffer(&self, mut offset: usize, mut buf: &[u8]) -> Result<bool, StorageError> {
while !buf.is_empty() {
self.set_bank(offset >> BANK_SHIFT)?;
let start = offset & BANK_MASK;
let end_len = cmp::min(BANK_LEN - start, buf.len());
if !unsafe { verify_raw_buf(&buf[..end_len], 0x0E000000 + start) } {
return Ok(false);
}
buf = &buf[end_len..];
offset += end_len;
}
Ok(true)
}
fn wait_for_timeout(
&self,
offset: usize,
val: u8,
ms: u16,
timeout: &mut Timeout,
) -> Result<(), StorageError> {
timeout.start();
let offset = 0x0E000000 + offset;
while unsafe { read_raw_byte(offset) != val } {
if timeout.check_timeout_met(ms) {
if self.requires_cancel_command {
FLASH_PORT_A.set(0xF0);
}
return Err(StorageError::OperationTimedOut);
}
}
Ok(())
}
fn erase_sector(&self, sector: usize, timeout: &mut Timeout) -> Result<(), StorageError> {
let offset = sector << self.info.sector_shift;
self.set_bank(offset >> BANK_SHIFT)?;
issue_flash_command(CMD_ERASE_SECTOR_BEGIN);
start_flash_command();
FLASH_DATA.set(offset & BANK_MASK, CMD_ERASE_SECTOR_CONFIRM);
self.wait_for_timeout(offset & BANK_MASK, 0xFF, self.erase_sector_timeout, timeout)
}
fn erase_chip(&self, timeout: &mut Timeout) -> Result<(), StorageError> {
issue_flash_command(CMD_ERASE_SECTOR_BEGIN);
issue_flash_command(CMD_ERASE_SECTOR_ALL);
self.wait_for_timeout(0, 0xFF, 3000, timeout)
}
fn write_byte(
&self,
offset: usize,
byte: u8,
timeout: &mut Timeout,
) -> Result<(), StorageError> {
issue_flash_command(CMD_WRITE);
FLASH_DATA.set(offset, byte);
self.wait_for_timeout(offset, byte, self.write_timeout, timeout)
}
#[allow(clippy::needless_range_loop)]
fn write_buffer(
&self,
offset: usize,
buf: &[u8],
timeout: &mut Timeout,
) -> Result<(), StorageError> {
self.set_bank(offset >> BANK_SHIFT)?;
for i in 0..buf.len() {
let byte_off = offset + i;
if (byte_off & BANK_MASK) == 0 {
self.set_bank(byte_off >> BANK_SHIFT)?;
}
self.write_byte(byte_off & BANK_MASK, buf[i], timeout)?;
}
Ok(())
}
#[allow(clippy::needless_range_loop)]
fn write_atmel_sector_raw(
&self,
offset: usize,
buf: &[u8],
timeout: &mut Timeout,
) -> Result<(), StorageError> {
critical_section::with(|_| {
issue_flash_command(CMD_WRITE);
for i in 0..128 {
FLASH_DATA.set(offset + i, buf[i]);
}
self.wait_for_timeout(offset + 127, buf[127], self.erase_sector_timeout, timeout)
})?;
Ok(())
}
#[inline(never)] fn write_atmel_sector_safe(
&self,
offset: usize,
buf: &[u8],
start: usize,
timeout: &mut Timeout,
) -> Result<(), StorageError> {
let mut sector = [0u8; 128];
self.read_buffer(offset, &mut sector[0..start])?;
sector[start..start + buf.len()].copy_from_slice(buf);
self.read_buffer(
offset + start + buf.len(),
&mut sector[start + buf.len()..128],
)?;
self.write_atmel_sector_raw(offset, §or, timeout)
}
fn write_atmel_sector(
&self,
offset: usize,
buf: &[u8],
start: usize,
timeout: &mut Timeout,
) -> Result<(), StorageError> {
if start == 0 && buf.len() == 128 {
self.write_atmel_sector_raw(offset, buf, timeout)
} else {
self.write_atmel_sector_safe(offset, buf, start, timeout)
}
}
}
pub struct FlashAccess;
impl RawSaveAccess for FlashAccess {
fn info(&self) -> Result<&'static MediaInfo, StorageError> {
Ok(cached_chip_info()?.info)
}
fn read(&self, offset: usize, buf: &mut [u8], _: &mut Timeout) -> Result<(), StorageError> {
let chip = cached_chip_info()?;
chip.check_len(offset, buf.len())?;
chip.read_buffer(offset, buf)
}
fn verify(&self, offset: usize, buf: &[u8], _: &mut Timeout) -> Result<bool, StorageError> {
let chip = cached_chip_info()?;
chip.check_len(offset, buf.len())?;
chip.verify_buffer(offset, buf)
}
fn prepare_write(
&self,
sector: usize,
count: usize,
timeout: &mut Timeout,
) -> Result<(), StorageError> {
let chip = cached_chip_info()?;
chip.check_sector_len(sector, count)?;
if chip.uses_atmel_api {
Ok(())
} else if count == chip.info.sector_count {
chip.erase_chip(timeout)
} else {
for i in sector..sector + count {
chip.erase_sector(i, timeout)?;
}
Ok(())
}
}
fn write(
&self,
mut offset: usize,
mut buf: &[u8],
timeout: &mut Timeout,
) -> Result<(), StorageError> {
let chip = cached_chip_info()?;
chip.check_len(offset, buf.len())?;
if chip.uses_atmel_api {
while !buf.is_empty() {
let start = offset & 127;
let end_len = cmp::min(128 - start, buf.len());
chip.write_atmel_sector(offset & !127, &buf[..end_len], start, timeout)?;
buf = &buf[end_len..];
offset += end_len;
}
Ok(())
} else {
chip.write_buffer(offset, buf, timeout)?;
Ok(())
}
}
}