From 3efc682bd6e9e39b9bd151fda92191d9237b4214 Mon Sep 17 00:00:00 2001 From: mwojcik Date: Tue, 8 Feb 2022 15:40:17 +0800 Subject: [PATCH] add pca954x autodetection, pca9547 support --- libboard_zynq/src/i2c/eeprom.rs | 6 +-- libboard_zynq/src/i2c/mod.rs | 88 ++++++++++++++++++++++++++++++--- libboard_zynq/src/i2c/regs.rs | 33 ++++++++++++- 3 files changed, 115 insertions(+), 12 deletions(-) diff --git a/libboard_zynq/src/i2c/eeprom.rs b/libboard_zynq/src/i2c/eeprom.rs index e00dd15..fb90f9c 100644 --- a/libboard_zynq/src/i2c/eeprom.rs +++ b/libboard_zynq/src/i2c/eeprom.rs @@ -35,16 +35,14 @@ impl<'a> EEPROM<'a> { #[cfg(feature = "target_zc706")] fn select(&mut self) -> Result<(), &'static str> { - let mask: u16 = 1 << self.port; - self.i2c.pca9548_select(0b1110100, mask as u8)?; + self.i2c.pca954x_select(0b1110100, self.port)?; Ok(()) } #[cfg(feature = "target_kasli_soc")] fn select(&mut self) -> Result<(), &'static str> { - let mask: u16 = 1 << self.port; // tca9548 is compatible with pca9548 - self.i2c.pca9548_select(0b1110001, mask as u8)?; + self.i2c.pca954x_select(0b1110001, self.port)?; Ok(()) } diff --git a/libboard_zynq/src/i2c/mod.rs b/libboard_zynq/src/i2c/mod.rs index 6129799..80e76d2 100644 --- a/libboard_zynq/src/i2c/mod.rs +++ b/libboard_zynq/src/i2c/mod.rs @@ -7,9 +7,16 @@ use super::time::Microseconds; use embedded_hal::timer::CountDown; use libregister::{RegisterR, RegisterRW, RegisterW}; +enum PCA954X { + PCA9548 = 0, + #[cfg(feature = "target_kasli_soc")] + PCA9547 = 1, +} + pub struct I2c { regs: regs::RegisterBlock, - count_down: super::timer::global::CountDown + count_down: super::timer::global::CountDown, + pca_type: PCA954X } impl I2c { @@ -48,14 +55,15 @@ impl I2c { slcr.gpio_rst_ctrl.reset_gpio(); }); - Self::i2c_common(0xFFFF - 0x000C) + Self::i2c_common(0xFFFF - 0x000C, 0xFFFF - 0x0002) } - fn i2c_common(gpio_output_mask: u16) -> Self { + fn i2c_common(gpio_output_mask: u16, _gpio_output_mask_lower: u16) -> Self { // Setup register block let self_ = Self { regs: regs::RegisterBlock::i2c(), - count_down: unsafe { super::timer::GlobalTimer::get() }.countdown() + count_down: unsafe { super::timer::GlobalTimer::get() }.countdown(), + pca_type: PCA954X::PCA9548 }; // Setup GPIO output mask @@ -67,6 +75,17 @@ impl I2c { w.scl(true).sda(true) }); + //Kasli-SoC exclusive: I2C_SW_RESET configuration + #[cfg(feature = "target_kasli_soc")] + { + self_.regs.gpio_output_mask_lower.modify(|_, w| { + w.mask(_gpio_output_mask_lower) + }); + self_.regs.gpio_direction.modify(|_, w| { + w.i2cswr(true) + }); + } + self_ } @@ -110,6 +129,47 @@ impl I2c { }) } + #[cfg(feature = "target_kasli_soc")] + fn i2cswr_oe(&mut self, oe: bool) { + self.regs.gpio_output_enable.modify(|_, w| { + w.i2cswr(oe) + }) + } + + #[cfg(feature = "target_kasli_soc")] + fn i2cswr_o(&mut self, o: bool) { + self.regs.gpio_output_mask_lower.modify(|_, w| { + w.i2cswr_o(o) + }) + } + + #[cfg(feature = "target_kasli_soc")] + fn pca_autodetect(&mut self) -> Result { + // start with resetting the PCA954X + self.i2cswr_oe(false); + self.i2cswr_o(false); + self.delay_us(10); // reset time is just 500ns + self.i2cswr_oe(true); + self.i2cswr_o(true); + self.delay_us(10); + + let pca954x_addr = 0x70; + + self.start()?; + // read the config register + if !self.write(pca954x_addr << 1 | 0x01)? { + return Err("PCA954X failed to ack read address"); + } + let config = self.read(true)?; + let pca = match config { + 0x00 => PCA954X::PCA9548, + 0x08 => PCA954X::PCA9547, + _ => { return Err("Unknown PCA954X type."); } + }; + self.stop()?; + Ok(pca) + } + pub fn init(&mut self) -> Result<(), &'static str> { self.scl_oe(false); self.sda_oe(false); @@ -136,6 +196,14 @@ impl I2c { return Err("SCL is stuck low"); } // postcondition: SCL and SDA high + #[cfg(feature = "target_kasli_soc")] + { + self.pca_type = self.pca_autodetect()?; + } + #[cfg(not(feature = "target_kasli_soc"))] + { + self.pca_type = PCA954X::PCA9548; + } Ok(()) } @@ -231,12 +299,20 @@ impl I2c { Ok(data) } - pub fn pca9548_select(&mut self, address: u8, channels: u8) -> Result<(), &'static str> { + pub fn pca954x_select(&mut self, address: u8, channel: u8) -> Result<(), &'static str> { self.start()?; + // PCA9547 supports only one channel at a time + // for compatibility, PCA9548 is treated as such + let setting = match self.pca_type { + PCA954X::PCA9548 => 1 << channel, + #[cfg(feature = "target_kasli_soc")] + PCA954X::PCA9547 => channel | 0x08, + }; + if !self.write(address << 1)? { return Err("PCA9548 failed to ack write address") } - if !self.write(channels)? { + if !self.write(setting)? { return Err("PCA9548 failed to ack control word") } self.stop()?; diff --git a/libboard_zynq/src/i2c/regs.rs b/libboard_zynq/src/i2c/regs.rs index f0fae8a..01d26ae 100644 --- a/libboard_zynq/src/i2c/regs.rs +++ b/libboard_zynq/src/i2c/regs.rs @@ -20,13 +20,15 @@ use libregister::{ // // Current compatibility: // zc706: GPIO 50, 51 == SCL, SDA -// kasli_soc: GPIO 50, 51 == SCL, SDA +// kasli_soc: GPIO 50, 51 == SCL, SDA; GPIO 33 == I2C_SW_RESET pub struct RegisterBlock { pub gpio_output_mask: &'static mut GPIOOutputMask, pub gpio_input: &'static mut GPIOInput, pub gpio_direction: &'static mut GPIODirection, pub gpio_output_enable: &'static mut GPIOOutputEnable, + #[cfg(feature = "target_kasli_soc")] + pub gpio_output_mask_lower: &'static mut GPIOOutputMaskLower, } impl RegisterBlock { @@ -35,7 +37,9 @@ impl RegisterBlock { gpio_output_mask: GPIOOutputMask::new(), gpio_input: GPIOInput::new(), gpio_direction: GPIODirection::new(), - gpio_output_enable: GPIOOutputEnable::new() + gpio_output_enable: GPIOOutputEnable::new(), + #[cfg(feature = "target_kasli_soc")] + gpio_output_mask_lower: GPIOOutputMaskLower::new(), } } } @@ -59,6 +63,21 @@ register_bits!(gpio_output_mask, /// Mask for keeping bits except SCL and SDA unchanged mask, u16, 16, 31); + +register!(gpio_output_mask_lower, + /// MASK_DATA_1_LSW: + /// Maskable output data for MIO[47:32] + GPIOOutputMaskLower, RW, u32); +#[cfg(feature = "target_kasli_soc")] +register_at!(GPIOOutputMaskLower, 0xE000A008, new); +#[cfg(feature = "target_kasli_soc")] +register_bit!(gpio_output_mask_lower, + /// Output for I2C_SW_RESET (MIO[33]) + i2cswr_o, 1); +#[cfg(feature = "target_kasli_soc")] +register_bits!(gpio_output_mask_lower, + mask, u16, 16, 31); + register!(gpio_input, /// DATA_1_RO: /// Input data for MIO[53:32] @@ -74,6 +93,7 @@ register_bit!(gpio_input, /// Input for SDA sda, 19); + register!(gpio_direction, /// DIRM_1: /// Direction mode for MIO[53:32]; 0/1 = in/out @@ -88,6 +108,10 @@ register_bit!(gpio_direction, register_bit!(gpio_direction, /// Direction for SDA sda, 19); +#[cfg(feature = "target_kasli_soc")] +register_bit!(gpio_direction, + /// Direction for I2C_SW_RESET + i2cswr, 1); register!(gpio_output_enable, /// OEN_1: @@ -103,3 +127,8 @@ register_bit!(gpio_output_enable, register_bit!(gpio_output_enable, /// Output enable for SDA sda, 19); +#[cfg(feature = "target_kasli_soc")] +register_bit!(gpio_output_enable, + /// Output enable for I2C_SW_RESET + i2cswr, 1); + \ No newline at end of file