add pca954x autodetection, pca9547 support

This commit is contained in:
mwojcik 2022-02-08 15:40:17 +08:00
parent e5e646f40e
commit 3efc682bd6
3 changed files with 115 additions and 12 deletions

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

@ -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<Microseconds>
count_down: super::timer::global::CountDown<Microseconds>,
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<PCA954X, &'static str> {
// 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()?;

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