//! Low-level GPIO access module. use embedded_hal::digital::PinState; use zynq7000::gpio::{Gpio, MaskedOutput, MmioGpio}; use crate::slcr::Slcr; use super::{PinIsOutputOnly, mio::MuxConf}; #[derive(Debug, Clone, Copy)] pub enum PinOffset { Mio(usize), Emio(usize), } impl PinOffset { /// Returs [None] if offset is larger than 53. pub const fn new_for_mio(offset: usize) -> Option { if offset > 53 { return None; } Some(PinOffset::Mio(offset)) } /// Returs [None] if offset is larger than 63. pub const fn new_for_emio(offset: usize) -> Option { if offset > 63 { return None; } Some(PinOffset::Emio(offset)) } pub fn is_mio(&self) -> bool { match self { PinOffset::Mio(_) => true, PinOffset::Emio(_) => false, } } } impl PinOffset { pub fn offset(&self) -> usize { match self { PinOffset::Mio(offset) => *offset, PinOffset::Emio(offset) => *offset, } } } pub struct LowLevelGpio { offset: PinOffset, regs: MmioGpio<'static>, } impl LowLevelGpio { pub fn new(offset: PinOffset) -> Self { Self { offset, regs: unsafe { Gpio::new_mmio_fixed() }, } } pub fn offset(&self) -> PinOffset { self.offset } /// Convert the pin into an output pin. pub fn configure_as_output_push_pull(&mut self, init_level: PinState) { let (offset, dirm, outen) = self.get_dirm_outen_regs_and_local_offset(); if self.offset.is_mio() { // Tri-state bit must be 0 for the output driver to work. self.reconfigure_slcr_mio_cfg(false, None, Some(MuxConf::new_for_gpio())); } let mut curr_dirm = unsafe { core::ptr::read_volatile(dirm) }; curr_dirm |= 1 << offset; unsafe { core::ptr::write_volatile(dirm, curr_dirm) }; let mut curr_outen = unsafe { core::ptr::read_volatile(outen) }; curr_outen |= 1 << offset; unsafe { core::ptr::write_volatile(outen, curr_outen) }; // Unwrap okay, just set mode. self.write_state(init_level); } /// Convert the pin into an output pin with open drain emulation. /// /// This works by only enabling the output driver when the pin is driven low and letting /// the pin float when it is driven high. A pin pull-up is used for MIO pins as well which /// pulls the pin to a defined state if it is not driven. This allows something like 1-wire bus /// operation because other devices can pull the pin low as well. /// /// For EMIO pins, the pull-up and the IO buffer necessary for open-drain usage must be /// provided by the FPGA design. pub fn configure_as_output_open_drain( &mut self, init_level: PinState, with_internal_pullup: bool, ) { let (offset, dirm, outen) = self.get_dirm_outen_regs_and_local_offset(); if self.offset.is_mio() { // Tri-state bit must be 0 for the output driver to work. Enable the pullup pin. self.reconfigure_slcr_mio_cfg( false, Some(with_internal_pullup), Some(MuxConf::new_for_gpio()), ); } let mut curr_dirm = unsafe { core::ptr::read_volatile(dirm) }; curr_dirm |= 1 << offset; unsafe { core::ptr::write_volatile(dirm, curr_dirm) }; // Disable the output driver depending on initial level. let mut curr_outen = unsafe { core::ptr::read_volatile(outen) }; if init_level == PinState::High { curr_outen &= !(1 << offset); } else { curr_outen |= 1 << offset; self.write_state(init_level); } unsafe { core::ptr::write_volatile(outen, curr_outen) }; } /// Convert the pin into a floating input pin. pub fn configure_as_input_floating(&mut self) -> Result<(), PinIsOutputOnly> { if self.offset.is_mio() { let offset_raw = self.offset.offset(); if offset_raw == 7 || offset_raw == 8 { return Err(PinIsOutputOnly); } self.reconfigure_slcr_mio_cfg(true, Some(false), Some(MuxConf::new_for_gpio())); } self.configure_input_pin(); Ok(()) } /// Convert the pin into an input pin with a pull up. pub fn configure_as_input_with_pull_up(&mut self) -> Result<(), PinIsOutputOnly> { if self.offset.is_mio() { let offset_raw = self.offset.offset(); if offset_raw == 7 || offset_raw == 8 { return Err(PinIsOutputOnly); } self.reconfigure_slcr_mio_cfg(true, Some(true), Some(MuxConf::new_for_gpio())); } self.configure_input_pin(); Ok(()) } /// Convert the pin into an IO peripheral pin. pub fn configure_as_io_periph_pin(&mut self, mux_conf: MuxConf, pullup: Option) { self.reconfigure_slcr_mio_cfg(false, pullup, Some(mux_conf)); } pub fn set_mio_pin_config(&mut self, config: zynq7000::slcr::mio::Config) { let raw_offset = self.offset.offset(); // Safety: We only modify the MIO config of the pin. let mut slcr_wrapper = unsafe { Slcr::steal() }; slcr_wrapper.modify(|mut_slcr| mut_slcr.write_mio_pins(raw_offset, config).unwrap()); } /// Set the MIO pin configuration with an unlocked SLCR. pub fn set_mio_pin_config_with_unlocked_slcr( &mut self, slcr: &mut zynq7000::slcr::MmioSlcr<'static>, config: zynq7000::slcr::mio::Config, ) { let raw_offset = self.offset.offset(); slcr.write_mio_pins(raw_offset, config).unwrap(); } #[inline] pub fn is_low(&self) -> bool { let (offset, in_reg) = self.get_data_in_reg_and_local_offset(); let in_val = unsafe { core::ptr::read_volatile(in_reg) }; ((in_val >> offset) & 0b1) == 0 } #[inline] pub fn is_high(&self) -> bool { !self.is_low() } #[inline] pub fn is_set_low(&self) -> bool { let (offset, out_reg) = self.get_data_out_reg_and_local_offset(); let out_val = unsafe { core::ptr::read_volatile(out_reg) }; ((out_val >> offset) & 0b1) == 0 } #[inline] pub fn is_set_high(&self) -> bool { !self.is_set_low() } #[inline] pub fn enable_output_driver(&mut self) { let (offset, _dirm, outen) = self.get_dirm_outen_regs_and_local_offset(); let mut outen_reg = unsafe { core::ptr::read_volatile(outen) }; outen_reg |= 1 << offset; unsafe { core::ptr::write_volatile(outen, outen_reg) }; } #[inline] pub fn disable_output_driver(&mut self) { let (offset, _dirm, outen) = self.get_dirm_outen_regs_and_local_offset(); let mut outen_reg = unsafe { core::ptr::read_volatile(outen) }; outen_reg &= !(1 << offset); unsafe { core::ptr::write_volatile(outen, outen_reg) }; } #[inline] pub fn set_low(&mut self) { self.write_state(PinState::Low) } #[inline] pub fn set_high(&mut self) { self.write_state(PinState::High) } #[inline] fn write_state(&mut self, level: PinState) { let (offset_in_reg, masked_out_ptr) = self.get_masked_out_reg_and_local_offset(); unsafe { core::ptr::write_volatile( masked_out_ptr, MaskedOutput::builder() .with_mask(!(1 << offset_in_reg)) .with_output((level as u16) << offset_in_reg) .build(), ); } } fn reconfigure_slcr_mio_cfg( &mut self, tristate: bool, pullup: Option, mux_conf: Option, ) { let raw_offset = self.offset.offset(); // Safety: We only modify the MIO config of the pin. let mut slcr_wrapper = unsafe { Slcr::steal() }; // We read first, because writing also required unlocking the SLCR. // This allows the user to configure the SLCR themselves to avoid unnecessary // re-configuration which might also be potentially unsafe at run-time. let mio_cfg = slcr_wrapper.regs().read_mio_pins(raw_offset).unwrap(); if (pullup.is_some() && mio_cfg.pullup() != pullup.unwrap()) || (mux_conf.is_some() && MuxConf::from(mio_cfg) != mux_conf.unwrap()) || tristate != mio_cfg.tri_enable() { slcr_wrapper.modify(|mut_slcr| { mut_slcr .modify_mio_pins(raw_offset, |mut val| { if let Some(pullup) = pullup { val.set_pullup(pullup); } if let Some(mux_conf) = mux_conf { val.set_l0_sel(mux_conf.l0_sel()); val.set_l1_sel(mux_conf.l1_sel()); val.set_l2_sel(mux_conf.l2_sel()); val.set_l3_sel(mux_conf.l3_sel()); } val.set_tri_enable(tristate); val }) .unwrap(); }); } } fn configure_input_pin(&mut self) { let (offset, dirm, outen) = self.get_dirm_outen_regs_and_local_offset(); let mut curr_dirm = unsafe { core::ptr::read_volatile(dirm) }; curr_dirm &= !(1 << offset); unsafe { core::ptr::write_volatile(dirm, curr_dirm) }; let mut curr_outen = unsafe { core::ptr::read_volatile(outen) }; curr_outen &= !(1 << offset); unsafe { core::ptr::write_volatile(outen, curr_outen) }; } #[inline(always)] fn get_data_in_reg_and_local_offset(&self) -> (usize, *mut u32) { match self.offset { PinOffset::Mio(offset) => match offset { 0..=31 => (offset, self.regs.pointer_to_in_0()), 32..=53 => (offset - 32, self.regs.pointer_to_in_1()), _ => panic!("invalid MIO pin offset"), }, PinOffset::Emio(offset) => match offset { 0..=31 => (offset, self.regs.pointer_to_in_2()), 32..=63 => (offset - 32, self.regs.pointer_to_in_3()), _ => panic!("invalid EMIO pin offset"), }, } } #[inline(always)] fn get_data_out_reg_and_local_offset(&self) -> (usize, *mut u32) { match self.offset { PinOffset::Mio(offset) => match offset { 0..=31 => (offset, self.regs.pointer_to_out_0()), 32..=53 => (offset - 32, self.regs.pointer_to_out_1()), _ => panic!("invalid MIO pin offset"), }, PinOffset::Emio(offset) => match offset { 0..=31 => (offset, self.regs.pointer_to_out_2()), 32..=63 => (offset - 32, self.regs.pointer_to_out_3()), _ => panic!("invalid EMIO pin offset"), }, } } #[inline(always)] fn get_dirm_outen_regs_and_local_offset(&self) -> (usize, *mut u32, *mut u32) { match self.offset { PinOffset::Mio(offset) => match offset { 0..=31 => ( offset, self.regs.bank_0_shared().pointer_to_dirm(), self.regs.bank_0_shared().pointer_to_out_en(), ), 32..=53 => ( offset - 32, self.regs.bank_1_shared().pointer_to_dirm(), self.regs.bank_1_shared().pointer_to_out_en(), ), _ => panic!("invalid MIO pin offset"), }, PinOffset::Emio(offset) => match offset { 0..=31 => ( offset, self.regs.bank_2_shared().pointer_to_dirm(), self.regs.bank_2_shared().pointer_to_out_en(), ), 32..=63 => ( offset - 32, self.regs.bank_3_shared().pointer_to_dirm(), self.regs.bank_3_shared().pointer_to_out_en(), ), _ => panic!("invalid EMIO pin offset"), }, } } #[inline(always)] fn get_masked_out_reg_and_local_offset(&mut self) -> (usize, *mut MaskedOutput) { match self.offset { PinOffset::Mio(offset) => match offset { 0..=15 => (offset, self.regs.pointer_to_masked_out_0_lsw()), 16..=31 => (offset - 16, self.regs.pointer_to_masked_out_0_msw()), 32..=47 => (offset - 32, self.regs.pointer_to_masked_out_1_lsw()), 48..=53 => (offset - 48, self.regs.pointer_to_masked_out_1_msw()), _ => panic!("invalid MIO pin offset"), }, PinOffset::Emio(offset) => match offset { 0..=15 => (offset, self.regs.pointer_to_masked_out_2_lsw()), 16..=31 => (offset - 16, self.regs.pointer_to_masked_out_2_msw()), 32..=47 => (offset - 32, self.regs.pointer_to_masked_out_3_lsw()), 48..=63 => (offset - 48, self.regs.pointer_to_masked_out_3_msw()), _ => panic!("invalid EMIO pin offset"), }, } } }