I2C API for PCA9547 support (#1860)

This commit is contained in:
Spaqin 2022-03-01 15:07:53 +08:00 committed by GitHub
parent 9bfbd39fa3
commit a85b4d5f5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 111 additions and 42 deletions

View File

@ -35,6 +35,9 @@ Highlights:
* Removed worker DB warning for writing a dataset that is also in the archive * Removed worker DB warning for writing a dataset that is also in the archive
* Extended Kasli gateware JSON description with configuration for SPI over DIO. * Extended Kasli gateware JSON description with configuration for SPI over DIO.
See: https://github.com/m-labs/artiq/pull/1800 See: https://github.com/m-labs/artiq/pull/1800
* ``PCA9548`` I2C switch class renamed to ``I2CSwitch``, to accomodate support for PCA9547, and
possibly other switches in future. Readback has been removed, and now only one channel per
switch is supported.
Breaking changes: Breaking changes:

View File

@ -33,6 +33,11 @@ def i2c_read(busno: TInt32, ack: TBool) -> TInt32:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def i2c_switch_select(busno: TInt32, address: TInt32, mask: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@kernel @kernel
def i2c_poll(busno, busaddr): def i2c_poll(busno, busaddr):
"""Poll I2C device at address. """Poll I2C device at address.
@ -137,8 +142,10 @@ def i2c_read_many(busno, busaddr, addr, data):
i2c_stop(busno) i2c_stop(busno)
class PCA9548: class I2CSwitch:
"""Driver for the PCA9548 I2C bus switch. """Driver for the I2C bus switch.
PCA954X (or other) type detection is done by the CPU during I2C init.
I2C transactions not real-time, and are performed by the CPU without I2C transactions not real-time, and are performed by the CPU without
involving RTIO. involving RTIO.
@ -151,25 +158,18 @@ class PCA9548:
self.busno = busno self.busno = busno
self.address = address self.address = address
@kernel
def select(self, mask):
"""Enable/disable channels.
:param mask: Bit mask of enabled channels
"""
i2c_write_byte(self.busno, self.address, mask)
@kernel @kernel
def set(self, channel): def set(self, channel):
"""Enable one channel. """Enable one channel.
:param channel: channel number (0-7) :param channel: channel number (0-7)
""" """
self.select(1 << channel) i2c_switch_select(self.busno, self.address >> 1, 1 << channel)
@kernel @kernel
def readback(self): def unset(self):
return i2c_read_byte(self.busno, self.address) """Disable output of the I2C switch.
"""
i2c_switch_select(self.busno, self.address >> 1, 0)
class TCA6424A: class TCA6424A:

View File

@ -37,13 +37,17 @@ class KasliEEPROM:
@kernel @kernel
def select(self): def select(self):
mask = 1 << self.port mask = 1 << self.port
self.sw0.select(mask) if self.port < 8:
self.sw1.select(mask >> 8) self.sw0.set(self.port)
self.sw1.unset()
else:
self.sw0.unset()
self.sw1.set(self.port - 8)
@kernel @kernel
def deselect(self): def deselect(self):
self.sw0.select(0) self.sw0.unset()
self.sw1.select(0) self.sw1.unset()
@kernel @kernel
def write_i32(self, addr, value): def write_i32(self, addr, value):

View File

@ -29,13 +29,13 @@ device_db = {
"i2c_switch0": { "i2c_switch0": {
"type": "local", "type": "local",
"module": "artiq.coredevice.i2c", "module": "artiq.coredevice.i2c",
"class": "PCA9548", "class": "I2CSwitch",
"arguments": {"address": 0xe0} "arguments": {"address": 0xe0}
}, },
"i2c_switch1": { "i2c_switch1": {
"type": "local", "type": "local",
"module": "artiq.coredevice.i2c", "module": "artiq.coredevice.i2c",
"class": "PCA9548", "class": "I2CSwitch",
"arguments": {"address": 0xe2} "arguments": {"address": 0xe2}
}, },
} }

View File

@ -27,13 +27,13 @@ device_db = {
"i2c_switch0": { "i2c_switch0": {
"type": "local", "type": "local",
"module": "artiq.coredevice.i2c", "module": "artiq.coredevice.i2c",
"class": "PCA9548", "class": "I2CSwitch",
"arguments": {"address": 0xe0} "arguments": {"address": 0xe0}
}, },
"i2c_switch1": { "i2c_switch1": {
"type": "local", "type": "local",
"module": "artiq.coredevice.i2c", "module": "artiq.coredevice.i2c",
"class": "PCA9548", "class": "I2CSwitch",
"arguments": {"address": 0xe2} "arguments": {"address": 0xe2}
}, },

View File

@ -31,7 +31,7 @@ device_db = {
"i2c_switch": { "i2c_switch": {
"type": "local", "type": "local",
"module": "artiq.coredevice.i2c", "module": "artiq.coredevice.i2c",
"class": "PCA9548" "class": "I2CSwitch"
}, },
# Generic TTL # Generic TTL

View File

@ -159,6 +159,7 @@ static mut API: &'static [(&'static str, *const ())] = &[
api!(i2c_stop = ::nrt_bus::i2c::stop), api!(i2c_stop = ::nrt_bus::i2c::stop),
api!(i2c_write = ::nrt_bus::i2c::write), api!(i2c_write = ::nrt_bus::i2c::write),
api!(i2c_read = ::nrt_bus::i2c::read), api!(i2c_read = ::nrt_bus::i2c::read),
api!(i2c_switch_select = ::nrt_bus::i2c::switch_select),
api!(spi_set_config = ::nrt_bus::spi::set_config), api!(spi_set_config = ::nrt_bus::spi::set_config),
api!(spi_write = ::nrt_bus::spi::write), api!(spi_write = ::nrt_bus::spi::write),

View File

@ -43,6 +43,17 @@ pub mod i2c {
data data
}) as i32 }) as i32
} }
pub extern fn switch_select(busno: i32, address: i32, mask: i32) {
send(&I2cSwitchSelectRequest {
busno: busno as u32,
address: address as u8,
mask: mask as u8 });
recv!(&I2cBasicReply { succeeded } => { if !succeeded {
raise!("I2CError", "I2C bus could not be accessed");
}
});
}
} }
pub mod spi { pub mod spi {

View File

@ -179,17 +179,17 @@ fn init() -> Result<()> {
#[cfg(soc_platform = "kasli")] #[cfg(soc_platform = "kasli")]
{ {
i2c::pca9548_select(BUSNO, 0x70, 0)?; i2c::switch_select(BUSNO, 0x70, 0)?;
i2c::pca9548_select(BUSNO, 0x71, 1 << 3)?; i2c::switch_select(BUSNO, 0x71, 1 << 3)?;
} }
#[cfg(soc_platform = "sayma_amc")] #[cfg(soc_platform = "sayma_amc")]
i2c::pca9548_select(BUSNO, 0x70, 1 << 4)?; i2c::switch_select(BUSNO, 0x70, 1 << 4)?;
#[cfg(soc_platform = "sayma_rtm")] #[cfg(soc_platform = "sayma_rtm")]
i2c::pca9548_select(BUSNO, 0x77, 1 << 5)?; i2c::switch_select(BUSNO, 0x77, 1 << 5)?;
#[cfg(soc_platform = "metlino")] #[cfg(soc_platform = "metlino")]
i2c::pca9548_select(BUSNO, 0x70, 1 << 4)?; i2c::switch_select(BUSNO, 0x70, 1 << 4)?;
#[cfg(soc_platform = "kc705")] #[cfg(soc_platform = "kc705")]
i2c::pca9548_select(BUSNO, 0x74, 1 << 7)?; i2c::switch_select(BUSNO, 0x74, 1 << 7)?;
if ident()? != 0x0182 { if ident()? != 0x0182 {
return Err("Si5324 does not have expected product number"); return Err("Si5324 does not have expected product number");

View File

@ -188,12 +188,15 @@ mod imp {
Ok(data) Ok(data)
} }
pub fn pca9548_select(busno: u8, address: u8, channels: u8) -> Result<(), &'static str> { pub fn switch_select(busno: u8, address: u8, mask: u8) -> Result<(), &'static str> {
// address in 7-bit form
// mask in format of 1 << channel (or 0 for disabling output)
// PCA9548 support only for now
start(busno)?; start(busno)?;
if !write(busno, address << 1)? { if !write(busno, address << 1)? {
return Err("PCA9548 failed to ack write address") return Err("PCA9548 failed to ack write address")
} }
if !write(busno, channels)? { if !write(busno, mask)? {
return Err("PCA9548 failed to ack control word") return Err("PCA9548 failed to ack control word")
} }
stop(busno)?; stop(busno)?;
@ -210,7 +213,7 @@ mod imp {
pub fn stop(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) } pub fn stop(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
pub fn write(_busno: u8, _data: u8) -> Result<bool, &'static str> { Err(NO_I2C) } pub fn write(_busno: u8, _data: u8) -> Result<bool, &'static str> { Err(NO_I2C) }
pub fn read(_busno: u8, _ack: bool) -> Result<u8, &'static str> { Err(NO_I2C) } pub fn read(_busno: u8, _ack: bool) -> Result<u8, &'static str> { Err(NO_I2C) }
pub fn pca9548_select(_busno: u8, _address: u8, _channels: u8) -> Result<(), &'static str> { Err(NO_I2C) } pub fn switch_select(_busno: u8, _address: u8, _mask: u8) -> Result<(), &'static str> { Err(NO_I2C) }
} }
pub use self::imp::*; pub use self::imp::*;

View File

@ -31,8 +31,8 @@ impl EEPROM {
#[cfg(soc_platform = "kasli")] #[cfg(soc_platform = "kasli")]
fn select(&self) -> Result<(), &'static str> { fn select(&self) -> Result<(), &'static str> {
let mask: u16 = 1 << self.port; let mask: u16 = 1 << self.port;
i2c::pca9548_select(self.busno, 0x70, mask as u8)?; i2c::switch_select(self.busno, 0x70, mask as u8)?;
i2c::pca9548_select(self.busno, 0x71, (mask >> 8) as u8)?; i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
Ok(()) Ok(())
} }

View File

@ -43,8 +43,8 @@ impl IoExpander {
#[cfg(soc_platform = "kasli")] #[cfg(soc_platform = "kasli")]
fn select(&self) -> Result<(), &'static str> { fn select(&self) -> Result<(), &'static str> {
let mask: u16 = 1 << self.port; let mask: u16 = 1 << self.port;
i2c::pca9548_select(self.busno, 0x70, mask as u8)?; i2c::switch_select(self.busno, 0x70, mask as u8)?;
i2c::pca9548_select(self.busno, 0x71, (mask >> 8) as u8)?; i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
Ok(()) Ok(())
} }

View File

@ -47,6 +47,7 @@ pub enum Packet {
I2cReadRequest { destination: u8, busno: u8, ack: bool }, I2cReadRequest { destination: u8, busno: u8, ack: bool },
I2cReadReply { succeeded: bool, data: u8 }, I2cReadReply { succeeded: bool, data: u8 },
I2cBasicReply { succeeded: bool }, I2cBasicReply { succeeded: bool },
I2cSwitchSelectRequest { destination: u8, busno: u8, address: u8, mask: u8 },
SpiSetConfigRequest { destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8 }, SpiSetConfigRequest { destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8 },
SpiWriteRequest { destination: u8, busno: u8, data: u32 }, SpiWriteRequest { destination: u8, busno: u8, data: u32 },
@ -154,6 +155,12 @@ impl Packet {
0x87 => Packet::I2cBasicReply { 0x87 => Packet::I2cBasicReply {
succeeded: reader.read_bool()? succeeded: reader.read_bool()?
}, },
0x88 => Packet::I2cSwitchSelectRequest {
destination: reader.read_u8()?,
busno: reader.read_u8()?,
address: reader.read_u8()?,
mask: reader.read_u8()?,
},
0x90 => Packet::SpiSetConfigRequest { 0x90 => Packet::SpiSetConfigRequest {
destination: reader.read_u8()?, destination: reader.read_u8()?,
@ -313,6 +320,13 @@ impl Packet {
writer.write_u8(0x87)?; writer.write_u8(0x87)?;
writer.write_bool(succeeded)?; writer.write_bool(succeeded)?;
}, },
Packet::I2cSwitchSelectRequest { destination, busno, address, mask } => {
writer.write_u8(0x88)?;
writer.write_u8(destination)?;
writer.write_u8(busno)?;
writer.write_u8(address)?;
writer.write_u8(mask)?;
},
Packet::SpiSetConfigRequest { destination, busno, flags, length, div, cs } => { Packet::SpiSetConfigRequest { destination, busno, flags, length, div, cs } => {
writer.write_u8(0x90)?; writer.write_u8(0x90)?;

View File

@ -65,6 +65,7 @@ pub enum Message<'a> {
I2cReadRequest { busno: u32, ack: bool }, I2cReadRequest { busno: u32, ack: bool },
I2cReadReply { succeeded: bool, data: u8 }, I2cReadReply { succeeded: bool, data: u8 },
I2cBasicReply { succeeded: bool }, I2cBasicReply { succeeded: bool },
I2cSwitchSelectRequest { busno: u32, address: u8, mask: u8 },
SpiSetConfigRequest { busno: u32, flags: u8, length: u8, div: u8, cs: u8 }, SpiSetConfigRequest { busno: u32, flags: u8, length: u8, div: u8, cs: u8 },
SpiWriteRequest { busno: u32, data: u32 }, SpiWriteRequest { busno: u32, data: u32 },

View File

@ -115,6 +115,28 @@ mod remote_i2c {
} }
} }
} }
pub fn switch_select(io: &Io, aux_mutex: &Mutex, linkno: u8, destination: u8, busno: u8, address: u8, mask: u8) -> Result<u8, &'static str> {
let reply = drtio::aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::I2cPca954xSelectRequest {
destination: destination,
busno: busno,
address: address,
mask: mask,
});
match reply {
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err("received unexpected aux packet")
}
Err(e) => {
error!("aux packet error ({})", e);
Err(e)
}
}
}
} }
#[cfg(has_drtio)] #[cfg(has_drtio)]
@ -259,6 +281,11 @@ pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex,
Err(_) => kern_send(io, &kern::I2cReadReply { succeeded: false, data: 0xff }) Err(_) => kern_send(io, &kern::I2cReadReply { succeeded: false, data: 0xff })
} }
} }
&kern::I2cSwitchSelectRequest { busno, address, mask } => {
let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno,
switch_select, address, mask).is_ok();
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
}
&kern::SpiSetConfigRequest { busno, flags, length, div, cs } => { &kern::SpiSetConfigRequest { busno, flags, length, div, cs } => {
let succeeded = dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno, let succeeded = dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno,

View File

@ -275,6 +275,11 @@ fn process_aux_packet(_repeaters: &mut [repeater::Repeater],
&drtioaux::Packet::I2cReadReply { succeeded: false, data: 0xff }) &drtioaux::Packet::I2cReadReply { succeeded: false, data: 0xff })
} }
} }
drtioaux::Packet::I2cSwitchSelectRequest { destination: _destination, busno, address, mask } => {
forward!(_routing_table, _destination, *_rank, _repeaters, &packet);
let succeeded = i2c::switch_select(busno, address, mask).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
}
drtioaux::Packet::SpiSetConfigRequest { destination: _destination, busno, flags, length, div, cs } => { drtioaux::Packet::SpiSetConfigRequest { destination: _destination, busno, flags, length, div, cs } => {
forward!(_routing_table, _destination, *_rank, _repeaters, &packet); forward!(_routing_table, _destination, *_rank, _repeaters, &packet);

View File

@ -52,13 +52,13 @@ def process_header(output, description):
"i2c_switch0": {{ "i2c_switch0": {{
"type": "local", "type": "local",
"module": "artiq.coredevice.i2c", "module": "artiq.coredevice.i2c",
"class": "PCA9548", "class": "I2CSwitch",
"arguments": {{"address": 0xe0}} "arguments": {{"address": 0xe0}}
}}, }},
"i2c_switch1": {{ "i2c_switch1": {{
"type": "local", "type": "local",
"module": "artiq.coredevice.i2c", "module": "artiq.coredevice.i2c",
"class": "PCA9548", "class": "I2CSwitch",
"arguments": {{"address": 0xe2}} "arguments": {{"address": 0xe2}}
}}, }},
}} }}

