From 082fdaf45056c0ae1a4e2ab74786479b1c26171d Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 4 Jan 2017 21:04:38 +0100 Subject: [PATCH] move i2c to libboard, do bit-banging on comms CPU --- artiq/coredevice/exceptions.py | 4 - artiq/coredevice/i2c.py | 10 +- artiq/firmware/libboard/i2c.rs | 136 ++++++++++++++ artiq/firmware/libboard/lib.rs | 2 + artiq/firmware/libksupport/api.rs | 14 +- artiq/firmware/libksupport/i2c.rs | 176 ------------------- artiq/firmware/libksupport/lib.rs | 20 ++- artiq/firmware/runtime/kernel_proto.rs | 7 + artiq/firmware/runtime/lib.rs | 2 + artiq/firmware/runtime/session.rs | 38 ++++ artiq/gateware/soc.py | 3 - artiq/gateware/targets/kc705.py | 4 +- artiq/gateware/targets/kc705_drtio_master.py | 1 - artiq/gateware/targets/phaser.py | 4 +- artiq/gateware/targets/pipistrello.py | 1 - 15 files changed, 212 insertions(+), 210 deletions(-) create mode 100644 artiq/firmware/libboard/i2c.rs delete mode 100644 artiq/firmware/libksupport/i2c.rs diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py index 5ba1c5f43..58c07fec3 100644 --- a/artiq/coredevice/exceptions.py +++ b/artiq/coredevice/exceptions.py @@ -126,10 +126,6 @@ class DDSError(Exception): incorrect. """ -class I2CError(Exception): - """Raised with a I2C transaction fails.""" - artiq_builtin = True - class WatchdogExpired(Exception): """Raised when a watchdog expires.""" diff --git a/artiq/coredevice/i2c.py b/artiq/coredevice/i2c.py index da3fa6269..51b2b4e33 100644 --- a/artiq/coredevice/i2c.py +++ b/artiq/coredevice/i2c.py @@ -1,11 +1,10 @@ from artiq.language.core import syscall, kernel from artiq.language.types import TBool, TInt32, TNone -from artiq.coredevice.exceptions import I2CError -@syscall(flags={"nowrite"}) -def i2c_init(busno: TInt32) -> TNone: - raise NotImplementedError("syscall not simulated") +class I2CError(Exception): + """Raised with a I2C transaction fails.""" + pass @syscall(flags={"nounwind", "nowrite"}) @@ -48,7 +47,6 @@ class PCA9548: :param channel: channel number (0-7) """ - i2c_init(self.busno) i2c_start(self.busno) try: if not i2c_write(self.busno, self.address): @@ -60,7 +58,6 @@ class PCA9548: @kernel def readback(self): - i2c_init(self.busno) i2c_start(self.busno) r = 0 try: @@ -84,7 +81,6 @@ class TCA6424A: @kernel def _write24(self, command, value): - i2c_init(self.busno) i2c_start(self.busno) try: if not i2c_write(self.busno, self.address): diff --git a/artiq/firmware/libboard/i2c.rs b/artiq/firmware/libboard/i2c.rs new file mode 100644 index 000000000..1c6b8181e --- /dev/null +++ b/artiq/firmware/libboard/i2c.rs @@ -0,0 +1,136 @@ +use csr; +use clock; + +fn half_period() { clock::spin_us(100) } +fn sda_bit(busno: u32) -> u8 { 1 << (2 * busno + 1) } +fn scl_bit(busno: u32) -> u8 { 1 << (2 * busno) } + +fn sda_i(busno: u32) -> bool { + unsafe { + csr::i2c::in_read() & sda_bit(busno) != 0 + } +} + +fn sda_oe(busno: u32, oe: bool) { + unsafe { + let reg = csr::i2c::oe_read(); + let reg = if oe { reg | sda_bit(busno) } else { reg & !sda_bit(busno) }; + csr::i2c::oe_write(reg) + } +} + +fn sda_o(busno: u32, o: bool) { + unsafe { + let reg = csr::i2c::out_read(); + let reg = if o { reg | sda_bit(busno) } else { reg & !sda_bit(busno) }; + csr::i2c::out_write(reg) + } +} + +fn scl_oe(busno: u32, oe: bool) { + unsafe { + let reg = csr::i2c::oe_read(); + let reg = if oe { reg | scl_bit(busno) } else { reg & !scl_bit(busno) }; + csr::i2c::oe_write(reg) + } +} + +fn scl_o(busno: u32, o: bool) { + unsafe { + let reg = csr::i2c::out_read(); + let reg = if o { reg | scl_bit(busno) } else { reg & !scl_bit(busno) }; + csr::i2c::out_write(reg) + } +} + +pub fn init() { + for busno in 0..csr::CONFIG_I2C_BUS_COUNT { + // Set SCL as output, and high level + scl_o(busno, true); + scl_oe(busno, true); + // Prepare a zero level on SDA so that sda_oe pulls it down + sda_o(busno, false); + // Release SDA + sda_oe(busno, false); + + // Check the I2C bus is ready + half_period(); + half_period(); + if !sda_i(busno) { + error!("SDA is stuck low on bus #{}", busno) + } + } +} + +pub fn start(busno: u32) { + // Set SCL high then SDA low + scl_o(busno, true); + half_period(); + sda_oe(busno, true); + half_period(); +} + +pub fn stop(busno: u32) { + // First, make sure SCL is low, so that the target releases the SDA line + scl_o(busno, false); + half_period(); + // Set SCL high then SDA high + sda_oe(busno, true); + scl_o(busno, true); + half_period(); + sda_oe(busno, false); + half_period(); +} + +pub fn write(busno: u32, data: u8) -> bool { + // MSB first + for bit in (0..8).rev() { + // Set SCL low and set our bit on SDA + scl_o(busno, false); + sda_oe(busno, data & (1 << bit) == 0); + half_period(); + // Set SCL high ; data is shifted on the rising edge of SCL + scl_o(busno, true); + half_period(); + } + // Check ack + // Set SCL low, then release SDA so that the I2C target can respond + scl_o(busno, false); + half_period(); + sda_oe(busno, false); + // Set SCL high and check for ack + scl_o(busno, true); + half_period(); + // returns true if acked (I2C target pulled SDA low) + !sda_i(busno) +} + +pub fn read(busno: u32, ack: bool) -> u8 { + // Set SCL low first, otherwise setting SDA as input may cause a transition + // on SDA with SCL high which will be interpreted as START/STOP condition. + scl_o(busno, false); + half_period(); // make sure SCL has settled low + sda_oe(busno, false); + + let mut data: u8 = 0; + + // MSB first + for bit in (0..8).rev() { + scl_o(busno, false); + half_period(); + // Set SCL high and shift data + scl_o(busno, true); + half_period(); + if sda_i(busno) { data |= 1 << bit } + } + // Send ack + // Set SCL low and pull SDA low when acking + scl_o(busno, false); + if ack { sda_oe(busno, true) } + half_period(); + // then set SCL high + scl_o(busno, true); + half_period(); + + data +} diff --git a/artiq/firmware/libboard/lib.rs b/artiq/firmware/libboard/lib.rs index bf865efb9..24e5eab6c 100644 --- a/artiq/firmware/libboard/lib.rs +++ b/artiq/firmware/libboard/lib.rs @@ -11,6 +11,8 @@ include!(concat!(env!("BUILDINC_DIRECTORY"), "/generated/csr.rs")); pub mod spr; pub mod irq; pub mod clock; +#[cfg(has_i2c)] +pub mod i2c; #[cfg(has_ad9516)] #[allow(dead_code)] mod ad9516_reg; diff --git a/artiq/firmware/libksupport/api.rs b/artiq/firmware/libksupport/api.rs index 00c6ccec1..5ca10b15a 100644 --- a/artiq/firmware/libksupport/api.rs +++ b/artiq/firmware/libksupport/api.rs @@ -106,14 +106,8 @@ static mut API: &'static [(&'static str, *const ())] = &[ api!(rtio_input_timestamp = ::rtio::input_timestamp), api!(rtio_input_data = ::rtio::input_data), - #[cfg(has_i2c)] - api!(i2c_init = ::i2c::init), - #[cfg(has_i2c)] - api!(i2c_start = ::i2c::start), - #[cfg(has_i2c)] - api!(i2c_stop = ::i2c::stop), - #[cfg(has_i2c)] - api!(i2c_write = ::i2c::write), - #[cfg(has_i2c)] - api!(i2c_read = ::i2c::read), + api!(i2c_start = ::i2c_start), + api!(i2c_stop = ::i2c_stop), + api!(i2c_write = ::i2c_write), + api!(i2c_read = ::i2c_read), ]; diff --git a/artiq/firmware/libksupport/i2c.rs b/artiq/firmware/libksupport/i2c.rs deleted file mode 100644 index 96dedcabe..000000000 --- a/artiq/firmware/libksupport/i2c.rs +++ /dev/null @@ -1,176 +0,0 @@ -use board::csr; - -fn half_period() { - unsafe { - csr::timer_kernel::en_write(0); - csr::timer_kernel::load_write(csr::CONFIG_CLOCK_FREQUENCY/10000); - csr::timer_kernel::reload_write(0); - csr::timer_kernel::en_write(1); - - csr::timer_kernel::update_value_write(1); - while csr::timer_kernel::value_read() != 0 { - csr::timer_kernel::update_value_write(1) - } - } -} - -#[cfg(has_i2c)] -mod imp { - use board::csr; - - fn sda_bit(busno: u32) -> u32 { 1 << (2 * busno + 1) } - fn scl_bit(busno: u32) -> u32 { 1 << (2 * busno) } - - pub fn sda_i(busno: u32) -> bool { - unsafe { - if busno >= csr::CONFIG_I2C_BUS_COUNT { - true - } else { - csr::i2c::in_read() & sda_bit(busno) != 0 - } - } - } - - pub fn sda_oe(busno: u32, oe: bool) { - unsafe { - let reg = csr::i2c::oe_read(); - let reg = if oe { reg | sda_bit(busno) } else { reg & !sda_bit(busno) }; - csr::i2c::oe_write(reg); - } - } - - pub fn sda_o(busno: u32, o: bool) { - unsafe { - let reg = csr::i2c::out_read(); - let reg = if o { reg | sda_bit(busno) } else { reg & !sda_bit(busno) }; - csr::i2c::out_write(reg) - } - } - - pub fn scl_oe(busno: u32, oe: bool) { - unsafe { - let reg = csr::i2c::oe_read(); - let reg = if oe { reg | scl_bit(busno) } else { reg & !scl_bit(busno) }; - csr::i2c::oe_write(reg) - } - } - - pub fn scl_o(busno: u32, o: bool) { - unsafe { - let reg = csr::i2c::out_read(); - let reg = if o { reg | scl_bit(busno) } else { reg & !scl_bit(busno) }; - csr::i2c::out_write(reg) - } - } -} - -#[cfg(not(has_i2c))] -mod imp { - pub fn sda_i(busno: u32) -> bool { true } - pub fn sda_oe(busno: u32, oe: bool) {} - pub fn sda_o(busno: u32, o: bool) {} - pub fn scl_oe(busno: u32, oe: bool) {} - pub fn scl_o(busno: u32, o: bool) {} -} - -use self::imp::*; - -pub extern fn init(busno: i32) { - let busno = busno as u32; - - // Set SCL as output, and high level - scl_o(busno, true); - scl_oe(busno, true); - // Prepare a zero level on SDA so that sda_oe pulls it down - sda_o(busno, false); - // Release SDA - sda_oe(busno, false); - - // Check the I2C bus is ready - half_period(); - half_period(); - if !sda_i(busno) { - artiq_raise!("I2CError", "SDA is stuck low") - } -} - -pub extern fn start(busno: i32) { - let busno = busno as u32; - - // Set SCL high then SDA low - scl_o(busno, true); - half_period(); - sda_oe(busno, true); - half_period(); -} - -pub extern fn stop(busno: i32) { - let busno = busno as u32; - - // First, make sure SCL is low, so that the target releases the SDA line - scl_o(busno, false); - half_period(); - // Set SCL high then SDA high - sda_oe(busno, true); - scl_o(busno, true); - half_period(); - sda_oe(busno, false); - half_period(); -} - -pub extern fn write(busno: i32, data: i8) -> bool { - let (busno, data) = (busno as u32, data as u8); - - // MSB first - for bit in (0..8).rev() { - // Set SCL low and set our bit on SDA - scl_o(busno, false); - sda_oe(busno, data & (1 << bit) == 0); - half_period(); - // Set SCL high ; data is shifted on the rising edge of SCL - scl_o(busno, true); - half_period(); - } - // Check ack - // Set SCL low, then release SDA so that the I2C target can respond - scl_o(busno, false); - half_period(); - sda_oe(busno, false); - // Set SCL high and check for ack - scl_o(busno, true); - half_period(); - // returns true if acked (I2C target pulled SDA low) - !sda_i(busno) -} - -pub extern fn read(busno: i32, ack: bool) -> i8 { - let busno = busno as u32; - - // Set SCL low first, otherwise setting SDA as input may cause a transition - // on SDA with SCL high which will be interpreted as START/STOP condition. - scl_o(busno, false); - half_period(); // make sure SCL has settled low - sda_oe(busno, false); - - let mut data: u8 = 0; - - // MSB first - for bit in (0..8).rev() { - scl_o(busno, false); - half_period(); - // Set SCL high and shift data - scl_o(busno, true); - half_period(); - if sda_i(busno) { data |= 1 << bit } - } - // Send ack - // Set SCL low and pull SDA low when acking - scl_o(busno, false); - if ack { sda_oe(busno, true) } - half_period(); - // then set SCL high - scl_o(busno, true); - half_period(); - - data as i8 -} diff --git a/artiq/firmware/libksupport/lib.rs b/artiq/firmware/libksupport/lib.rs index 168df9e55..dbca35b54 100644 --- a/artiq/firmware/libksupport/lib.rs +++ b/artiq/firmware/libksupport/lib.rs @@ -48,8 +48,6 @@ macro_rules! artiq_raise { } mod rtio; -#[cfg(has_i2c)] -mod i2c; use core::{mem, ptr, slice, str}; use std::io::Cursor; @@ -243,6 +241,24 @@ extern fn cache_put(key: *const u8, list: ArtiqList) { }) } +extern fn i2c_start(busno: i32) { + send(&I2CStartRequest { busno: busno as u32 }); +} + +extern fn i2c_stop(busno: i32) { + send(&I2CStopRequest { busno: busno as u32 }); +} + +extern fn i2c_write(busno: i32, data: i8) -> bool { + send(&I2CWriteRequest { busno: busno as u32, data: data as u8 }); + recv!(&I2CWriteReply { ack } => ack) +} + +extern fn i2c_read(busno: i32, ack: bool) -> i8 { + send(&I2CReadRequest { busno: busno as u32, ack: ack }); + recv!(&I2CReadReply { data } => data) as i8 +} + unsafe fn attribute_writeback(typeinfo: *const ()) { struct Attr { offset: usize, diff --git a/artiq/firmware/runtime/kernel_proto.rs b/artiq/firmware/runtime/kernel_proto.rs index f8d5f8d3e..d32750648 100644 --- a/artiq/firmware/runtime/kernel_proto.rs +++ b/artiq/firmware/runtime/kernel_proto.rs @@ -57,6 +57,13 @@ pub enum Message<'a> { CachePutRequest { key: &'a str, value: &'a [i32] }, CachePutReply { succeeded: bool }, + I2CStartRequest { busno: u32 }, + I2CStopRequest { busno: u32 }, + I2CWriteRequest { busno: u32, data: u8 }, + I2CWriteReply { ack: bool }, + I2CReadRequest { busno: u32, ack: bool }, + I2CReadReply { data: u8 }, + Log(fmt::Arguments<'a>), LogSlice(&'a str) } diff --git a/artiq/firmware/runtime/lib.rs b/artiq/firmware/runtime/lib.rs index 8ba424d4d..71d215814 100644 --- a/artiq/firmware/runtime/lib.rs +++ b/artiq/firmware/runtime/lib.rs @@ -120,6 +120,8 @@ pub unsafe extern fn rust_main() { } info!("continuing boot"); + #[cfg(has_i2c)] + board::i2c::init(); #[cfg(has_ad9516)] board::ad9516::init().unwrap(); #[cfg(has_converter_spi)] diff --git a/artiq/firmware/runtime/session.rs b/artiq/firmware/runtime/session.rs index 52617ef08..cceab0500 100644 --- a/artiq/firmware/runtime/session.rs +++ b/artiq/firmware/runtime/session.rs @@ -422,6 +422,44 @@ fn process_kern_message(waiter: Waiter, kern_send(waiter, &kern::CachePutReply { succeeded: succeeded }) } + #[cfg(has_i2c)] + &kern::I2CStartRequest { busno } => { + board::i2c::start(busno); + kern_acknowledge() + } + #[cfg(has_i2c)] + &kern::I2CStopRequest { busno } => { + board::i2c::stop(busno); + kern_acknowledge() + } + #[cfg(has_i2c)] + &kern::I2CWriteRequest { busno, data } => { + let ack = board::i2c::write(busno, data); + kern_send(waiter, &kern::I2CWriteReply { ack: ack }) + } + #[cfg(has_i2c)] + &kern::I2CReadRequest { busno, ack } => { + let data = board::i2c::read(busno, ack); + kern_send(waiter, &kern::I2CReadReply { data: data }) + } + + #[cfg(not(has_i2c))] + &kern::I2CStartRequest { .. } => { + kern_acknowledge() + } + #[cfg(not(has_i2c))] + &kern::I2CStopRequest { .. } => { + kern_acknowledge() + } + #[cfg(not(has_i2c))] + &kern::I2CWriteRequest { .. } => { + kern_send(waiter, &kern::I2CWriteReply { ack: false }) + } + #[cfg(not(has_i2c))] + &kern::I2CReadRequest { .. } => { + kern_send(waiter, &kern::I2CReadReply { data: 0xff }) + } + &kern::RunFinished => { unsafe { kernel::stop() } session.kernel_state = KernelState::Absent; diff --git a/artiq/gateware/soc.py b/artiq/gateware/soc.py index 73fd412a7..0be283570 100644 --- a/artiq/gateware/soc.py +++ b/artiq/gateware/soc.py @@ -36,9 +36,6 @@ class AMPSoC: self.add_memory_region("mailbox", self.mem_map["mailbox"] | 0x80000000, 4) - self.submodules.timer_kernel = timer.Timer() - self.register_kernel_cpu_csrdevice("timer_kernel") - def register_kernel_cpu_csrdevice(self, name, csrs=None): if csrs is None: csrs = getattr(self, name).get_csrs() diff --git a/artiq/gateware/targets/kc705.py b/artiq/gateware/targets/kc705.py index 16abb050a..8d3bd855d 100755 --- a/artiq/gateware/targets/kc705.py +++ b/artiq/gateware/targets/kc705.py @@ -100,10 +100,8 @@ _ams101_dac = [ class _NIST_Ions(MiniSoC, AMPSoC): mem_map = { - "timer_kernel": 0x10000000, "rtio": 0x20000000, "rtio_dma": 0x30000000, - "i2c": 0x50000000, "mailbox": 0x70000000 } mem_map.update(MiniSoC.mem_map) @@ -134,7 +132,7 @@ class _NIST_Ions(MiniSoC, AMPSoC): i2c = self.platform.request("i2c") self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) - self.register_kernel_cpu_csrdevice("i2c") + self.csr_devices.append("i2c") self.config["I2C_BUS_COUNT"] = 1 self.config["HAS_DDS"] = None diff --git a/artiq/gateware/targets/kc705_drtio_master.py b/artiq/gateware/targets/kc705_drtio_master.py index 66d0fe64e..6628b221f 100755 --- a/artiq/gateware/targets/kc705_drtio_master.py +++ b/artiq/gateware/targets/kc705_drtio_master.py @@ -20,7 +20,6 @@ from artiq import __version__ as artiq_version class Master(MiniSoC, AMPSoC): mem_map = { - "timer_kernel": 0x10000000, "rtio": 0x20000000, "rtio_dma": 0x30000000, "drtio_aux": 0x50000000, diff --git a/artiq/gateware/targets/phaser.py b/artiq/gateware/targets/phaser.py index 83362ee72..a8b9a7841 100755 --- a/artiq/gateware/targets/phaser.py +++ b/artiq/gateware/targets/phaser.py @@ -156,10 +156,8 @@ class AD9154(Module, AutoCSR): class Phaser(MiniSoC, AMPSoC): mem_map = { - "timer_kernel": 0x10000000, "rtio": 0x20000000, # "rtio_dma": 0x30000000, - "i2c": 0x30000000, "mailbox": 0x70000000, "ad9154": 0x50000000, } @@ -188,7 +186,7 @@ class Phaser(MiniSoC, AMPSoC): i2c = platform.request("i2c") self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) - self.register_kernel_cpu_csrdevice("i2c") + self.csr_devices.append("i2c") self.config["I2C_BUS_COUNT"] = 1 ad9154_spi = platform.request("ad9154_spi") diff --git a/artiq/gateware/targets/pipistrello.py b/artiq/gateware/targets/pipistrello.py index 7df20bff8..503144070 100755 --- a/artiq/gateware/targets/pipistrello.py +++ b/artiq/gateware/targets/pipistrello.py @@ -149,7 +149,6 @@ _ttl_io = [ class Demo(BaseSoC, AMPSoC): mem_map = { - "timer_kernel": 0x10000000, # (shadow @0x90000000) "rtio": 0x20000000, # (shadow @0xa0000000) "mailbox": 0x70000000 # (shadow @0xf0000000) }