Bitbang I2C

This commit is contained in:
Jeremy Soller
2020-12-02 10:52:08 -07:00
parent 2fea7dffde
commit 00c19d4295
3 changed files with 362 additions and 122 deletions

70
usb4/Cargo.lock generated
View File

@@ -1,29 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cc"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "coreboot-collector"
version = "0.1.0"
@@ -47,37 +23,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "i2cdev"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c0eb3d9b6b02dc2508ee23439170004e44344bab9d53a490eb1f64c885b5003"
dependencies = [
"bitflags",
"byteorder",
"libc",
"nix",
]
[[package]]
name = "libc"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
[[package]]
name = "nix"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"void",
]
[[package]]
name = "numtoa"
version = "0.2.3"
@@ -169,22 +120,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "usb4"
version = "0.1.0"
dependencies = [
"coreboot-collector",
]
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "you-ass-bee-see"
version = "0.1.0"
dependencies = [
"coreboot-collector",
"i2cdev",
]

View File

@@ -1,5 +1,5 @@
[package]
name = "you-ass-bee-see"
name = "usb4"
version = "0.1.0"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2018"
@@ -8,4 +8,3 @@ edition = "2018"
[dependencies]
coreboot-collector = { path = "../tools/coreboot-collector" }
i2cdev = "0.4.4"

View File

