use crate::device::{Device, Result};
use core::time::Duration;
use low_level::{
CMD_CHANGE_PROGRAMMING_MODE, CMD_ERASE_PAGES, CMD_PROGRAMMING_STATUS, CMD_PROGRAM_DATA,
CMD_READ_MEMORY, CMD_SET_CHECKSUM, CMD_SET_PROGRAMMING_ADDRESS,
};
pub trait Bootloader: Device {
fn write_xored(&mut self, command: u8, data: impl AsRef<[u8]>) -> Result<u8, Self::I2cError> {
let data = data.as_ref();
let data_len = data.len();
let mut buf = [0; 10];
buf[0] = command;
buf[1..=data_len].copy_from_slice(data);
let xor = data.iter().fold(0, |a, &b| a ^ b);
buf[data_len + 1] = xor;
self.write(&buf[..data_len + 2])?;
Ok(xor)
}
fn get_programming_status(&mut self) -> Result<ProgrammingStatus, Self::I2cError> {
let mut buf = [0; 2];
self.write_read([CMD_PROGRAMMING_STATUS], &mut buf)?;
Ok(ProgrammingStatus {
status: buf[0],
checksum: buf[1],
})
}
fn enter_programming_mode(&mut self) -> Result<(), Self::I2cError> {
self.write([CMD_CHANGE_PROGRAMMING_MODE, 0x17, 0x27, 0x65, 0x40])
}
fn leave_programming_mode(&mut self, commit: bool) -> Result<(), Self::I2cError> {
self.write([CMD_CHANGE_PROGRAMMING_MODE, u8::from(commit)])
}
fn erase_pages(&mut self, index: u8, count: u8) -> Result<Duration, Self::I2cError> {
self.write_xored(CMD_ERASE_PAGES, [index, count])?;
Ok(Duration::from_millis(u64::from(count + 1) * 30))
}
fn set_programming_address(&mut self, address: u32) -> Result<(), Self::I2cError> {
self.write_xored(CMD_SET_PROGRAMMING_ADDRESS, address.to_le_bytes())?;
Ok(())
}
fn program_data(&mut self, data: &[u8], checksum: &mut u8) -> Result<(), Self::I2cError> {
let xor = self.write_xored(CMD_PROGRAM_DATA, data)?;
*checksum ^= xor;
Ok(())
}
fn read_memory(&mut self) -> Result<[u8; 8], Self::I2cError> {
let mut result = [0; 8];
self.write_read([CMD_READ_MEMORY], &mut result)?;
Ok(result)
}
fn set_checksum(&mut self, checksum: u8) -> Result<(), Self::I2cError> {
self.write([CMD_SET_CHECKSUM, checksum])
}
}
impl<D: Device> Bootloader for D {}
#[derive(Clone, Copy, Debug)]
pub struct ProgrammingStatus {
status: u8,
checksum: u8,
}
impl ProgrammingStatus {
#[must_use]
pub fn programming_mode(self) -> bool {
self.status & 1 != 0
}
#[must_use]
pub fn error_detected(self) -> bool {
self.status & 2 != 0
}
#[must_use]
pub fn application_present(self) -> bool {
self.status & 4 != 0
}
#[must_use]
pub fn checksum(self) -> u8 {
self.checksum
}
}
pub(crate) mod low_level {
pub const CMD_PROGRAMMING_STATUS: u8 = 0x10;
pub const CMD_CHANGE_PROGRAMMING_MODE: u8 = 0x20;
pub const CMD_ERASE_PAGES: u8 = 0x24;
pub const CMD_SET_PROGRAMMING_ADDRESS: u8 = 0x28;
pub const CMD_PROGRAM_DATA: u8 = 0x2c;
pub const CMD_READ_MEMORY: u8 = 0x30;
pub const CMD_SET_CHECKSUM: u8 = 0x34;
#[cfg(feature = "low-level")]
pub const CMD_REBOOT_TO_APPLICATION: u8 = 0xe2;
}