PCA9547 Support/Autodetect #87

Merged
sb10q merged 6 commits from mwojcik/zynq-rs:pca9547_support into master 2022-02-11 12:38:59 +08:00
4 changed files with 121 additions and 15 deletions

View File

@ -278,6 +278,7 @@
openocd gdb
openssh rsync
llvmPackages_9.clang-unwrapped
(python3.withPackages(ps: [ ps.pyftdi ]))
mkbootimage ];
};
};

View File

@ -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(())
}

View File

@ -6,10 +6,19 @@ use super::slcr;
use super::time::Microseconds;
use embedded_hal::timer::CountDown;
use libregister::{RegisterR, RegisterRW, RegisterW};
#[cfg(feature = "target_kasli_soc")]
use log::info;
pub enum I2cMultiplexer {
PCA9548 = 0,
#[cfg(feature = "target_kasli_soc")]
PCA9547 = 1,
}
pub struct I2c {
regs: regs::RegisterBlock,
count_down: super::timer::global::CountDown<Microseconds>
count_down: super::timer::global::CountDown<Microseconds>,
pca_type: I2cMultiplexer
}
impl I2c {
@ -48,14 +57,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: I2cMultiplexer::PCA9548 //default for zc706
};
// Setup GPIO output mask
@ -67,6 +77,17 @@ impl I2c {
w.scl(true).sda(true)
});
//Kasli-SoC only: 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 +131,48 @@ 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<I2cMultiplexer, &'static str> {
// start with resetting the PCA954X
// SDA must be clear (before start)
// reset time is 500ns, unit_delay (100us) to account for propagation
self.i2cswr_o(true);
self.unit_delay();
self.i2cswr_o(false);
self.unit_delay();
let pca954x_read_addr = (0x71 << 1) | 0x01;
self.start()?;
// read the config register
if !self.write(pca954x_read_addr)? {
return Err("PCA954X failed to ack read address");
}
let config = self.read(false)?;
let pca = match config {
0x00 => { info!("PCA9548 detected"); I2cMultiplexer::PCA9548 },
0x08 => { info!("PCA9547 detected"); I2cMultiplexer::PCA9547 },
_ => { return Err("Unknown response for PCA954X autodetect")},
};
self.stop()?;
Ok(pca)
}
pub fn init(&mut self) -> Result<(), &'static str> {
self.scl_oe(false);
self.sda_oe(false);
@ -136,6 +199,13 @@ impl I2c {
return Err("SCL is stuck low");
}
// postcondition: SCL and SDA high
#[cfg(feature = "target_kasli_soc")]
{
self.i2cswr_oe(true);
self.pca_type = self.pca_autodetect()?;
}
Ok(())
}
@ -219,7 +289,7 @@ impl I2c {
if self.sda_i() { data |= 1 << bit }
self.scl_oe(true);
}
// Send ack/nack
// Send ack/nack (true = nack, false = ack)
self.sda_oe(ack);
self.unit_delay();
self.scl_oe(false);
@ -231,13 +301,21 @@ 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 too
let setting = match self.pca_type {
I2cMultiplexer::PCA9548 => 1 << channel,
#[cfg(feature = "target_kasli_soc")]
I2cMultiplexer::PCA9547 => channel | 0x08,
};
if !self.write(address << 1)? {
return Err("PCA9548 failed to ack write address")
return Err("PCA954X failed to ack write address")
}
if !self.write(channels)? {
return Err("PCA9548 failed to ack control word")
if !self.write(setting)? {
return Err("PCA954X failed to ack control word")
}
self.stop()?;
Ok(())

View File

@ -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);