@@ -1,7 +1,11 @@
use coreboot_collector::sideband::Sideband;
use i2cdev::core::I2CDevice;
use i2cdev::linux::LinuxI2CDevice;
use std::{fs, thread, time};
use std::{
fs,
rc::Rc,
process,
thread,
time
};
const IECS_CMD: u8 = 8;
const IECS_DATA: u8 = 9;
@@ -13,71 +17,319 @@ const CMD_BLKW: u32 = 0x574b4c42;
const CMD_BOPS: u32 = 0x53504f42;
const CMD_PCYC: u32 = 0x43594350;
const GPIO_FORCE_POWER: (u8, u8) = (0x6E, 0x82); // GPP_A23
fn read<I: I2CDevice>(dev: &mut I, reg: u8) -> Result<u32, I::Error> {
let bytes = dev.smbus_read_block_data(reg)?;
//TODO: return error on bytes.len() != 4
#[repr(u64)]
pub enum GpioPadMode {
Gpio = 0 << 10,
Nf1 = 1 << 10,
Nf2 = 2 << 10,
Nf3 = 3 << 10,
Nf4 = 4 << 10,
Nf5 = 5 << 10,
Nf6 = 6 << 10,
Nf7 = 7 << 10,
}
pub struct Gpio {
//TODO: this should probably be locked
sideband: Rc<Sideband>,
port: u8,
pad: u8,
}
impl Gpio {
const PAD_MODE: u64 = 0b111 << 10;
const RX_DIS: u64 = 1 << 9;
const TX_DIS: u64 = 1 << 8;
const RX: u64 = 1 << 1;
const TX: u64 = 1 << 0;
pub fn new(sideband: Rc<Sideband>, port: u8, pad: u8) -> Self {
Self { sideband, port, pad }
}
unsafe fn get_config(&self) -> u64 {
self.sideband.gpio(self.port, self.pad)
}
unsafe fn set_config(&mut self, config: u64) {
self.sideband.set_gpio(self.port, self.pad, config);
}
unsafe fn get_mask(&self, mask: u64) -> bool {
self.get_config() & mask == mask
}
unsafe fn set_mask(&mut self, mask: u64, value: bool) {
let mut config = self.get_config();
if value {
config |= mask;
} else {
config &= !mask;
}
self.set_config(config);
}
pub unsafe fn set_pad_mode(&mut self, mode: GpioPadMode) {
let mut config = self.get_config();
config &= !Self::PAD_MODE;
config |= mode as u64;
self.set_config(config);
}
pub unsafe fn enable_rx(&mut self, value: bool) {
self.set_mask(Self::RX_DIS, !value);
}
pub unsafe fn enable_tx(&mut self, value: bool) {
self.set_mask(Self::TX_DIS, !value);
}
pub unsafe fn get_rx(&self) -> bool {
self.get_mask(Self::RX)
}
pub unsafe fn get_tx(&self) -> bool {
self.get_mask(Self::TX)
}
pub unsafe fn set_tx(&mut self, value: bool) {
self.set_mask(Self::TX, value);
}
}
pub struct I2CBitbang {
scl: Gpio,
scl_config: u64,
sda: Gpio,
sda_config: u64,
}
impl I2CBitbang {
pub unsafe fn new(mut scl: Gpio, mut sda: Gpio) -> Self {
//TODO: will this transmit something invalid?
let scl_config = scl.get_config();
scl.enable_rx(true);
scl.enable_tx(false);
scl.set_tx(false);
scl.set_pad_mode(GpioPadMode::Gpio);
let sda_config = sda.get_config();
sda.enable_rx(true);
sda.enable_tx(false);
sda.set_tx(false);
sda.set_pad_mode(GpioPadMode::Gpio);
Self { scl, scl_config, sda, sda_config, }
}
// Delay half half of period
fn delay(&self) {
// Hard coded to 5 us, which is half of the period 10 us for a frequency of 100 KHz
thread::sleep(time::Duration::from_micros(5));
}
// Pull SCL low
unsafe fn clr_scl(&mut self) {
self.scl.enable_tx(true);
}
// Release SCL, bus pulls it high
unsafe fn set_scl(&mut self) {
self.scl.enable_tx(false);
}
// Pull SDA low
unsafe fn clr_sda(&mut self) {
self.sda.enable_tx(true);
}
// Release SDA, bus pulls it high
unsafe fn set_sda(&mut self) {
self.sda.enable_tx(false);
}
unsafe fn start(&mut self) {
self.set_scl();
self.set_sda();
self.delay();
self.clr_sda();
self.delay();
self.clr_scl();
self.delay();
}
unsafe fn stop(&mut self) {
self.clr_sda();
self.delay();
self.set_scl();
self.delay();
self.set_sda();
self.delay();
}
unsafe fn write_bit(&mut self, bit: bool) {
if bit {
self.set_sda();
} else {
self.clr_sda();
}
self.delay();
self.set_scl();
self.delay();
self.clr_scl();
}
unsafe fn read_bit(&mut self) -> bool {
self.set_sda();
self.delay();
self.set_scl();
self.delay();
let bit = self.sda.get_rx();
self.clr_scl();
bit
}
unsafe fn write_byte(&mut self, byte: u8, start: bool) -> bool {
if start {
self.start();
}
for i in (0..8).rev() {
self.write_bit(byte & (1 << i) != 0);
}
self.read_bit()
}
unsafe fn read_byte(&mut self, ack: bool) -> u8 {
let mut byte = 0;
for i in (0..8).rev() {
if self.read_bit() {
byte |= 1 << i;
}
}
self.write_bit(!ack);
byte
}
pub unsafe fn smbus_block_write(&mut self, address: u8, command: u8, bytes: &[u8]) -> usize {
// Only 32 bytes can be processed at a time
if bytes.len() > 32 {
return 0;
}
let mut count = 0;
if self.write_byte(address << 1, true) {
if self.write_byte(command, false) {
if self.write_byte(bytes.len() as u8, false) {
for byte in bytes.iter() {
if self.write_byte(*byte, false) {
count += 1;
} else {
break;
}
}
}
}
}
self.stop();
count
}
pub unsafe fn smbus_block_read(&mut self, address: u8, command: u8) -> Vec<u8> {
//TODO: use static buffer?
let mut bytes = Vec::new();
if self.write_byte(address << 1, true) {
if self.write_byte(command, false) {
if self.write_byte(address << 1 | 1, true) {
let count = self.read_byte(true);
for _i in 0..count {
bytes.push(self.read_byte(true));
}
}
}
}
self.stop();
bytes
}
}
impl Drop for I2CBitbang {
fn drop(&mut self) {
unsafe {
//TODO: will this transmit something invalid?
self.scl.set_config(self.scl_config);
self.sda.set_config(self.sda_config);
}
}
}
pub struct Retimer {
i2c: I2CBitbang,
address: u8,
}
impl Retimer {
pub fn new(i2c: I2CBitbang, address: u8) -> Self {
Self { i2c, address }
}
pub unsafe fn read(&mut self, reg: u8) -> Result<u32, String> {
let bytes = self.i2c.smbus_block_read(self.address, reg);
if bytes.len() == 4 {
Ok(
bytes[0] as u32 |
(bytes[1] as u32) << 8 |
(bytes[2] as u32) << 16 |
(bytes[3] as u32) << 24
)
} else {
Err(format!("Retimer::read: read {} bytes instead of 4", bytes.len()))
}
}
fn write<I: I2CDevice>(dev: &mut I, reg: u8, data: u32) -> Result<(), I::Error> {
pub unsafe fn write(&mut self, reg: u8, data: u32) -> Result<(), String> {
let bytes = [
data as u8,
(data >> 8) as u8,
(data >> 16) as u8,
(data >> 24) as u8,
];
dev.smbus_write_block_data(reg, &bytes)
let count = self.i2c.smbus_block_write(self.address, reg, &bytes);
if count == 4 {
Ok(())
} else {
Err(format!("Retimer::write: wrote {} bytes instead of 4", count))
}
}
fn command<I: I2CDevice>(dev: &mut I, cmd: u32) -> Result<u32, I::Error> {
write(dev, IECS_CMD, cmd)?;
loop {
let status = read(dev, IECS_CMD)?;
pub unsafe fn command(&mut self, cmd: u32) -> Result<(), String> {
self.write(IECS_CMD, cmd)?;
//TODO: is this the right number of retries?
let retries = 1000;
for _i in 0..retries {
let status = self.read(IECS_CMD)?;
if status != cmd {
//TODO: perform error checking here
return Ok(status);
if status == 0 {
return Ok(());
} else {
return Err(format!("Retimer::command: read 0x{:X} instead of 0", status));
}
}
}
fn main() {
//TODO: check model
let sideband = unsafe { Sideband::new(0xFD00_0000).unwrap() };
// Set FORCE_POWER high
unsafe {
let (port, pad) = GPIO_FORCE_POWER;
let mut value = sideband.gpio(port, pad);
value |= 1;
sideband.set_gpio(port, pad, value);
Err(format!("Retimer::command: timed out after {} retries", retries))
}
}
// Sleep 40ms
thread::sleep(time::Duration::from_millis(40));
let mut dev = LinuxI2CDevice::new("/dev/i2c-11", 0x40).unwrap();
eprintln!("Vendor: {:X}", read(&mut dev, 0).unwrap());
eprintln!("Device: {:X}", read(&mut dev, 1).unwrap());
let image = fs::read("../models/galp5/usb4-retimer.rom").unwrap();
for i in 2..=32 {
println!("{}: {:X}", i, read(&mut dev, i).unwrap());
}
write(&mut dev, IECS_DATA, 0).unwrap();
println!("IECS_DATA: {:X}", read(&mut dev, IECS_DATA).unwrap());
unsafe fn flash_retimer(retimer: &mut Retimer) -> Result<(), String> {
eprintln!("Vendor: {:X}", retimer.read(0)?);
eprintln!("Device: {:X}", retimer.read(1)?);
/*
let image = fs::read("../models/galp5/usb4-retimer.rom").unwrap();
eprintln!("Set offset to 0");
write(&mut dev, IECS_DATA, 0).unwrap();
let status = command(&mut dev, CMD_BOPS).unwrap();
@@ -125,11 +377,56 @@ fn main() {
eprintln!("Successfully flashed retimer");
*/
// Set FORCE_POWER low
Ok(())
}
unsafe fn retimer_access(i2c: I2CBitbang) -> i32 {
let mut retimer = Retimer::new(i2c, 0x40);
match flash_retimer(&mut retimer) {
Ok(()) => 0,
Err(err) => {
eprintln!("Failed to flash retimer: {}", err);
1
}
}
}
unsafe fn i2c_access(sideband: Rc<Sideband>) -> i32 {
let scl = Gpio::new(sideband.clone(), 0x6A, 0x06); // GPP_C3
let sda = Gpio::new(sideband.clone(), 0x6A, 0x08); // GPP_C4
let i2c = I2CBitbang::new(scl, sda);
retimer_access(i2c)
}
unsafe fn force_power(sideband: Rc<Sideband>) -> i32 {
let mut force_power = Gpio::new(sideband.clone(), 0x6E, 0x82); // GPP_A23
println!("Set FORCE_POWER high");
force_power.set_tx(true);
println!("Sleep 40 ms");
thread::sleep(time::Duration::from_millis(40));
let exit_status = i2c_access(sideband);
eprintln!("Set FORCE_POWER low");
force_power.set_tx(false);
exit_status
}
fn main() {
//TODO: check model
unsafe {
let (port, pad) = GPIO_FORCE_POWER;
let mut value = sideband.gpio(port, pad);
value &= !1;
sideband.set_gpio(port, pad, value);
let sideband = match Sideband::new(0xFD00_0000) {
Ok(ok) => Rc::new(ok),
Err(err) => {
eprintln!("Failed to access sideband: {}", err);
process::exit(1);
}
};
process::exit(force_power(sideband));
}
}