View File

@ -3,10 +3,10 @@ import os, unittest
from artiq.experiment import * from artiq.experiment import *
from artiq.test.hardware_testbench import ExperimentCase from artiq.test.hardware_testbench import ExperimentCase
from artiq.coredevice.exceptions import I2CError from artiq.coredevice.exceptions import I2CError
from artiq.coredevice.i2c import PCA9548 from artiq.coredevice.i2c import I2CSwitch
class I2CSwitch(EnvExperiment): class I2CSwitchTest(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("i2c_switch") self.setattr_device("i2c_switch")
@ -25,7 +25,7 @@ class NonexistentI2CBus(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("i2c_switch") # HACK: only run this test on boards with I2C self.setattr_device("i2c_switch") # HACK: only run this test on boards with I2C
self.broken_switch = PCA9548(self._HasEnvironment__device_mgr, 255) self.broken_switch = I2CSwitch(self._HasEnvironment__device_mgr, 255)
@kernel @kernel
def run(self): def run(self):
@ -34,7 +34,7 @@ class NonexistentI2CBus(EnvExperiment):
class I2CTest(ExperimentCase): class I2CTest(ExperimentCase):
def test_i2c_switch(self): def test_i2c_switch(self):
self.execute(I2CSwitch) self.execute(I2CSwitchTest)
self.assertTrue(self.dataset_mgr.get("passed")) self.assertTrue(self.dataset_mgr.get("passed"))
def test_nonexistent_bus(self): def test_nonexistent_bus(self):