use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use core::fmt;
use crate::bus::spi::{
BitOrder, ChipSelect, Format, Mode, SlavePins, SpiSlave, buses, pin as spi_pin,
};
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;
const CLASS_NAME: &str = "sitronix.st7272a";
const STATE_VERSION: u32 = 1;
pub const REGISTER_COUNT: usize = 128;
pub const DEFAULT_WIDTH: u64 = 320;
pub const DEFAULT_HEIGHT: u64 = 240;
pub const DEFAULT_HTOTAL: u64 = 371;
pub const DEFAULT_VTOTAL: u64 = 260;
pub const RESET_SETTLE_NANOS: u64 = 100_000_000;
pub const FRAME: Format = Format {
mode: Mode::Mode0,
bits: 16,
order: BitOrder::MsbFirst,
};
const CMD_READ: u32 = 1 << 15;
const CMD_ADDR_SHIFT: u32 = 8;
const CMD_ADDR_MASK: u32 = 0x7f;
pub const REG_GRB_DISP: u8 = 0x10;
pub const REG_CONTRAST: u8 = 0x11;
pub const REG_SUB_CONTRAST_R: u8 = 0x12;
pub const REG_SUB_CONTRAST_B: u8 = 0x13;
pub const REG_BRIGHTNESS: u8 = 0x14;
pub const REG_SUB_BRIGHTNESS_R: u8 = 0x15;
pub const REG_SUB_BRIGHTNESS_B: u8 = 0x16;
pub const REG_H_BLANKING: u8 = 0x17;
pub const REG_V_BLANKING: u8 = 0x18;
pub const REG_DISPLAY_MODE: u8 = 0x19;
pub const REG_AUTO_REFRESH: u8 = 0x1b;
pub const REG_BIST: u8 = 0x1c;
const GRB_DISP_GRB: u8 = 1 << 3;
const GRB_DISP_DISP: u8 = 1 << 0;
const MODE_VDIR: u8 = 1 << 6;
const MODE_HDIR: u8 = 1 << 5;
const MODE_SBGR: u8 = 1 << 4;
const BIST_PICSEL: u8 = 0x07;
#[derive(Debug, Clone, Copy)]
struct RegDef {
addr: u8,
default: u8,
writable: u8,
}
const REGISTERS: &[RegDef] = &[
RegDef {
addr: 0x01,
default: 0x7f,
writable: 0x7f,
}, RegDef {
addr: 0x02,
default: 0x7f,
writable: 0x7f,
}, RegDef {
addr: 0x03,
default: 0x7f,
writable: 0x7f,
}, RegDef {
addr: 0x04,
default: 0x78,
writable: 0x7f,
}, RegDef {
addr: 0x05,
default: 0x40,
writable: 0x7f,
}, RegDef {
addr: 0x10,
default: 0x08,
writable: 0x09,
},
RegDef {
addr: 0x11,
default: 0x40,
writable: 0xff,
},
RegDef {
addr: 0x12,
default: 0x40,
writable: 0x7f,
},
RegDef {
addr: 0x13,
default: 0x40,
writable: 0x7f,
},
RegDef {
addr: 0x14,
default: 0x40,
writable: 0xff,
},
RegDef {
addr: 0x15,
default: 0x40,
writable: 0x7f,
},
RegDef {
addr: 0x16,
default: 0x40,
writable: 0x7f,
},
RegDef {
addr: 0x17,
default: 0x2b,
writable: 0xff,
},
RegDef {
addr: 0x18,
default: 0x0c,
writable: 0xff,
},
RegDef {
addr: 0x19,
default: 0x6d,
writable: 0xff,
},
RegDef {
addr: 0x1b,
default: 0x0c,
writable: 0x04,
},
RegDef {
addr: 0x1c,
default: 0x38,
writable: 0x3f,
},
RegDef {
addr: 0x20,
default: 0x00,
writable: 0x1f,
}, RegDef {
addr: 0x21,
default: 0x00,
writable: 0x1f,
}, RegDef {
addr: 0x22,
default: 0x00,
writable: 0xff,
}, RegDef {
addr: 0x23,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x24,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x25,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x26,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x27,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x28,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x29,
default: 0x00,
writable: 0x1f,
}, RegDef {
addr: 0x30,
default: 0x00,
writable: 0x1f,
}, RegDef {
addr: 0x31,
default: 0x00,
writable: 0x1f,
}, RegDef {
addr: 0x32,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x33,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x34,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x35,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x36,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x37,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x38,
default: 0x00,
writable: 0xff,
},
RegDef {
addr: 0x39,
default: 0x00,
writable: 0x1f,
}, RegDef {
addr: 0x40,
default: 0x40,
writable: 0x3f,
}, RegDef {
addr: 0x41,
default: 0x00,
writable: 0x7f,
}, RegDef {
addr: 0x44,
default: 0x40,
writable: 0x3f,
}, RegDef {
addr: 0x45,
default: 0x00,
writable: 0x0f,
}, RegDef {
addr: 0x46,
default: 0x00,
writable: 0xff,
}, RegDef {
addr: 0x47,
default: 0x00,
writable: 0x07,
}, RegDef {
addr: 0x49,
default: 0x04,
writable: 0x70,
}, RegDef {
addr: 0x4a,
default: 0x00,
writable: 0x77,
}, RegDef {
addr: 0x60,
default: 0x00,
writable: 0x06,
}, RegDef {
addr: 0x65,
default: 0x00,
writable: 0xff,
}, RegDef {
addr: 0x66,
default: 0x03,
writable: 0x07,
}, RegDef {
addr: 0x67,
default: 0x03,
writable: 0x07,
}, RegDef {
addr: 0x68,
default: 0x03,
writable: 0x07,
}, RegDef {
addr: 0x69,
default: 0x03,
writable: 0x07,
}, RegDef {
addr: 0x6a,
default: 0x03,
writable: 0x07,
}, RegDef {
addr: 0x6b,
default: 0x03,
writable: 0x07,
}, RegDef {
addr: 0x6c,
default: 0x03,
writable: 0x07,
}, ];
const DEFAULTS: [u8; REGISTER_COUNT] = build_defaults();
const WRITABLE: [u8; REGISTER_COUNT] = build_writable();
const KNOWN: [bool; REGISTER_COUNT] = build_known();
const fn build_defaults() -> [u8; REGISTER_COUNT] {
let mut out = [0u8; REGISTER_COUNT];
let mut i = 0;
while i < REGISTERS.len() {
out[REGISTERS[i].addr as usize] = REGISTERS[i].default;
i += 1;
}
out
}
const fn build_writable() -> [u8; REGISTER_COUNT] {
let mut out = [0u8; REGISTER_COUNT];
let mut i = 0;
while i < REGISTERS.len() {
out[REGISTERS[i].addr as usize] = REGISTERS[i].writable;
i += 1;
}
out
}
const fn build_known() -> [bool; REGISTER_COUNT] {
let mut out = [false; REGISTER_COUNT];
let mut i = 0;
while i < REGISTERS.len() {
out[REGISTERS[i].addr as usize] = true;
i += 1;
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Registers {
values: [u8; REGISTER_COUNT],
}
impl Default for Registers {
fn default() -> Registers {
Registers { values: DEFAULTS }
}
}
impl Registers {
#[must_use]
pub fn new() -> Registers {
Registers::default()
}
#[must_use]
pub fn get(&self, addr: u8) -> u8 {
self.values[usize::from(addr & 0x7f)]
}
pub fn set(&mut self, addr: u8, value: u8) -> bool {
let index = usize::from(addr & 0x7f);
if !KNOWN[index] {
return false;
}
let mask = WRITABLE[index];
self.values[index] = (DEFAULTS[index] & !mask) | (value & mask);
true
}
#[must_use]
pub fn is_known(addr: u8) -> bool {
KNOWN[usize::from(addr & 0x7f)]
}
#[must_use]
pub fn display_on(&self) -> bool {
self.get(REG_GRB_DISP) & GRB_DISP_DISP != 0
}
#[must_use]
pub fn grb(&self) -> bool {
self.get(REG_GRB_DISP) & GRB_DISP_GRB != 0
}
#[must_use]
pub fn swap_rb(&self) -> bool {
self.get(REG_DISPLAY_MODE) & MODE_SBGR != 0
}
#[must_use]
pub fn scan_left_to_right(&self) -> bool {
self.get(REG_DISPLAY_MODE) & MODE_HDIR != 0
}
#[must_use]
pub fn scan_top_to_bottom(&self) -> bool {
self.get(REG_DISPLAY_MODE) & MODE_VDIR != 0
}
#[must_use]
pub fn bist_pattern(&self) -> [u8; 3] {
match self.get(REG_BIST) & BIST_PICSEL {
0b001 => [0xff, 0xff, 0xff], 0b010 => [0xff, 0x00, 0x00], 0b011 => [0x00, 0xff, 0x00], 0b100 => [0x00, 0x00, 0xff], _ => [0x00, 0x00, 0x00],
}
}
#[must_use]
pub fn contrast_q6(&self) -> u32 {
u32::from(self.get(REG_CONTRAST))
}
#[must_use]
pub fn sub_contrast_r_q10(&self) -> u32 {
sub_contrast_q10(self.get(REG_SUB_CONTRAST_R))
}
#[must_use]
pub fn sub_contrast_b_q10(&self) -> u32 {
sub_contrast_q10(self.get(REG_SUB_CONTRAST_B))
}
#[must_use]
pub fn brightness(&self) -> i32 {
i32::from(self.get(REG_BRIGHTNESS)) - 64
}
#[must_use]
pub fn sub_brightness_r(&self) -> i32 {
i32::from(self.get(REG_SUB_BRIGHTNESS_R) & 0x7f) - 64
}
#[must_use]
pub fn sub_brightness_b(&self) -> i32 {
i32::from(self.get(REG_SUB_BRIGHTNESS_B) & 0x7f) - 64
}
}
fn sub_contrast_q10(value: u8) -> u32 {
768 + u32::from(value & 0x7f) * 508 / 127
}
#[derive(Debug)]
pub struct St7272a {
shared: Arc<Shared>,
pins: Arc<SlavePins>,
}
struct Shared {
state: Mutex<State>,
width: u32,
height: u32,
frame_ticks: u64,
ticks: AtomicU64,
next_event: AtomicU64,
lazy: Mutex<Option<LazyHandle>>,
control: Mutex<alloc::vec::Vec<Arc<ControlSink>>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct State {
ticks: u64,
shadow: Registers,
active: Registers,
pending: Option<u32>,
grb: Level,
disp: Level,
bist_en: Level,
wired: u8,
unlisted: u32,
frames: u64,
}
const WIRED_GRB: u8 = 1 << 0;
const WIRED_DISP: u8 = 1 << 1;
const WIRED_BIST: u8 = 1 << 2;
impl Default for State {
fn default() -> State {
State {
ticks: 0,
shadow: Registers::new(),
active: Registers::new(),
pending: None,
grb: Level::High,
disp: Level::High,
bist_en: Level::Low,
wired: 0,
unlisted: 0,
frames: 0,
}
}
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Shared");
s.field("width", &self.width).field("height", &self.height);
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
pub mod pin {
pub const GRB: &str = "grb";
pub const DISP: &str = "disp";
pub const BIST_EN: &str = "bist_en";
pub const GRB_LINE: u32 = 16;
pub const DISP_LINE: u32 = 17;
pub const BIST_EN_LINE: u32 = 18;
}
impl St7272a {
pub fn new(props: &Props) -> Result<St7272a> {
let mut r = props.reader();
let width: u64 = r.or("width", DEFAULT_WIDTH)?;
let height: u64 = r.or("height", DEFAULT_HEIGHT)?;
let htotal: u64 = r.or("htotal", DEFAULT_HTOTAL)?;
let vtotal: u64 = r.or("vtotal", DEFAULT_VTOTAL)?;
let bus_name = r.optional_str("bus")?.map(String::from);
let cs: u64 = r.or("cs", 0)?;
r.finish()?;
let bad = |message: String| Error::Config {
at: String::from(CLASS_NAME),
message,
};
if width == 0 || height == 0 {
return Err(bad(alloc::format!(
"a panel is {width}x{height}; both dimensions must be at least 1"
)));
}
if width > u64::from(u32::MAX) || height > u64::from(u32::MAX) {
return Err(bad(String::from(
"a panel dimension is a pixel count, not an address",
)));
}
if htotal < width || vtotal < height {
return Err(bad(alloc::format!(
"the total period {htotal}x{vtotal} is smaller than the visible {width}x{height}; \
§7.3.4's Th and Tv include the blanking, so they are never the smaller pair"
)));
}
let shared = Arc::new(Shared {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
width: width as u32,
height: height as u32,
frame_ticks: htotal.saturating_mul(vtotal),
ticks: AtomicU64::new(0),
next_event: AtomicU64::new(htotal.saturating_mul(vtotal)),
lazy: Mutex::with_rank(LockRank::WIRE, None),
control: Mutex::with_rank(LockRank::WIRE, alloc::vec::Vec::new()),
});
let pins = Arc::new(SlavePins::new(Arc::clone(&shared) as Arc<dyn SpiSlave>));
let panel = St7272a { shared, pins };
if let Some(name) = bus_name {
let bus = buses::open(&name);
if cs >= u64::from(crate::bus::spi::MAX_CHIP_SELECTS as u8) {
return Err(bad(alloc::format!(
"`cs` is {cs}; an SPI bus routes {} chip selects",
crate::bus::spi::MAX_CHIP_SELECTS
)));
}
bus.attach(
ChipSelect(cs as u8),
Arc::clone(&panel.shared) as Arc<dyn SpiSlave>,
)?;
}
Ok(panel)
}
#[must_use]
pub fn size(&self) -> (u32, u32) {
(self.shared.width, self.shared.height)
}
#[must_use]
pub fn frame_ticks(&self) -> u64 {
self.shared.frame_ticks
}
#[must_use]
pub fn frames(&self) -> u64 {
self.shared.state.lock().frames
}
#[must_use]
pub fn shadow(&self) -> Registers {
self.shared.state.lock().shadow
}
#[must_use]
pub fn active(&self) -> Registers {
self.shared.state.lock().active
}
#[must_use]
pub fn pins(&self) -> &Arc<SlavePins> {
&self.pins
}
#[must_use]
pub fn unlisted_commands(&self) -> u32 {
self.shared.state.lock().unlisted
}
#[must_use]
pub fn is_displaying(&self) -> bool {
let state = self.shared.state.lock();
State::displaying(&state)
}
#[must_use]
pub fn bist_colour(&self) -> Option<[u8; 3]> {
let state = self.shared.state.lock();
(state.wired & WIRED_BIST != 0 && state.bist_en.is_high())
.then(|| state.active.bist_pattern())
}
#[must_use]
pub fn apply(&self, rgb: [u8; 3]) -> [u8; 3] {
let state = self.shared.state.lock();
if !State::displaying(&state) {
return [0, 0, 0];
}
if state.wired & WIRED_BIST != 0 && state.bist_en.is_high() {
return state.active.bist_pattern();
}
State::process(&state.active, rgb)
}
#[must_use]
pub fn map_pixel(&self, x: u32, y: u32) -> (u32, u32) {
let state = self.shared.state.lock();
let x = if state.active.scan_left_to_right() {
x
} else {
self.shared.width.saturating_sub(1).saturating_sub(x)
};
let y = if state.active.scan_top_to_bottom() {
y
} else {
self.shared.height.saturating_sub(1).saturating_sub(y)
};
(x, y)
}
pub fn latch(&self) {
let mut state = self.shared.state.lock();
state.active = state.shadow;
state.frames += 1;
self.shared.publish(&state);
}
pub fn advance_to(&self, target: u64) {
self.shared.advance_to(target);
}
}
impl State {
fn displaying(state: &State) -> bool {
if state.wired & WIRED_GRB != 0 && state.grb.is_low() {
return false;
}
if state.wired & WIRED_DISP != 0 && state.disp.is_low() {
return false;
}
state.active.display_on()
}
fn process(regs: &Registers, rgb: [u8; 3]) -> [u8; 3] {
let [mut r, g, mut b] = rgb;
if regs.swap_rb() {
core::mem::swap(&mut r, &mut b);
}
let contrast = regs.contrast_q6();
let gain = |v: u8, sub_q10: u32| -> i32 {
let scaled = u32::from(v) * contrast * sub_q10;
(scaled / (64 * 1024)) as i32
};
let plain = |v: u8| -> i32 { ((u32::from(v) * contrast) / 64) as i32 };
let out = [
gain(r, regs.sub_contrast_r_q10()) + regs.brightness() + regs.sub_brightness_r(),
plain(g) + regs.brightness(),
gain(b, regs.sub_contrast_b_q10()) + regs.brightness() + regs.sub_brightness_b(),
];
[
out[0].clamp(0, 255) as u8,
out[1].clamp(0, 255) as u8,
out[2].clamp(0, 255) as u8,
]
}
}
impl Shared {
fn publish(&self, state: &State) {
self.ticks.store(state.ticks, Ordering::Relaxed);
let next = state
.ticks
.saturating_sub(state.ticks % self.frame_ticks)
.saturating_add(self.frame_ticks);
self.next_event
.store(next.max(state.ticks.saturating_add(1)), Ordering::Relaxed);
}
fn advance_to(&self, target: u64) {
let mut state = self.state.lock();
if target <= state.ticks {
return;
}
let before = state.ticks / self.frame_ticks;
let after = target / self.frame_ticks;
state.ticks = target;
if after > before {
state.active = state.shadow;
state.frames += after - before;
}
self.publish(&state);
}
fn command(&self, word: u32) {
let addr = ((word >> CMD_ADDR_SHIFT) & CMD_ADDR_MASK) as u8;
if word & CMD_READ != 0 {
return;
}
let mut state = self.state.lock();
let data = (word & 0xff) as u8;
if !state.shadow.set(addr, data) {
state.unlisted = state.unlisted.saturating_add(1);
return;
}
if addr == REG_GRB_DISP && data & GRB_DISP_GRB == 0 {
state.shadow = Registers::new();
state.active = Registers::new();
}
}
}
impl SpiSlave for Shared {
fn format(&self) -> Format {
FRAME
}
fn turnaround(&self) -> Option<u8> {
Some(8)
}
fn partial(&self, bits: u8, received: u32) -> Option<u32> {
if bits != 8 {
return None;
}
if received & 0x80 == 0 {
return None;
}
let addr = (received & CMD_ADDR_MASK) as u8;
Some(u32::from(self.state.lock().shadow.get(addr)))
}
fn select(&self, selected: bool) {
let word = {
let mut state = self.state.lock();
if selected {
state.pending = None;
None
} else {
state.pending.take()
}
};
if let Some(word) = word {
self.command(word);
}
}
fn transfer(&self, mosi: u32) -> u32 {
self.state.lock().pending = Some(mosi);
u32::MAX
}
fn peek(&self) -> u32 {
u32::MAX
}
}
struct ControlSink {
shared: Arc<Shared>,
line: u32,
}
impl fmt::Debug for ControlSink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ControlSink")
.field("line", &self.line)
.finish()
}
}
impl WireSink for ControlSink {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
let mut state = self.shared.state.lock();
match self.line {
pin::GRB_LINE => {
let was = state.grb;
state.grb = level;
if was.is_high() && level.is_low() {
state.shadow = Registers::new();
state.active = Registers::new();
}
}
pin::DISP_LINE => state.disp = level,
pin::BIST_EN_LINE => state.bist_en = level,
_ => {}
}
}
}
impl Device for St7272a {
fn class(&self) -> &'static DeviceClass {
&ST7272A_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut state = self.shared.state.lock();
let (ticks, wired, grb, disp, bist_en) = (
state.ticks,
state.wired,
state.grb,
state.disp,
state.bist_en,
);
*state = State {
ticks,
wired,
grb,
disp,
bist_en,
..State::default()
};
self.shared.publish(&state);
}
self.pins.reset();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = *self.shared.state.lock();
w.write_u64(state.ticks)?;
w.write_u64(state.frames)?;
for addr in 0..REGISTER_COUNT {
w.write_u8(state.shadow.values[addr])?;
}
for addr in 0..REGISTER_COUNT {
w.write_u8(state.active.values[addr])?;
}
w.write_u32(state.unlisted)?;
w.write_bool(state.pending.is_some())?;
w.write_u32(state.pending.unwrap_or(0))?;
let (rx, tx, count, selected, sck, mosi, loaded) = self.pins.snapshot();
w.write_u32(rx)?;
w.write_u32(tx)?;
w.write_u8(count)?;
w.write_bool(selected)?;
w.write_bool(sck)?;
w.write_bool(mosi)?;
w.write_bool(loaded)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let ticks = r.read_u64()?;
let frames = r.read_u64()?;
let mut shadow = Registers::new();
for addr in 0..REGISTER_COUNT {
shadow.values[addr] = r.read_u8()?;
}
let mut active = Registers::new();
for addr in 0..REGISTER_COUNT {
active.values[addr] = r.read_u8()?;
}
let unlisted = r.read_u32()?;
let has_pending = r.read_bool()?;
let pending_word = r.read_u32()?;
let pending = has_pending.then_some(pending_word);
let pins = (
r.read_u32()?,
r.read_u32()?,
r.read_u8()?,
r.read_bool()?,
r.read_bool()?,
r.read_bool()?,
r.read_bool()?,
);
{
let mut state = self.shared.state.lock();
state.ticks = ticks;
state.frames = frames;
state.shadow = shadow;
state.active = active;
state.unlisted = unlisted;
state.pending = pending;
self.shared.publish(&state);
}
self.pins.restore(pins);
Ok(())
}
fn sink(&self, port: &str, _sources: &[WireId]) -> Option<SinkPin> {
let control = |line: u32, bit: u8| -> SinkPin {
self.shared.state.lock().wired |= bit;
let pin = Arc::new(ControlSink {
shared: Arc::clone(&self.shared),
line,
});
self.shared.control.lock().push(Arc::clone(&pin));
SinkPin {
sink: pin as Arc<dyn WireSink>,
line,
}
};
match port {
spi_pin::SCK_NAME => Some(SinkPin {
sink: self.pins.sink(spi_pin::SCK),
line: spi_pin::SCK,
}),
spi_pin::MOSI_NAME => Some(SinkPin {
sink: self.pins.sink(spi_pin::MOSI),
line: spi_pin::MOSI,
}),
spi_pin::CS_NAME => Some(SinkPin {
sink: self.pins.sink(spi_pin::CS),
line: spi_pin::CS,
}),
pin::GRB => Some(control(pin::GRB_LINE, WIRED_GRB)),
pin::DISP => Some(control(pin::DISP_LINE, WIRED_DISP)),
pin::BIST_EN => Some(control(pin::BIST_EN_LINE, WIRED_BIST)),
_ => None,
}
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != spi_pin::MISO_NAME {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!(
"the ST7272A drives only `{}` — on the real part it is the same `SDA` pin as \
`{}`, split here because a wire has fixed drivers",
spi_pin::MISO_NAME,
spi_pin::MOSI_NAME
),
});
}
self.pins.connect_miso(source);
Ok(())
}
fn announce(&self, port: &str) {
if port == spi_pin::MISO_NAME {
self.pins.publish_miso();
}
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.shared.ticks.load(Ordering::Relaxed)
}
fn advance_to(&self, tick: u64) {
St7272a::advance_to(self, tick);
}
fn next_event_tick(&self) -> Option<u64> {
Some(self.shared.next_event.load(Ordering::Relaxed))
}
fn attach_lazy(&self, handle: LazyHandle) {
*self.shared.lazy.lock() = Some(handle);
}
}
impl Instance for St7272a {}
pub static ST7272A_CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "Sitronix ST7272A TFT panel driver: SPI register configuration, RGB pixel input",
properties: &[
PropertySpec {
name: "width",
kind: ValueKind::Uint,
required: false,
summary: "visible pixels across (default 320, the part of §2)",
},
PropertySpec {
name: "height",
kind: ValueKind::Uint,
required: false,
summary: "visible pixels down (default 240)",
},
PropertySpec {
name: "htotal",
kind: ValueKind::Uint,
required: false,
summary: "one horizontal period in DCLK, blanking included (§7.3.4's Th)",
},
PropertySpec {
name: "vtotal",
kind: ValueKind::Uint,
required: false,
summary: "one vertical period in HSYNC (§7.3.4's Tv)",
},
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the named SPI bus to attach to, for a transactional link",
},
PropertySpec {
name: "cs",
kind: ValueKind::Uint,
required: false,
summary: "which chip select on that bus (default 0)",
},
],
construct: |props| Ok(Box::new(St7272a::new(props)?)),
};
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&ST7272A_CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(St7272a::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("width", ValueKind::Uint).range(1, u64::from(u32::MAX)))
.prop(PropSchema::new("height", ValueKind::Uint).range(1, u64::from(u32::MAX)))
.prop(PropSchema::new("htotal", ValueKind::Uint).range(1, u64::from(u32::MAX)))
.prop(PropSchema::new("vtotal", ValueKind::Uint).range(1, u64::from(u32::MAX)))
.prop(PropSchema::new("bus", ValueKind::Str))
.prop(
PropSchema::new("cs", ValueKind::Uint)
.range(0, crate::bus::spi::MAX_CHIP_SELECTS as u64 - 1),
)
.port(spi_pin::SCK_NAME, PortDir::In)
.port(spi_pin::MOSI_NAME, PortDir::In)
.port(spi_pin::CS_NAME, PortDir::In)
.port(spi_pin::MISO_NAME, PortDir::Out)
.port(pin::GRB, PortDir::In)
.port(pin::DISP, PortDir::In)
.port(pin::BIST_EN, PortDir::In)
}
#[cfg(test)]
mod tests;