diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 31f4cdbb9..64f19d4f0 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -147,7 +147,7 @@ max-sizes = {flash = 65536, ram = 16384 } stacksize = 3800 start = true task-slots = ["i2c_driver", "sensor", "gimlet_seq"] -notifications = ["timer", "external_badness"] +notifications = ["timer"] [tasks.hiffy] name = "task-hiffy" diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 5d00583a3..d2b57dede 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -259,3 +259,28 @@ pins = [ # SPI6 CS repurposed for debugging { name = "DEBUG", pin = { port = "G", pin = 8, af = 0, direction = "output"}} ] + +# +# This device definition is only provided as a way to test this driver absent +# a larger board that has it; unless your Gimletlet is in a basement known +# to be roamed by a certain board-killing cat, it's unlikely to have this +# device attached! (But other than the `power` task reporting it as being +# absent, there should be no harm to it being missing.) +# +[[config.i2c.devices]] +device = "lm5066" +controller = 4 +address = 0x16 +description = "LM5066 evaluation board" +power = { rails = [ "LM5066_EVL_VOUT" ] } +sensors = { voltage = 1, current = 1, temperature = 1 } + +[tasks.power] +name = "task-power" +priority = 6 +max-sizes = {flash = 65536, ram = 16384 } +stacksize = 3800 +start = true +task-slots = ["i2c_driver", "sensor"] +notifications = ["timer"] + diff --git a/drv/i2c-devices/src/adm1272.rs b/drv/i2c-devices/src/adm1272.rs index 9fdb3d276..b2d05dcde 100644 --- a/drv/i2c-devices/src/adm1272.rs +++ b/drv/i2c-devices/src/adm1272.rs @@ -47,7 +47,9 @@ impl From for ResponseCode { Error::BadRead { code, .. } => code, Error::BadWrite { code, .. } => code, Error::BadValidation { code, .. } => code, - _ => panic!(), + Error::BadData { .. } + | Error::InvalidData { .. } + | Error::InvalidConfig => ResponseCode::BadDeviceState, } } } @@ -85,7 +87,7 @@ enum Trace { None, } -ringbuf!(Trace, 32, Trace::None); +ringbuf!(Trace, 8, Trace::None); impl Adm1272 { pub fn new(device: &I2cDevice, rsense: Ohms) -> Self { diff --git a/drv/i2c-devices/src/adt7420.rs b/drv/i2c-devices/src/adt7420.rs index 1c72ad68c..f4dc69c04 100644 --- a/drv/i2c-devices/src/adt7420.rs +++ b/drv/i2c-devices/src/adt7420.rs @@ -39,7 +39,7 @@ impl From for ResponseCode { match err { Error::BadValidate { code } => code, Error::BadTempRead { code } => code, - _ => panic!(), + Error::BadID { .. } => ResponseCode::BadDeviceState, } } } diff --git a/drv/i2c-devices/src/bmr491.rs b/drv/i2c-devices/src/bmr491.rs index a6470b510..defbcd64c 100644 --- a/drv/i2c-devices/src/bmr491.rs +++ b/drv/i2c-devices/src/bmr491.rs @@ -21,10 +21,19 @@ pub struct Bmr491 { #[derive(Debug)] pub enum Error { + /// I2C error on PMBus read from device BadRead { cmd: u8, code: ResponseCode }, + + /// I2C error on PMBus write to device BadWrite { cmd: u8, code: ResponseCode }, + + /// Failed to parse PMBus data from device BadData { cmd: u8 }, + + /// I2C error attempting to validate device BadValidation { cmd: u8, code: ResponseCode }, + + /// PMBus data returned from device is invalid InvalidData { err: pmbus::Error }, } @@ -49,7 +58,9 @@ impl From for ResponseCode { Error::BadRead { code, .. } => code, Error::BadWrite { code, .. } => code, Error::BadValidation { code, .. } => code, - _ => panic!(), + Error::BadData { .. } | Error::InvalidData { .. } => { + ResponseCode::BadDeviceState + } } } } diff --git a/drv/i2c-devices/src/isl68224.rs b/drv/i2c-devices/src/isl68224.rs index 8be08db8d..dbc4c9772 100644 --- a/drv/i2c-devices/src/isl68224.rs +++ b/drv/i2c-devices/src/isl68224.rs @@ -34,10 +34,19 @@ impl core::fmt::Display for Isl68224 { #[derive(Debug)] pub enum Error { + /// I2C error on PMBus read from device BadRead { cmd: u8, code: ResponseCode }, + + /// I2C error on PMBus write to device BadWrite { cmd: u8, code: ResponseCode }, + + /// Failed to parse PMBus data from device BadData { cmd: u8 }, + + /// I2C error attempting to validate device BadValidation { cmd: u8, code: ResponseCode }, + + /// PMBus data returned from device is invalid InvalidData { err: pmbus::Error }, } @@ -56,7 +65,9 @@ impl From for ResponseCode { Error::BadRead { code, .. } => code, Error::BadWrite { code, .. } => code, Error::BadValidation { code, .. } => code, - _ => panic!(), + Error::BadData { .. } | Error::InvalidData { .. } => { + ResponseCode::BadDeviceState + } } } } diff --git a/drv/i2c-devices/src/lib.rs b/drv/i2c-devices/src/lib.rs index a9b2d2640..f35c4c3b4 100644 --- a/drv/i2c-devices/src/lib.rs +++ b/drv/i2c-devices/src/lib.rs @@ -12,6 +12,7 @@ //! - [`ds2482`]: DS2482-100 1-wire initiator //! - [`emc2305`]: EMC2305 fan driver //! - [`isl68224`]: ISL68224 power controller +//! - [`lm5066`]: LM5066 hot swap controller //! - [`ltc4282`]: LTC4282 high current hot swap controller //! - [`m24c02`]: M24C02 EEPROM, used in MWOCP68 power shelf //! - [`m2_hp_only`]: M.2 drive; identical to `nvme_bmc`, with the limitation @@ -238,6 +239,7 @@ pub mod bmr491; pub mod ds2482; pub mod emc2305; pub mod isl68224; +pub mod lm5066; pub mod ltc4282; pub mod m24c02; pub mod m2_hp_only; diff --git a/drv/i2c-devices/src/lm5066.rs b/drv/i2c-devices/src/lm5066.rs new file mode 100644 index 000000000..91654a14e --- /dev/null +++ b/drv/i2c-devices/src/lm5066.rs @@ -0,0 +1,246 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Driver for the LM5066 hot-swap controller + +use core::cell::Cell; + +use crate::{ + pmbus_validate, BadValidation, CurrentSensor, TempSensor, Validate, + VoltageSensor, +}; +use drv_i2c_api::*; +use num_traits::float::FloatCore; +use pmbus::commands::*; +use ringbuf::*; +use userlib::units::*; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + /// I2C error on PMBus read from device + BadRead { cmd: u8, code: ResponseCode }, + + /// I2C error on PMBus write to device + BadWrite { cmd: u8, code: ResponseCode }, + + /// Failed to parse PMBus data from device + BadData { cmd: u8 }, + + /// I2C error attempting to validate device + BadValidation { cmd: u8, code: ResponseCode }, + + /// PMBus data returned from device is invalid + InvalidData { err: pmbus::Error }, + + /// Device setup is invalid + InvalidDeviceSetup, +} + +impl From for Error { + fn from(value: BadValidation) -> Self { + Self::BadValidation { + cmd: value.cmd, + code: value.code, + } + } +} + +impl From for Error { + fn from(err: pmbus::Error) -> Self { + Error::InvalidData { err } + } +} + +impl From for ResponseCode { + fn from(err: Error) -> Self { + match err { + Error::BadRead { code, .. } => code, + Error::BadWrite { code, .. } => code, + Error::BadValidation { code, .. } => code, + Error::BadData { .. } + | Error::InvalidData { .. } + | Error::InvalidDeviceSetup => ResponseCode::BadDeviceState, + } + } +} + +#[derive(Copy, Clone)] +#[allow(dead_code)] +struct Coefficients { + current: pmbus::Coefficients, + power: pmbus::Coefficients, +} + +#[derive(Copy, Clone)] +pub enum CurrentLimitStrap { + /// CL pin is strapped to VDD, denoting the Low setting + VDD, + /// CL pin is strapped to GND (or left floating), denoting the high setting + GND, +} + +pub struct Lm5066 { + /// Underlying I2C device + device: I2cDevice, + /// Value of the rsense resistor, in milliohms + rsense: i32, + /// Sense of current limit pin + cl: CurrentLimitStrap, + /// Our (cached) coefficients + coefficients: Cell>, + /// Our (cached) device setup + device_setup: Cell>, +} + +impl core::fmt::Display for Lm5066 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "lm5066: {}", &self.device) + } +} + +#[derive(Copy, Clone, PartialEq)] +enum Trace { + CurrentCoefficients(pmbus::Coefficients), + PowerCoefficients(pmbus::Coefficients), + DeviceSetup(lm5066::DEVICE_SETUP::CommandData), + None, +} + +ringbuf!(Trace, 8, Trace::None); + +impl Lm5066 { + pub fn new( + device: &I2cDevice, + rsense: Ohms, + cl: CurrentLimitStrap, + ) -> Self { + Self { + device: *device, + rsense: (rsense.0 * 1000.0).round() as i32, + cl, + coefficients: Cell::new(None), + device_setup: Cell::new(None), + } + } + + fn read_device_setup( + &self, + ) -> Result { + if let Some(ref device_setup) = self.device_setup.get() { + return Ok(*device_setup); + } + + let device_setup = pmbus_read!(self.device, lm5066::DEVICE_SETUP)?; + ringbuf_entry!(Trace::DeviceSetup(device_setup)); + self.device_setup.set(Some(device_setup)); + + Ok(device_setup) + } + + /// + /// The coefficients for the LM5066 depend on the value of the current + /// sense resistor and the sense of the current limit (CL) strap. + /// Unfortunately, DEVICE_SETUP will not tell us the physical sense of + /// this strap; we rely on this information to be provided when the + /// device is initialized. + /// + fn load_coefficients(&self) -> Result { + use lm5066::DEVICE_SETUP::*; + + if let Some(coefficients) = self.coefficients.get() { + return Ok(coefficients); + } + + let device_setup = self.read_device_setup()?; + + let setting = device_setup + .get_current_setting() + .ok_or(Error::InvalidDeviceSetup)?; + + let config = device_setup + .get_current_config() + .ok_or(Error::InvalidDeviceSetup)?; + + let cl = match config { + CurrentConfig::Pin => self.cl, + CurrentConfig::SMBus => match setting { + CurrentSetting::Low => CurrentLimitStrap::VDD, + CurrentSetting::High => CurrentLimitStrap::GND, + }, + }; + + // + // From Table 43 of the LM5066 datasheet. Note that the datasheet has + // an admonishment about adjusting R to keep m to within a signed + // 16-bit quantity (that is, no larger than 32767), but we actually + // treat m as a 32-bit quantity so there is no need to clamp it here. + // (At the maximum of 200 mΩ, m is well within a 32-bit quantity.) + // + let current = match cl { + CurrentLimitStrap::GND => pmbus::Coefficients { + m: 5405 * self.rsense, + b: -600, + R: -2, + }, + CurrentLimitStrap::VDD => pmbus::Coefficients { + m: 10753 * self.rsense, + b: -1200, + R: -2, + }, + }; + + ringbuf_entry!(Trace::CurrentCoefficients(current)); + + let power = match cl { + CurrentLimitStrap::GND => pmbus::Coefficients { + m: 605 * self.rsense, + b: -8000, + R: -3, + }, + CurrentLimitStrap::VDD => pmbus::Coefficients { + m: 1204 * self.rsense, + b: -6000, + R: -3, + }, + }; + + ringbuf_entry!(Trace::PowerCoefficients(power)); + + self.coefficients.set(Some(Coefficients { current, power })); + Ok(self.coefficients.get().unwrap()) + } + + pub fn i2c_device(&self) -> &I2cDevice { + &self.device + } +} + +impl Validate for Lm5066 { + fn validate(device: &I2cDevice) -> Result { + let expected = b"LM5066\0\0"; + pmbus_validate(device, CommandCode::MFR_MODEL, expected) + .map_err(Into::into) + } +} + +impl TempSensor for Lm5066 { + fn read_temperature(&self) -> Result { + let temp = pmbus_read!(self.device, lm5066::READ_TEMPERATURE_1)?; + Ok(Celsius(temp.get()?.0)) + } +} + +impl CurrentSensor for Lm5066 { + fn read_iout(&self) -> Result { + let iout = pmbus_read!(self.device, lm5066::MFR_READ_IIN)?; + Ok(Amperes(iout.get(&self.load_coefficients()?.current)?.0)) + } +} + +impl VoltageSensor for Lm5066 { + fn read_vout(&self) -> Result { + let vout = pmbus_read!(self.device, lm5066::READ_VOUT)?; + Ok(Volts(vout.get()?.0)) + } +} diff --git a/drv/i2c-devices/src/raa229618.rs b/drv/i2c-devices/src/raa229618.rs index c4a47fa32..adb30b491 100644 --- a/drv/i2c-devices/src/raa229618.rs +++ b/drv/i2c-devices/src/raa229618.rs @@ -34,10 +34,19 @@ impl core::fmt::Display for Raa229618 { #[derive(Copy, Clone, Debug, PartialEq)] pub enum Error { + /// I2C error on PMBus read from device BadRead { cmd: u8, code: ResponseCode }, + + /// I2C error on PMBus write to device BadWrite { cmd: u8, code: ResponseCode }, + + /// Failed to parse PMBus data from device BadData { cmd: u8 }, + + /// I2C error attempting to validate device BadValidation { cmd: u8, code: ResponseCode }, + + /// PMBus data returned from device is invalid InvalidData { err: pmbus::Error }, } @@ -56,7 +65,9 @@ impl From for ResponseCode { Error::BadRead { code, .. } => code, Error::BadWrite { code, .. } => code, Error::BadValidation { code, .. } => code, - _ => panic!(), + Error::BadData { .. } | Error::InvalidData { .. } => { + ResponseCode::BadDeviceState + } } } } diff --git a/drv/i2c-devices/src/sbrmi.rs b/drv/i2c-devices/src/sbrmi.rs index ffab24a33..a857708a2 100644 --- a/drv/i2c-devices/src/sbrmi.rs +++ b/drv/i2c-devices/src/sbrmi.rs @@ -77,6 +77,7 @@ impl From for ResponseCode { match err { Error::BadRegisterRead { code, .. } => code, Error::BadCpuidRead { code } => code, + Error::BadRdmsr { code } => code, _ => ResponseCode::BadResponse, } } diff --git a/task/power/src/bsp/gimletlet_2.rs b/task/power/src/bsp/gimletlet_2.rs index 088b2059f..d0a762fae 100644 --- a/task/power/src/bsp/gimletlet_2.rs +++ b/task/power/src/bsp/gimletlet_2.rs @@ -9,11 +9,13 @@ use crate::{ pub(crate) const CONTROLLER_CONFIG_LEN: usize = 1; pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; - CONTROLLER_CONFIG_LEN] = [ - // The DC2024 has 10 3mΩ current sense resistors in parallel (5 on each - // channel), given a total current sense resistance of 300µΩ - ltc4282_controller!(HotSwapQSFP, v12_out_100a, A2, Ohms(0.003 / 10.0)), -]; + CONTROLLER_CONFIG_LEN] = [lm5066_controller!( + HotSwap, + lm5066_evl_vout, + A2, + Ohms(0.003), + drv_i2c_devices::lm5066::CurrentLimitStrap::VDD +)]; pub(crate) fn get_state() -> PowerState { PowerState::A2 diff --git a/task/power/src/main.rs b/task/power/src/main.rs index 1bb578fd2..b80dbf6da 100644 --- a/task/power/src/main.rs +++ b/task/power/src/main.rs @@ -13,6 +13,7 @@ use drv_i2c_devices::adm1272::*; use drv_i2c_devices::bmr491::*; use drv_i2c_devices::isl68224::*; +use drv_i2c_devices::lm5066::*; use drv_i2c_devices::ltc4282::*; use drv_i2c_devices::max5970::*; use drv_i2c_devices::mwocp68::*; @@ -87,6 +88,7 @@ enum DeviceChip { Max5970(Ohms), Mwocp68, Ltc4282(Ohms), + Lm5066(Ohms, CurrentLimitStrap), } struct PowerControllerConfig { @@ -113,6 +115,7 @@ enum Device { Max5970(Max5970), Mwocp68(Mwocp68), Ltc4282(Ltc4282), + Lm5066(Lm5066), } impl Device { @@ -124,6 +127,7 @@ impl Device { Device::Isl68224(dev) => dev.read_temperature()?, Device::Tps546B24A(dev) => dev.read_temperature()?, Device::Adm1272(dev) => dev.read_temperature()?, + Device::Lm5066(dev) => dev.read_temperature()?, Device::Mwocp68(..) => { // The MWOCP68 actually has three temperature sensors, but they // aren't associated with power rails, so we don't read them @@ -148,6 +152,7 @@ impl Device { Device::Max5970(dev) => dev.read_iout()?, Device::Mwocp68(dev) => dev.read_iout()?, Device::Ltc4282(dev) => dev.read_iout()?, + Device::Lm5066(dev) => dev.read_iout()?, }; Ok(r) } @@ -163,6 +168,7 @@ impl Device { Device::Max5970(dev) => dev.read_vout()?, Device::Mwocp68(dev) => dev.read_vout()?, Device::Ltc4282(dev) => dev.read_vout()?, + Device::Lm5066(dev) => dev.read_vout()?, }; Ok(r) } @@ -211,6 +217,7 @@ impl Device { | Device::Tps546B24A(_) | Device::Adm1272(_) | Device::Ltc4282(_) + | Device::Lm5066(_) | Device::Max5970(_) => { return Err(ResponseCode::OperationNotSupported) } @@ -226,7 +233,10 @@ impl Device { Device::Raa229620A(dev) => dev.read_mode()?, Device::Isl68224(dev) => dev.read_mode()?, Device::Tps546B24A(dev) => dev.read_mode()?, - Device::Adm1272(..) | Device::Ltc4282(..) | Device::Max5970(..) => { + Device::Adm1272(..) + | Device::Ltc4282(..) + | Device::Max5970(..) + | Device::Lm5066(..) => { return Err(ResponseCode::OperationNotSupported) } }; @@ -244,6 +254,7 @@ impl Device { Device::Adm1272(dev) => dev.i2c_device(), Device::Ltc4282(dev) => dev.i2c_device(), Device::Max5970(dev) => dev.i2c_device(), + Device::Lm5066(dev) => dev.i2c_device(), } } } @@ -273,6 +284,9 @@ impl PowerControllerConfig { DeviceChip::Ltc4282(sense) => { Device::Ltc4282(Ltc4282::new(&dev, *sense)) } + DeviceChip::Lm5066(sense, strap) => { + Device::Lm5066(Lm5066::new(&dev, *sense, *strap)) + } } } } @@ -361,6 +375,28 @@ macro_rules! ltc4282_controller { }; } +#[allow(unused_macros)] +macro_rules! lm5066_controller { + ($which:ident, $rail:ident, $state:ident, $rsense:expr, $strap:expr) => { + paste::paste! { + PowerControllerConfig { + state: $crate::PowerState::$state, + device: $crate::DeviceType::$which, + chip: $crate::DeviceChip::Lm5066($rsense, $strap), + builder: i2c_config::pmbus::$rail, + voltage: sensors::[], + input_voltage: None, + current: sensors::[], + input_current: None, + temperature: Some( + sensors::[] + ), + phases: None, + } + } + }; +} + #[allow(unused_macros)] macro_rules! max5970_controller { ($which:ident, $rail:ident, $state:ident, $rsense:expr) => {