diff --git a/clicky-core/src/devices/display/hd66753.rs b/clicky-core/src/devices/display/hd66753.rs index 6ec5cb1..50a1fab 100644 --- a/clicky-core/src/devices/display/hd66753.rs +++ b/clicky-core/src/devices/display/hd66753.rs @@ -3,6 +3,7 @@ use crate::devices::prelude::*; use std::sync::{Arc, RwLock}; use relativity::Instant; +use crate::devices::display::LcdPanel; use crate::gui::RenderCallback; use either::Either; @@ -91,10 +92,6 @@ struct InternalRegs { /// Hitachi HD66753 168x132 monochrome LCD Controller. pub struct Hd66753 { - // FIXME: not sure if there are separate latches for the command and data registers... - write_byte_latch: Option, - read_byte_latch: Option, - /// Index Register ir: u16, /// Address counter @@ -108,8 +105,6 @@ pub struct Hd66753 { impl std::fmt::Debug for Hd66753 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Hd66753") - .field("write_byte_latch", &self.write_byte_latch) - .field("read_byte_latch", &self.read_byte_latch) .field("ir", &self.ir) .field("ac", &self.ac) .field("cgram", &"[...]") @@ -130,8 +125,6 @@ impl Hd66753 { ir: 0, ac: 0, cgram, - write_byte_latch: None, - read_byte_latch: None, ireg, } } @@ -149,7 +142,7 @@ impl Hd66753 { /// /// The callback accepts a minifb framebuffer, and returns the rendered /// dimensions. - pub fn render_callback(&self) -> RenderCallback { + fn make_render_callback(&self) -> RenderCallback { let cgram = Arc::clone(&self.cgram); let ireg = Arc::clone(&self.ireg); let start = Instant::now(); @@ -429,77 +422,35 @@ impl Hd66753 { } } -impl Device for Hd66753 { - fn kind(&self) -> &'static str { - "HD 66753" - } - - fn probe(&self, offset: u32) -> Probe { - let reg = match offset { - 0x0 => "LCD Control", - 0x8 => "LCD Command", - 0x10 => "LCD Data", - _ => return Probe::Unmapped, - }; - - Probe::Register(reg) - } -} - -impl Memory for Hd66753 { - fn r32(&mut self, offset: u32) -> MemResult { - if offset == 0x0 { - // bypass the latch - return Ok(0); // HACK: Emulated LCD is never busy - } +impl LcdPanel for Hd66753 { + fn write_command(&mut self, val: u16) -> MemResult<()> { + self.ir = val; - if let Some(val) = self.read_byte_latch.take() { - return Ok(val as u32); + if self.ir > 0x12 { + return Err(ContractViolation { + msg: format!("set invalid LCD Command: {:#04x?}", val), + severity: Error, + stub_val: None, + }); } - let val: u16 = match offset { - // XXX: not currently tracking driving raster-row position - 0x8 => self.ireg.read().unwrap().ct as u16, - 0x10 => self.handle_data_read()?, - _ => return Err(Unexpected), - }; - - self.read_byte_latch = Some(val as u8); // latch lower 8 bits - Ok((val >> 8) as u32) // returning the higher 8 bits first + Ok(()) } - fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { - if offset == 0x0 { - // bypass the latch - return Err(StubWrite(Error, ())); - } - - // the iPod uses the controller via an 8-bit interface - let val = val as u8; // FIXME: this should use trunc_to_u8, but it crashes... - let val = match self.write_byte_latch.take() { - None => { - self.write_byte_latch = Some(val); - return Ok(()); - } - Some(hi) => (hi as u16) << 8 | (val as u16), - }; + fn read_command(&mut self) -> MemResult { + // XXX: not currently tracking driving raster-row position + Ok(self.ireg.read().unwrap().ct as u16) + } - match offset { - 0x8 => { - self.ir = val; + fn write_data(&mut self, val: u16) -> MemResult<()> { + self.handle_data_write(val) + } - if self.ir > 0x12 { - return Err(ContractViolation { - msg: format!("set invalid LCD Command: {:#04x?}", val), - severity: Error, - stub_val: None, - }); - } + fn read_data(&mut self) -> MemResult { + self.handle_data_read() + } - Ok(()) - } - 0x10 => Ok(self.handle_data_write(val)?), - _ => Err(Unexpected), - } + fn render_callback(&self) -> RenderCallback { + self.make_render_callback() } } diff --git a/clicky-core/src/devices/display/mod.rs b/clicky-core/src/devices/display/mod.rs index 4b242e8..3fa2a4e 100644 --- a/clicky-core/src/devices/display/mod.rs +++ b/clicky-core/src/devices/display/mod.rs @@ -1,3 +1,27 @@ //! Display-related devices. +use crate::devices::prelude::*; + +use crate::gui::RenderCallback; + pub mod hd66753; + +/// LCD Controller IC trait (eg. HD66753) +pub trait LcdPanel: std::fmt::Debug + Send + Sync { + /// Select a register / issue a command (i.e: write the Index Register). + fn write_command(&mut self, val: u16) -> MemResult<()>; + + /// Read back the command register. + fn read_command(&mut self) -> MemResult; + + /// Write to the currently selected register. + fn write_data(&mut self, val: u16) -> MemResult<()>; + + /// Read from the currently selected register. + fn read_data(&mut self) -> MemResult; + + /// Returns a callback which renders the panel's framebuffer. + /// + /// The callback accepts a framebuffer, and returns the rendered dimensions. + fn render_callback(&self) -> RenderCallback; +} diff --git a/clicky-core/src/devices/platform/pp.rs b/clicky-core/src/devices/platform/pp.rs index a0a2da9..a163fdc 100644 --- a/clicky-core/src/devices/platform/pp.rs +++ b/clicky-core/src/devices/platform/pp.rs @@ -16,6 +16,7 @@ mod i2s; mod intcon; mod mailbox; mod memcon; +mod mlcd; mod opto; mod ppcon; mod rtc; @@ -40,6 +41,7 @@ pub use i2s::*; pub use intcon::*; pub use mailbox::*; pub use memcon::*; +pub use mlcd::*; pub use opto::*; pub use ppcon::*; pub use rtc::*; diff --git a/clicky-core/src/devices/platform/pp/mlcd.rs b/clicky-core/src/devices/platform/pp/mlcd.rs new file mode 100644 index 0000000..6242259 --- /dev/null +++ b/clicky-core/src/devices/platform/pp/mlcd.rs @@ -0,0 +1,100 @@ +use crate::devices::prelude::*; + +use crate::devices::display::LcdPanel; +use crate::gui::RenderCallback; + +/// PP5020 monochrome LCD controller. +/// +/// The panel is driven over an 8-bit interface, so each 16-bit transfer takes +/// two accesses. Writes latch the high byte and commit on the second write; +/// reads return the high byte first and latch the low byte for the next read. +#[derive(Debug)] +pub struct MonoLcdBridge { + // FIXME: not sure if there are separate latches for the command and data + // registers... + write_byte_latch: Option, + read_byte_latch: Option, + + panel: Box, +} + +impl MonoLcdBridge { + pub fn new(panel: Box) -> MonoLcdBridge { + MonoLcdBridge { + write_byte_latch: None, + read_byte_latch: None, + panel, + } + } + + /// Returns a callback to update the framebuffer. + pub fn render_callback(&self) -> RenderCallback { + self.panel.render_callback() + } +} + +impl Device for MonoLcdBridge { + fn kind(&self) -> &'static str { + "Mono LCD Bridge" + } + + fn probe(&self, offset: u32) -> Probe { + let reg = match offset { + 0x0 => "LCD Control", + 0x8 => "LCD Command", + 0x10 => "LCD Data", + _ => return Probe::Unmapped, + }; + + Probe::Register(reg) + } +} + +impl Memory for MonoLcdBridge { + fn r32(&mut self, offset: u32) -> MemResult { + if offset == 0x0 { + // bypass the latch + // + // Bit 15 is BUSY (iPodLinux: `lcd_busy_mask = 0x8000`), which + // guests poll before each transfer. HACK: the emulated bridge + // completes transfers instantly, so it is never busy. + return Ok(0); + } + + if let Some(val) = self.read_byte_latch.take() { + return Ok(val as u32); + } + + let val: u16 = match offset { + 0x8 => self.panel.read_command()?, + 0x10 => self.panel.read_data()?, + _ => return Err(Unexpected), + }; + + self.read_byte_latch = Some(val as u8); // latch lower 8 bits + Ok((val >> 8) as u32) // returning the higher 8 bits first + } + + fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { + if offset == 0x0 { + // bypass the latch + return Err(StubWrite(Error, ())); + } + + // the iPod uses the controller via an 8-bit interface + let val = val as u8; // FIXME: this should use trunc_to_u8, but it crashes... + let val = match self.write_byte_latch.take() { + None => { + self.write_byte_latch = Some(val); + return Ok(()); + } + Some(hi) => (hi as u16) << 8 | (val as u16), + }; + + match offset { + 0x8 => self.panel.write_command(val), + 0x10 => self.panel.write_data(val), + _ => Err(Unexpected), + } + } +} diff --git a/clicky-core/src/sys/ipod4g/mod.rs b/clicky-core/src/sys/ipod4g/mod.rs index c4d9207..2dba0e9 100644 --- a/clicky-core/src/sys/ipod4g/mod.rs +++ b/clicky-core/src/sys/ipod4g/mod.rs @@ -351,7 +351,7 @@ impl Ipod4g { /// Return the system's RenderCallback method. pub fn render_callback(&self) -> RenderCallback { - self.devices.hd66753.render_callback() + self.devices.mlcd.render_callback() } } @@ -367,7 +367,7 @@ pub struct Ipod4gBus { pub cpuid: devices::CpuIdReg, pub flash: devices::Flash, pub cpucon: devices::CpuCon, - pub hd66753: devices::Hd66753, + pub mlcd: devices::MonoLcdBridge, pub timer1: devices::CfgTimer, pub timer2: devices::CfgTimer, pub usec_timer: devices::UsecTimer, @@ -467,7 +467,7 @@ impl Ipod4gBus { usb: Usb::new(), flash: Flash::new(), cpucon: CpuCon::new(task_spawner.clone()), - hd66753: Hd66753::new(), + mlcd: MonoLcdBridge::new(Box::new(Hd66753::new())), timer1: CfgTimer::new("1", timer1_irq_tx, task_spawner.clone()), timer2: CfgTimer::new("2", timer2_irq_tx, task_spawner), usec_timer: UsecTimer::new(), @@ -643,7 +643,7 @@ mmap! { 0x6400_4000..=0x6400_41ff => intcon, // i guess there's a mirror? 0x7000_0000..=0x7000_1fff => ppcon, - 0x7000_3000..=0x7000_301f => hd66753, + 0x7000_3000..=0x7000_301f => mlcd, 0x7000_6000..=0x7000_603f => serial0, 0x7000_6040..=0x7000_607f => serial1, 0x7000_a000..=0x7000_a03f => pwmcon,