diff --git a/artiq/firmware/libboard_misoc/io_expander.rs b/artiq/firmware/libboard_misoc/io_expander.rs new file mode 100644 index 000000000..0ff278a5e --- /dev/null +++ b/artiq/firmware/libboard_misoc/io_expander.rs @@ -0,0 +1,101 @@ +use i2c; + +pub struct IoExpander { + busno: u8, + port: u8, + address: u8, + virtual_led_mapping: &'static [(u8, u8, u8)], + out_current: [u8; 2], + out_target: [u8; 2], +} + +impl IoExpander { + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] + pub fn new(index: u8) -> Self { + const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 6), (1, 1, 6)]; + const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)]; + // Both expanders on SHARED I2C bus + match index { + 0 => IoExpander { + busno: 0, + port: 11, + address: 0x40, + virtual_led_mapping: &VIRTUAL_LED_MAPPING0, + out_current: [0; 2], + out_target: [0; 2], + }, + 1 => IoExpander { + busno: 0, + port: 11, + address: 0x42, + virtual_led_mapping: &VIRTUAL_LED_MAPPING1, + out_current: [0; 2], + out_target: [0; 2], + }, + _ => panic!("incorrect I/O expander index"), + } + } + + #[cfg(soc_platform = "kasli")] + fn select(&self) -> Result<(), &'static str> { + let mask: u16 = 1 << self.port; + i2c::pca9548_select(self.busno, 0x70, mask as u8)?; + i2c::pca9548_select(self.busno, 0x71, (mask >> 8) as u8)?; + Ok(()) + } + + fn write(&self, addr: u8, value: u8) -> Result<(), &'static str> { + i2c::start(self.busno)?; + i2c::write(self.busno, self.address)?; + i2c::write(self.busno, addr)?; + i2c::write(self.busno, value)?; + i2c::stop(self.busno)?; + Ok(()) + } + + pub fn init(&mut self) -> Result<(), &'static str> { + self.select()?; + + let mut iodir = [0xffu8; 2]; + for (_led, port, bit) in self.virtual_led_mapping.iter() { + iodir[*port as usize] &= !(1 << *bit); + } + self.write(0x00, iodir[0])?; + self.write(0x01, iodir[1])?; + + self.out_current[0] = 0x00; + self.write(0x12, 0x00)?; + self.out_current[1] = 0x00; + self.write(0x13, 0x00)?; + Ok(()) + } + + pub fn set(&mut self, port: u8, bit: u8, high: bool) { + if high { + self.out_target[port as usize] |= 1 << bit; + } else { + self.out_target[port as usize] &= !(1 << bit); + } + } + + pub fn service(&mut self) -> Result<(), &'static str> { + for (led, port, bit) in self.virtual_led_mapping.iter() { + // TODO: get level from gateware + self.set(*port, *bit, false); + } + + if self.out_target != self.out_current { + self.select()?; + if self.out_target[0] != self.out_current[0] { + self.write(0x12, self.out_target[0])?; + self.out_current[0] = self.out_target[0]; + } + if self.out_target[1] != self.out_current[1] { + self.write(0x13, self.out_target[1])?; + self.out_current[1] = self.out_target[1]; + } + } + + Ok(()) + } +} diff --git a/artiq/firmware/libboard_misoc/lib.rs b/artiq/firmware/libboard_misoc/lib.rs index 8abddf898..5e8a92972 100644 --- a/artiq/firmware/libboard_misoc/lib.rs +++ b/artiq/firmware/libboard_misoc/lib.rs @@ -37,6 +37,8 @@ pub mod ethmac; pub mod i2c; #[cfg(soc_platform = "kasli")] pub mod i2c_eeprom; +#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] +pub mod io_expander; #[cfg(all(has_ethmac, feature = "smoltcp"))] pub mod net_settings; #[cfg(has_slave_fpga_cfg)] diff --git a/artiq/firmware/runtime/main.rs b/artiq/firmware/runtime/main.rs index 02e3fa61a..3fc931032 100644 --- a/artiq/firmware/runtime/main.rs +++ b/artiq/firmware/runtime/main.rs @@ -99,6 +99,15 @@ fn startup() { setup_log_levels(); #[cfg(has_i2c)] board_misoc::i2c::init().expect("I2C initialization failed"); + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] + let (mut io_expander0, mut io_expander1); + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] + { + io_expander0 = board_misoc::io_expander::IoExpander::new(0); + io_expander1 = board_misoc::io_expander::IoExpander::new(1); + io_expander0.init().expect("I2C I/O expander #0 initialization failed"); + io_expander1.init().expect("I2C I/O expander #1 initialization failed"); + } rtio_clocking::init(); let mut net_device = unsafe { ethmac::EthernetDevice::new() }; @@ -210,6 +219,12 @@ fn startup() { if let Some(_net_stats_diff) = net_stats.update() { debug!("ethernet mac:{}", ethmac::EthernetStatistics::new()); } + + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] + { + io_expander0.service().expect("I2C I/O expander #0 service failed"); + io_expander1.service().expect("I2C I/O expander #1 service failed"); + } } } diff --git a/artiq/firmware/satman/main.rs b/artiq/firmware/satman/main.rs index 8a07db183..c3a9b05d9 100644 --- a/artiq/firmware/satman/main.rs +++ b/artiq/firmware/satman/main.rs @@ -454,13 +454,23 @@ pub extern fn main() -> i32 { info!("software ident {}", csr::CONFIG_IDENTIFIER_STR); info!("gateware ident {}", ident::read(&mut [0; 64])); - #[cfg(has_si5324)] + #[cfg(has_i2c)] + i2c::init().expect("I2C initialization failed"); + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] + let (mut io_expander0, mut io_expander1); + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] { - i2c::init().expect("I2C initialization failed"); - si5324::setup(&SI5324_SETTINGS, si5324::Input::Ckin1).expect("cannot initialize Si5324"); + io_expander0 = board_misoc::io_expander::IoExpander::new(0); + io_expander1 = board_misoc::io_expander::IoExpander::new(1); + io_expander0.init().expect("I2C I/O expander #0 initialization failed"); + io_expander1.init().expect("I2C I/O expander #1 initialization failed"); } + + #[cfg(has_si5324)] + si5324::setup(&SI5324_SETTINGS, si5324::Input::Ckin1).expect("cannot initialize Si5324"); #[cfg(has_wrpll)] wrpll::init(); + unsafe { csr::drtio_transceiver::stable_clkin_write(1); } @@ -507,6 +517,11 @@ pub extern fn main() -> i32 { for mut rep in repeaters.iter_mut() { rep.service(&routing_table, rank); } + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] + { + io_expander0.service().expect("I2C I/O expander #0 service failed"); + io_expander1.service().expect("I2C I/O expander #1 service failed"); + } hardware_tick(&mut hardware_tick_ts); } @@ -531,6 +546,11 @@ pub extern fn main() -> i32 { for mut rep in repeaters.iter_mut() { rep.service(&routing_table, rank); } + #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] + { + io_expander0.service().expect("I2C I/O expander #0 service failed"); + io_expander1.service().expect("I2C I/O expander #1 service failed"); + } hardware_tick(&mut hardware_tick_ts); if drtiosat_tsc_loaded() { info!("TSC loaded from uplink");