Port io_expander code from Kasli #214

Merged
sb10q merged 4 commits from esavkin/artiq-zynq:213-sfp-fiber-support into master 2023-02-16 10:29:06 +08:00
2 changed files with 170 additions and 0 deletions

View File

@ -0,0 +1,149 @@
use crate::i2c;
use log::info;
// Only the bare minimum registers. Bits/IO connections equivalent between IC types.
struct Registers {
// PCA9539 equivalent register names in comments
iodira: u8, // Configuration Port 0
iodirb: u8, // Configuration Port 1
gpioa: u8, // Output Port 0
gpiob: u8, // Output Port 1
}
pub struct IoExpander {
busno: i32,
port: u8,
address: i32,
iodir: [u8; 2],
out_current: [u8; 2],
out_target: [u8; 2],
registers: Registers,
}
impl IoExpander {
pub fn new(index: u8) -> Result<Self, &'static str> {
// Both expanders on SHARED I2C bus
let mut io_expander = match index {
0 => IoExpander {
busno: 0,
port: 11,
address: 0x40,
iodir: [0xff; 2],
out_current: [0; 2],
out_target: [0; 2],
registers: Registers {
iodira: 0x00,
iodirb: 0x01,
gpioa: 0x12,
gpiob: 0x13,
},
},
1 => IoExpander {
busno: 0,
port: 11,
address: 0x42,
iodir: [0xff; 2],
out_current: [0; 2],
out_target: [0; 2],
registers: Registers {
iodira: 0x00,
iodirb: 0x01,
gpioa: 0x12,
gpiob: 0x13,
},
},
_ => return Err("incorrect I/O expander index"),
};
if !io_expander.check_ack()? {
info!(
"MCP23017 io expander {} not found. Checking for PCA9539.",
index
);
io_expander.address += 0xa8; // translate to PCA9539 addresses (see schematic)
io_expander.registers = Registers {
iodira: 0x06,
iodirb: 0x07,
gpioa: 0x02,
gpiob: 0x03,
};
if !io_expander.check_ack()? {
return Err("Neither MCP23017 nor PCA9539 io expander found.");
};
}
Ok(io_expander)
}
fn select(&self) -> Result<(), &'static str> {
let mask: u16 = 1 << self.port;
i2c::switch_select(self.busno, 0x70, mask as u8 as i32);
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8 as i32);
Ok(())
}
fn write(&self, addr: u8, value: u8) -> Result<(), &'static str> {
i2c::start(self.busno);
i2c::write(self.busno, self.address as i32);
i2c::write(self.busno, addr as i32);
i2c::write(self.busno, value as i32);
i2c::stop(self.busno);
Ok(())
}
fn check_ack(&self) -> Result<bool, &'static str> {
// Check for ack from io expander
self.select()?;
i2c::start(self.busno);
let ack = i2c::write(self.busno, self.address);
i2c::stop(self.busno);
Ok(ack)
}
fn update_iodir(&self) -> Result<(), &'static str> {
self.write(self.registers.iodira, self.iodir[0])?;
self.write(self.registers.iodirb, self.iodir[1])?;
Ok(())
}
pub fn init(&mut self) -> Result<(), &'static str> {
self.select()?;
self.update_iodir()?;
self.out_current[0] = 0x00;
self.write(self.registers.gpioa, 0x00)?;
self.out_current[1] = 0x00;
self.write(self.registers.gpiob, 0x00)?;
Ok(())
}
pub fn set_oe(&mut self, port: u8, outputs: u8) -> Result<(), &'static str> {
self.iodir[port as usize] &= !outputs;
self.update_iodir()?;
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> {
if self.out_target != self.out_current {
self.select()?;
if self.out_target[0] != self.out_current[0] {
self.write(self.registers.gpioa, self.out_target[0])?;
self.out_current[0] = self.out_target[0];
}
if self.out_target[1] != self.out_current[1] {
self.write(self.registers.gpiob, self.out_target[1])?;
self.out_current[1] = self.out_target[1];
}
}
Ok(())
}
}

View File

@ -46,6 +46,8 @@ mod mgmt;
mod analyzer;
mod irq;
mod i2c;
#[cfg(feature = "target_kasli_soc")]
mod io_expander;
static mut SEEN_ASYNC_ERRORS: u8 = 0;
@ -114,6 +116,25 @@ pub fn main_core0() {
i2c::init();
#[cfg(feature = "target_kasli_soc")]
{
let (mut io_expander0, mut io_expander1) = (io_expander::IoExpander::new(0).unwrap(), io_expander::IoExpander::new(1).unwrap());
io_expander0.init().expect("I2C I/O expander #0 initialization failed");
io_expander1.init().expect("I2C I/O expander #1 initialization failed");
// Actively drive TX_DISABLE to false on SFP0..3

Needs to be done in satman as well.

Needs to be done in satman as well.

Maybe do it in another PR? It looks like i2c logic in satman needs to be refactored to make io_expander shared

Maybe do it in another PR? It looks like i2c logic in satman needs to be refactored to make io_expander shared

Looks like it's a super simple "refactoring" but ok if you want...

Looks like it's a super simple "refactoring" but ok if you want...
io_expander0.set_oe(0, 1 << 1).unwrap();
io_expander0.set_oe(1, 1 << 1).unwrap();
io_expander1.set_oe(0, 1 << 1).unwrap();
io_expander1.set_oe(1, 1 << 1).unwrap();
io_expander0.set(0, 1, false);
io_expander0.set(1, 1, false);
io_expander1.set(0, 1, false);
io_expander1.set(1, 1, false);
io_expander0.service().unwrap();
io_expander1.service().unwrap();
}
let cfg = match Config::new() {
Ok(cfg) => cfg,
Err(err) => {