use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::bus::i2c::wires::{SlaveWires, SlaveWiresState, pin as line};
use crate::bus::i2c::{Ack, Address, Direction, I2cBus, I2cSlave, buses};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::LazyHandle;
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicU64, LockRank, Mutex, Ordering};
use crate::core::wire::{Level, WireId, WireSink, WireSource};
use crate::machine::realize::Instance;
#[cfg(test)]
mod tests;
const CLASS_NAME: &str = "atmel.at24c";
const STATE_VERSION: u32 = 1;
pub const DEVICE_TYPE: u8 = 0b1010;
pub const BASE_ADDRESS: u8 = DEVICE_TYPE << 3;
pub const DEFAULT_SIZE: u64 = 256;
pub const DEFAULT_PAGE: u64 = 8;
pub const MAX_SIZE: u64 = 256;
pub const DEFAULT_WRITE_TICKS: u64 = 5_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum Phase {
#[default]
Idle,
WantWordAddress,
Writing,
Reading,
}
pub mod pin {
pub const WP: &str = "wp";
pub const WP_LINE: u32 = 2;
}
#[derive(Debug)]
pub struct At24c {
shared: Arc<Shared>,
wires: Arc<SlaveWires>,
bus: Option<Arc<I2cBus>>,
wp_pin: Mutex<Option<Arc<WriteProtectSink>>>,
}
struct Shared {
state: Mutex<State>,
size: u64,
page: u64,
address: u8,
write_ticks: u64,
ticks: AtomicU64,
next_event: AtomicU64,
lazy: Mutex<Option<LazyHandle>>,
}
const NO_EVENT: u64 = u64::MAX;
#[derive(Debug, Clone)]
struct State {
ticks: u64,
mem: Vec<u8>,
word: u64,
phase: Phase,
page_base: u64,
page_data: Vec<u8>,
page_touched: Vec<bool>,
busy_until: u64,
busy: bool,
wp: Level,
wp_wired: bool,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("At24cShared");
s.field("address", &alloc::format!("{:#04x}", self.address));
s.field("size", &self.size);
s.field("page", &self.page);
match self.state.try_lock() {
Some(state) => s
.field("phase", &state.phase)
.field("word", &state.word)
.field("busy", &state.busy),
None => s.field("state", &"<in use>"),
};
s.finish()
}
}
impl At24c {
pub fn new(props: &Props) -> Result<At24c> {
let mut r = props.reader();
let chip: u64 = r.or("chip", 0)?;
let size: u64 = r.or_size("size", DEFAULT_SIZE)?;
let page: u64 = r.or_size("page", DEFAULT_PAGE)?;
let write_ticks: u64 = r.or("write-ticks", DEFAULT_WRITE_TICKS)?;
let image = r
.optional_media("image")?
.map(crate::core::props::Media::to_bytes);
let bus_name = r.optional_str("bus")?.map(String::from);
r.finish()?;
let bad = |message: String| Error::Config {
at: String::from(CLASS_NAME),
message,
};
if chip > 7 {
return Err(bad(alloc::format!(
"`chip` is {chip}; it is the three hardware pins A2 A1 A0 (datasheet §4.1), so 0 \
to 7"
)));
}
if size == 0 || !size.is_power_of_two() || size > MAX_SIZE {
return Err(bad(alloc::format!(
"`size` is {size}; the word address is one byte (§4.1, Table 4-2), so this part \
holds a power of two up to {MAX_SIZE} — a 4K or larger 24Cxx moves the extra \
address bits into the device address and is a different part"
)));
}
if page == 0 || !page.is_power_of_two() || page > size {
return Err(bad(alloc::format!(
"`page` is {page}; it must be a power of two no larger than the {size}-byte array"
)));
}
let mut mem = alloc::vec![0xff_u8; size as usize];
if let Some(image) = image {
if image.len() as u64 > size {
return Err(bad(alloc::format!(
"`image` is {} bytes and the array is {size}",
image.len()
)));
}
mem[..image.len()].copy_from_slice(&image);
}
let shared = Arc::new(Shared {
state: Mutex::with_rank(
LockRank::DEVICE,
State {
ticks: 0,
mem,
word: 0,
phase: Phase::Idle,
page_base: 0,
page_data: alloc::vec![0; page as usize],
page_touched: alloc::vec![false; page as usize],
busy_until: 0,
busy: false,
wp: Level::Low,
wp_wired: false,
},
),
size,
page,
address: BASE_ADDRESS | (chip as u8),
write_ticks,
ticks: AtomicU64::new(0),
next_event: AtomicU64::new(NO_EVENT),
lazy: Mutex::with_rank(LockRank::WIRE, None),
});
let bus = bus_name
.as_deref()
.map(|name| buses::attach(props, name))
.transpose()?;
let wires = Arc::new(SlaveWires::new(Arc::clone(&shared) as Arc<dyn I2cSlave>));
Ok(At24c {
shared,
wires,
bus,
wp_pin: Mutex::with_rank(LockRank::WIRE, None),
})
}
#[must_use]
pub fn address(&self) -> Address {
Address::Seven(self.shared.address)
}
#[must_use]
pub fn size(&self) -> u64 {
self.shared.size
}
#[must_use]
pub fn page(&self) -> u64 {
self.shared.page
}
#[must_use]
pub fn slave(&self) -> Arc<dyn I2cSlave> {
Arc::clone(&self.shared) as Arc<dyn I2cSlave>
}
#[must_use]
pub fn wires(&self) -> &Arc<SlaveWires> {
&self.wires
}
#[must_use]
pub fn word_address(&self) -> u64 {
self.shared.state.lock().word
}
#[must_use]
pub fn busy(&self) -> bool {
let state = self.shared.state.lock();
self.shared.is_busy(&state)
}
#[must_use]
pub fn byte(&self, at: u64) -> Option<u8> {
let state = self.shared.state.lock();
state.mem.get(usize::try_from(at).ok()?).copied()
}
#[must_use]
pub fn contents(&self) -> Vec<u8> {
self.shared.state.lock().mem.clone()
}
#[must_use]
pub fn ticks(&self) -> u64 {
self.shared.ticks.load(Ordering::Relaxed)
}
pub fn advance_to(&self, target: u64) {
self.shared.advance_to(target);
}
}
impl Shared {
fn publish(&self, state: &State) {
self.ticks.store(state.ticks, Ordering::Relaxed);
self.next_event.store(
if state.busy {
state.busy_until.max(state.ticks.saturating_add(1))
} else {
NO_EVENT
},
Ordering::Relaxed,
);
}
fn advance_to(&self, target: u64) {
let mut state = self.state.lock();
if target <= state.ticks {
return;
}
state.ticks = target;
if state.busy && target >= state.busy_until {
state.busy = false;
}
self.publish(&state);
}
fn now(&self, state: &State) -> u64 {
let handle = self.lazy.lock().clone();
match handle {
Some(handle) => handle.present_tick().max(state.ticks),
None => state.ticks,
}
}
fn is_busy(&self, state: &State) -> bool {
state.busy && self.now(state) < state.busy_until
}
fn step_write(&self, state: &mut State) {
let low = (state.word + 1) & (self.page - 1);
state.word = (state.word & !(self.page - 1)) | low;
}
fn step_read(&self, state: &mut State) {
state.word = (state.word + 1) & (self.size - 1);
}
fn commit(&self, state: &mut State) {
let touched = state.page_touched.iter().any(|t| *t);
if !touched {
return;
}
if state.wp.is_low() {
for i in 0..state.page_data.len() {
if state.page_touched[i] {
let at = state.page_base + i as u64;
if let Some(slot) = state.mem.get_mut(at as usize) {
*slot = state.page_data[i];
}
}
}
state.busy = true;
state.busy_until = state.ticks.saturating_add(self.write_ticks);
}
state.page_touched.fill(false);
self.publish(state);
}
}
impl I2cSlave for Shared {
fn address(&self, address: Address, dir: Direction) -> Ack {
let mut state = self.state.lock();
let Address::Seven(a) = address else {
return Ack::Nack;
};
if a != self.address {
state.phase = Phase::Idle;
return Ack::Nack;
}
if self.is_busy(&state) {
state.phase = Phase::Idle;
return Ack::Nack;
}
state.phase = match dir {
Direction::Write => Phase::WantWordAddress,
Direction::Read => Phase::Reading,
};
Ack::Ack
}
fn write(&self, byte: u8) -> Ack {
let mut state = self.state.lock();
match state.phase {
Phase::WantWordAddress => {
state.word = u64::from(byte) & (self.size - 1);
state.page_base = state.word & !(self.page - 1);
state.page_touched.fill(false);
state.phase = Phase::Writing;
Ack::Ack
}
Phase::Writing => {
let slot = (state.word - state.page_base) as usize;
if let Some(cell) = state.page_data.get_mut(slot) {
*cell = byte;
state.page_touched[slot] = true;
}
self.step_write(&mut state);
Ack::Ack
}
Phase::Idle | Phase::Reading => Ack::Nack,
}
}
fn read(&self) -> u8 {
let state = self.state.lock();
if state.phase != Phase::Reading {
return 0xff;
}
state.mem.get(state.word as usize).copied().unwrap_or(0xff)
}
fn read_ack(&self, ack: Ack) {
let mut state = self.state.lock();
self.step_read(&mut state);
if !ack.is_ack() {
state.phase = Phase::Idle;
}
}
fn stop(&self) {
let mut state = self.state.lock();
if state.phase == Phase::Writing {
self.commit(&mut state);
}
state.phase = Phase::Idle;
}
fn peek(&self) -> u8 {
let state = self.state.lock();
state.mem.get(state.word as usize).copied().unwrap_or(0xff)
}
}
struct WriteProtectSink {
shared: Arc<Shared>,
}
impl fmt::Debug for WriteProtectSink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WriteProtectSink").finish_non_exhaustive()
}
}
impl WireSink for WriteProtectSink {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.shared.state.lock().wp = level;
}
}
impl Device for At24c {
fn class(&self) -> &'static DeviceClass {
&AT24C_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
if let Some(bus) = &self.bus {
bus.attach(self.slave())?;
}
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut state = self.shared.state.lock();
state.word = 0;
state.phase = Phase::Idle;
state.page_base = 0;
state.page_touched.fill(false);
state.busy = false;
state.busy_until = 0;
self.shared.publish(&state);
}
self.wires.reset();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.shared.state.lock();
w.write_u64(state.ticks)?;
w.write_bytes(&state.mem)?;
w.write_u64(state.word)?;
w.write_u8(phase_code(state.phase))?;
w.write_u64(state.page_base)?;
w.write_bytes(&state.page_data)?;
w.write_u64(state.page_touched.len() as u64)?;
for t in &state.page_touched {
w.write_bool(*t)?;
}
w.write_u64(state.busy_until)?;
w.write_bool(state.busy)?;
drop(state);
self.wires.snapshot().write(w)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let ticks = r.read_u64()?;
let mem = r.read_bytes()?.to_vec();
let word = r.read_u64()?;
let phase = phase_from_code(r.read_u8()?);
let page_base = r.read_u64()?;
let page_data = r.read_bytes()?.to_vec();
let touched_len = r.read_seq_len(1)?;
let mut page_touched = Vec::with_capacity(touched_len.min(MAX_SIZE) as usize);
for _ in 0..touched_len {
page_touched.push(r.read_bool()?);
}
let busy_until = r.read_u64()?;
let busy = r.read_bool()?;
let bits = SlaveWiresState::read(r)?;
{
let mut state = self.shared.state.lock();
if mem.len() == state.mem.len() {
state.mem = mem;
}
state.ticks = ticks;
state.word = word & (self.shared.size - 1);
state.phase = phase;
state.page_base = page_base & !(self.shared.page - 1);
if page_data.len() == state.page_data.len() {
state.page_data = page_data;
}
if page_touched.len() == state.page_touched.len() {
state.page_touched = page_touched;
}
state.busy_until = busy_until;
state.busy = busy;
self.shared.publish(&state);
}
self.wires.restore(bits);
Ok(())
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
match port {
line::SCL_NAME => Some(SinkPin {
sink: self.wires.sink(line::SCL, sources),
line: line::SCL,
}),
line::SDA_NAME => Some(SinkPin {
sink: self.wires.sink(line::SDA, sources),
line: line::SDA,
}),
pin::WP => {
self.shared.state.lock().wp_wired = true;
let sink = Arc::new(WriteProtectSink {
shared: Arc::clone(&self.shared),
});
*self.wp_pin.lock() = Some(Arc::clone(&sink));
Some(SinkPin {
sink: sink as Arc<dyn WireSink>,
line: pin::WP_LINE,
})
}
_ => None,
}
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
match port {
line::SCL_NAME => self.wires.connect(line::SCL, source),
line::SDA_NAME => self.wires.connect(line::SDA, source),
_ => {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!(
"an AT24C drives only `{}` and `{}`, and only ever low: both are \
open-drain (datasheet Table 1-1). `{}` is an input.",
line::SCL_NAME,
line::SDA_NAME,
pin::WP
),
});
}
}
Ok(())
}
fn announce(&self, _port: &str) {
self.wires.announce();
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.shared.ticks.load(Ordering::Relaxed)
}
fn advance_to(&self, tick: u64) {
At24c::advance_to(self, tick);
}
fn next_event_tick(&self) -> Option<u64> {
match self.shared.next_event.load(Ordering::Relaxed) {
NO_EVENT => None,
tick => Some(tick),
}
}
fn attach_lazy(&self, handle: LazyHandle) {
*self.shared.lazy.lock() = Some(handle);
}
}
impl Instance for At24c {}
const fn phase_code(phase: Phase) -> u8 {
match phase {
Phase::Idle => 0,
Phase::WantWordAddress => 1,
Phase::Writing => 2,
Phase::Reading => 3,
}
}
const fn phase_from_code(code: u8) -> Phase {
match code {
1 => Phase::WantWordAddress,
2 => Phase::Writing,
3 => Phase::Reading,
_ => Phase::Idle,
}
}
pub static AT24C_CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "Atmel AT24C01D/02D I2C serial EEPROM: page write, sequential read, \
acknowledge polling, write protect",
properties: &[
PropertySpec {
name: "chip",
kind: ValueKind::Uint,
required: false,
summary: "the hardware address pins A2 A1 A0 as one number, 0 to 7 (§4.1)",
},
PropertySpec {
name: "size",
kind: ValueKind::Uint,
required: false,
summary: "the array in bytes: 128 for an AT24C01D, 256 for an AT24C02D (default 256)",
},
PropertySpec {
name: "page",
kind: ValueKind::Uint,
required: false,
summary: "one page in bytes (default 8, which is what both parts have)",
},
PropertySpec {
name: "write-ticks",
kind: ValueKind::Uint,
required: false,
summary: "tWR in ticks of this device's clock domain (§5.4; default 5000)",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: false,
summary: "initial contents; absent means all 0xff, as delivered (§7)",
},
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the named I2C bus to hang off, for a transactional link",
},
],
construct: |props| Ok(Box::new(At24c::new(props)?)),
};
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&AT24C_CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(At24c::new(props)?)))
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("chip", ValueKind::Uint).range(0, 7))
.prop(PropSchema::new("size", ValueKind::Uint).range(1, MAX_SIZE))
.prop(PropSchema::new("page", ValueKind::Uint).range(1, MAX_SIZE))
.prop(PropSchema::new("write-ticks", ValueKind::Uint))
.prop(PropSchema::new("image", ValueKind::Media))
.prop(PropSchema::new("bus", ValueKind::Str))
.port(line::SCL_NAME, PortDir::InOut)
.port(line::SDA_NAME, PortDir::InOut)
.port(pin::WP, PortDir::In)
}