From a85b4d5f5ed80e50f1005530b16570057672d770 Mon Sep 17 00:00:00 2001 From: Spaqin Date: Tue, 1 Mar 2022 15:07:53 +0800 Subject: [PATCH] I2C API for PCA9547 support (#1860) --- RELEASE_NOTES.rst | 3 ++ artiq/coredevice/i2c.py | 28 +++++++++---------- artiq/coredevice/kasli_i2c.py | 12 +++++--- artiq/examples/kasli/device_db.py | 4 +-- artiq/examples/kasli_suservo/device_db.py | 4 +-- artiq/examples/kc705_nist_clock/device_db.py | 2 +- artiq/firmware/ksupport/api.rs | 1 + artiq/firmware/ksupport/nrt_bus.rs | 11 ++++++++ artiq/firmware/libboard_artiq/si5324.rs | 12 ++++---- artiq/firmware/libboard_misoc/i2c.rs | 9 ++++-- artiq/firmware/libboard_misoc/i2c_eeprom.rs | 4 +-- artiq/firmware/libboard_misoc/io_expander.rs | 4 +-- .../firmware/libproto_artiq/drtioaux_proto.rs | 14 ++++++++++ artiq/firmware/libproto_artiq/kernel_proto.rs | 1 + artiq/firmware/runtime/kern_hwreq.rs | 27 ++++++++++++++++++ artiq/firmware/satman/main.rs | 5 ++++ artiq/frontend/artiq_ddb_template.py | 4 +-- artiq/test/coredevice/test_i2c.py | 8 +++--- 18 files changed, 111 insertions(+), 42 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 5d890b590..de797aaff 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -35,6 +35,9 @@ Highlights: * 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. 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: diff --git a/artiq/coredevice/i2c.py b/artiq/coredevice/i2c.py index 3608f4dc8..2b3be2bc7 100644 --- a/artiq/coredevice/i2c.py +++ b/artiq/coredevice/i2c.py @@ -33,6 +33,11 @@ def i2c_read(busno: TInt32, ack: TBool) -> TInt32: 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 def i2c_poll(busno, busaddr): """Poll I2C device at address. @@ -137,8 +142,10 @@ def i2c_read_many(busno, busaddr, addr, data): i2c_stop(busno) -class PCA9548: - """Driver for the PCA9548 I2C bus switch. +class I2CSwitch: + """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 involving RTIO. @@ -151,25 +158,18 @@ class PCA9548: self.busno = busno 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 def set(self, channel): """Enable one channel. - :param channel: channel number (0-7) """ - self.select(1 << channel) + i2c_switch_select(self.busno, self.address >> 1, 1 << channel) @kernel - def readback(self): - return i2c_read_byte(self.busno, self.address) + def unset(self): + """Disable output of the I2C switch. + """ + i2c_switch_select(self.busno, self.address >> 1, 0) class TCA6424A: diff --git a/artiq/coredevice/kasli_i2c.py b/artiq/coredevice/kasli_i2c.py index 11c69172f..1e031fa2b 100644 --- a/artiq/coredevice/kasli_i2c.py +++ b/artiq/coredevice/kasli_i2c.py @@ -37,13 +37,17 @@ class KasliEEPROM: @kernel def select(self): mask = 1 << self.port - self.sw0.select(mask) - self.sw1.select(mask >> 8) + if self.port < 8: + self.sw0.set(self.port) + self.sw1.unset() + else: + self.sw0.unset() + self.sw1.set(self.port - 8) @kernel def deselect(self): - self.sw0.select(0) - self.sw1.select(0) + self.sw0.unset() + self.sw1.unset() @kernel def write_i32(self, addr, value): diff --git a/artiq/examples/kasli/device_db.py b/artiq/examples/kasli/device_db.py index 4b20c7a28..f4d39293e 100644 --- a/artiq/examples/kasli/device_db.py +++ b/artiq/examples/kasli/device_db.py @@ -29,13 +29,13 @@ device_db = { "i2c_switch0": { "type": "local", "module": "artiq.coredevice.i2c", - "class": "PCA9548", + "class": "I2CSwitch", "arguments": {"address": 0xe0} }, "i2c_switch1": { "type": "local", "module": "artiq.coredevice.i2c", - "class": "PCA9548", + "class": "I2CSwitch", "arguments": {"address": 0xe2} }, } diff --git a/artiq/examples/kasli_suservo/device_db.py b/artiq/examples/kasli_suservo/device_db.py index fdb85dc47..f0d3a48a2 100644 --- a/artiq/examples/kasli_suservo/device_db.py +++ b/artiq/examples/kasli_suservo/device_db.py @@ -27,13 +27,13 @@ device_db = { "i2c_switch0": { "type": "local", "module": "artiq.coredevice.i2c", - "class": "PCA9548", + "class": "I2CSwitch", "arguments": {"address": 0xe0} }, "i2c_switch1": { "type": "local", "module": "artiq.coredevice.i2c", - "class": "PCA9548", + "class": "I2CSwitch", "arguments": {"address": 0xe2} }, diff --git a/artiq/examples/kc705_nist_clock/device_db.py b/artiq/examples/kc705_nist_clock/device_db.py index 1b3c3e615..bf9637d32 100644 --- a/artiq/examples/kc705_nist_clock/device_db.py +++ b/artiq/examples/kc705_nist_clock/device_db.py @@ -31,7 +31,7 @@ device_db = { "i2c_switch": { "type": "local", "module": "artiq.coredevice.i2c", - "class": "PCA9548" + "class": "I2CSwitch" }, # Generic TTL diff --git a/artiq/firmware/ksupport/api.rs b/artiq/firmware/ksupport/api.rs index 06d7c1d89..3e99731b4 100644 --- a/artiq/firmware/ksupport/api.rs +++ b/artiq/firmware/ksupport/api.rs @@ -159,6 +159,7 @@ static mut API: &'static [(&'static str, *const ())] = &[ api!(i2c_stop = ::nrt_bus::i2c::stop), api!(i2c_write = ::nrt_bus::i2c::write), 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_write = ::nrt_bus::spi::write), diff --git a/artiq/firmware/ksupport/nrt_bus.rs b/artiq/firmware/ksupport/nrt_bus.rs index 9e226dd96..84927262c 100644 --- a/artiq/firmware/ksupport/nrt_bus.rs +++ b/artiq/firmware/ksupport/nrt_bus.rs @@ -43,6 +43,17 @@ pub mod i2c { data }) 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 { diff --git a/artiq/firmware/libboard_artiq/si5324.rs b/artiq/firmware/libboard_artiq/si5324.rs index f5e816f14..3b1103d99 100644 --- a/artiq/firmware/libboard_artiq/si5324.rs +++ b/artiq/firmware/libboard_artiq/si5324.rs @@ -179,17 +179,17 @@ fn init() -> Result<()> { #[cfg(soc_platform = "kasli")] { - i2c::pca9548_select(BUSNO, 0x70, 0)?; - i2c::pca9548_select(BUSNO, 0x71, 1 << 3)?; + i2c::switch_select(BUSNO, 0x70, 0)?; + i2c::switch_select(BUSNO, 0x71, 1 << 3)?; } #[cfg(soc_platform = "sayma_amc")] - i2c::pca9548_select(BUSNO, 0x70, 1 << 4)?; + i2c::switch_select(BUSNO, 0x70, 1 << 4)?; #[cfg(soc_platform = "sayma_rtm")] - i2c::pca9548_select(BUSNO, 0x77, 1 << 5)?; + i2c::switch_select(BUSNO, 0x77, 1 << 5)?; #[cfg(soc_platform = "metlino")] - i2c::pca9548_select(BUSNO, 0x70, 1 << 4)?; + i2c::switch_select(BUSNO, 0x70, 1 << 4)?; #[cfg(soc_platform = "kc705")] - i2c::pca9548_select(BUSNO, 0x74, 1 << 7)?; + i2c::switch_select(BUSNO, 0x74, 1 << 7)?; if ident()? != 0x0182 { return Err("Si5324 does not have expected product number"); diff --git a/artiq/firmware/libboard_misoc/i2c.rs b/artiq/firmware/libboard_misoc/i2c.rs index 19ff3195a..c7f8f063c 100644 --- a/artiq/firmware/libboard_misoc/i2c.rs +++ b/artiq/firmware/libboard_misoc/i2c.rs @@ -188,12 +188,15 @@ mod imp { 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)?; if !write(busno, address << 1)? { return Err("PCA9548 failed to ack write address") } - if !write(busno, channels)? { + if !write(busno, mask)? { return Err("PCA9548 failed to ack control word") } stop(busno)?; @@ -210,7 +213,7 @@ mod imp { pub fn stop(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) } pub fn write(_busno: u8, _data: u8) -> Result { Err(NO_I2C) } pub fn read(_busno: u8, _ack: bool) -> Result { 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::*; diff --git a/artiq/firmware/libboard_misoc/i2c_eeprom.rs b/artiq/firmware/libboard_misoc/i2c_eeprom.rs index 6f2dee15e..aabd13502 100644 --- a/artiq/firmware/libboard_misoc/i2c_eeprom.rs +++ b/artiq/firmware/libboard_misoc/i2c_eeprom.rs @@ -31,8 +31,8 @@ impl EEPROM { #[cfg(soc_platform = "kasli")] fn select(&self) -> Result<(), &'static str> { let mask: u16 = 1 << self.port; - i2c::pca9548_select(self.busno, 0x70, mask as u8)?; - i2c::pca9548_select(self.busno, 0x71, (mask >> 8) as u8)?; + i2c::switch_select(self.busno, 0x70, mask as u8)?; + i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?; Ok(()) } diff --git a/artiq/firmware/libboard_misoc/io_expander.rs b/artiq/firmware/libboard_misoc/io_expander.rs index d2d2acd2f..19baf5364 100644 --- a/artiq/firmware/libboard_misoc/io_expander.rs +++ b/artiq/firmware/libboard_misoc/io_expander.rs @@ -43,8 +43,8 @@ impl IoExpander { #[cfg(soc_platform = "kasli")] fn select(&self) -> Result<(), &'static str> { let mask: u16 = 1 << self.port; - i2c::pca9548_select(self.busno, 0x70, mask as u8)?; - i2c::pca9548_select(self.busno, 0x71, (mask >> 8) as u8)?; + i2c::switch_select(self.busno, 0x70, mask as u8)?; + i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?; Ok(()) } diff --git a/artiq/firmware/libproto_artiq/drtioaux_proto.rs b/artiq/firmware/libproto_artiq/drtioaux_proto.rs index bd4875655..36e2cb45b 100644 --- a/artiq/firmware/libproto_artiq/drtioaux_proto.rs +++ b/artiq/firmware/libproto_artiq/drtioaux_proto.rs @@ -47,6 +47,7 @@ pub enum Packet { I2cReadRequest { destination: u8, busno: u8, ack: bool }, I2cReadReply { succeeded: bool, data: u8 }, 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 }, SpiWriteRequest { destination: u8, busno: u8, data: u32 }, @@ -154,6 +155,12 @@ impl Packet { 0x87 => Packet::I2cBasicReply { 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 { destination: reader.read_u8()?, @@ -313,6 +320,13 @@ impl Packet { writer.write_u8(0x87)?; 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 } => { writer.write_u8(0x90)?; diff --git a/artiq/firmware/libproto_artiq/kernel_proto.rs b/artiq/firmware/libproto_artiq/kernel_proto.rs index a16aaa09b..3021447ec 100644 --- a/artiq/firmware/libproto_artiq/kernel_proto.rs +++ b/artiq/firmware/libproto_artiq/kernel_proto.rs @@ -65,6 +65,7 @@ pub enum Message<'a> { I2cReadRequest { busno: u32, ack: bool }, I2cReadReply { succeeded: bool, data: u8 }, I2cBasicReply { succeeded: bool }, + I2cSwitchSelectRequest { busno: u32, address: u8, mask: u8 }, SpiSetConfigRequest { busno: u32, flags: u8, length: u8, div: u8, cs: u8 }, SpiWriteRequest { busno: u32, data: u32 }, diff --git a/artiq/firmware/runtime/kern_hwreq.rs b/artiq/firmware/runtime/kern_hwreq.rs index 53e0daa8a..a80a7b71a 100644 --- a/artiq/firmware/runtime/kern_hwreq.rs +++ b/artiq/firmware/runtime/kern_hwreq.rs @@ -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 { + 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)] @@ -259,6 +281,11 @@ pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex, 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 } => { let succeeded = dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno, diff --git a/artiq/firmware/satman/main.rs b/artiq/firmware/satman/main.rs index c6685f895..a99e3d5b3 100644 --- a/artiq/firmware/satman/main.rs +++ b/artiq/firmware/satman/main.rs @@ -275,6 +275,11 @@ fn process_aux_packet(_repeaters: &mut [repeater::Repeater], &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 } => { forward!(_routing_table, _destination, *_rank, _repeaters, &packet); diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 3530cf996..3d5b639ca 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -52,13 +52,13 @@ def process_header(output, description): "i2c_switch0": {{ "type": "local", "module": "artiq.coredevice.i2c", - "class": "PCA9548", + "class": "I2CSwitch", "arguments": {{"address": 0xe0}} }}, "i2c_switch1": {{ "type": "local", "module": "artiq.coredevice.i2c", - "class": "PCA9548", + "class": "I2CSwitch", "arguments": {{"address": 0xe2}} }}, }} diff --git a/artiq/test/coredevice/test_i2c.py b/artiq/test/coredevice/test_i2c.py index e5424efc6..7b589cb4e 100644 --- a/artiq/test/coredevice/test_i2c.py +++ b/artiq/test/coredevice/test_i2c.py @@ -3,10 +3,10 @@ import os, unittest from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase 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): self.setattr_device("core") self.setattr_device("i2c_switch") @@ -25,7 +25,7 @@ class NonexistentI2CBus(EnvExperiment): def build(self): self.setattr_device("core") 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 def run(self): @@ -34,7 +34,7 @@ class NonexistentI2CBus(EnvExperiment): class I2CTest(ExperimentCase): def test_i2c_switch(self): - self.execute(I2CSwitch) + self.execute(I2CSwitchTest) self.assertTrue(self.dataset_mgr.get("passed")) def test_nonexistent_bus(self):