From db46670b4486aad9b76a1ec4809a9f1e1d798d36 Mon Sep 17 00:00:00 2001
From: Matt Keeter <matt@oxide.computer>
Date: Wed, 15 Jan 2025 14:53:46 -0500
Subject: [PATCH] Add IMX3112 mux and APML devices to Grapefruit

---
 app/grapefruit/app.toml            |  25 ++++++
 drv/stm32xx-i2c/src/imx3112.rs     | 136 +++++++++++++++++++++++++++++
 drv/stm32xx-i2c/src/lib.rs         |   1 +
 task/thermal/src/bsp/grapefruit.rs |   9 +-
 4 files changed, 169 insertions(+), 2 deletions(-)
 create mode 100644 drv/stm32xx-i2c/src/imx3112.rs

diff --git a/app/grapefruit/app.toml b/app/grapefruit/app.toml
index 7d21e50ead..d468ebe8a3 100644
--- a/app/grapefruit/app.toml
+++ b/app/grapefruit/app.toml
@@ -403,6 +403,11 @@ sda.pin = 11
 scl.pin = 10
 af = 4
 
+[[config.i2c.controllers.ports.B.muxes]]
+driver = "imx3112"
+address = 0x70
+# U6 on AMD Ruby schematic
+
 [config.i2c.controllers.ports.F]
 name = "pcie"
 sda.pin = 0
@@ -528,6 +533,26 @@ sensors = { temperature = 1 }
 description = "LM75 (H)"
 refdes = "U104"
 
+[[config.i2c.devices]]
+bus = "apml"
+address = 0x3c
+device = "sbrmi"
+name = "RMI"
+mux = 1
+segment = 1
+description = "CPU via SB-RMI"
+
+[[config.i2c.devices]]
+bus = "apml"
+address = 0x4c
+device = "sbtsi"
+name = "CPU"
+mux = 1
+segment = 1
+description = "CPU temperature sensor"
+sensors = { temperature = 1 }
+
+
 ################################################################################
 
 [config.spi.spi2]
diff --git a/drv/stm32xx-i2c/src/imx3112.rs b/drv/stm32xx-i2c/src/imx3112.rs
new file mode 100644
index 0000000000..f7b0f57afe
--- /dev/null
+++ b/drv/stm32xx-i2c/src/imx3112.rs
@@ -0,0 +1,136 @@
+// 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 IMX3112 I2C mux
+
+use crate::*;
+use drv_i2c_api::{ResponseCode, Segment};
+
+use bitfield::bitfield;
+
+#[allow(dead_code)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum Register {
+    DeviceTypeLo = 0x0,
+    DeviceTypeHi = 0x1,
+    DeviceRevision = 0x2,
+    VendorIdLo = 0x3,
+    VendorIdHi = 0x4,
+    LocalInterfaceCfg = 0xe,
+    PullupResistorConfig = 0xf,
+    DeviceCfg = 0x12,
+    ClearTempSensorAlarm = 0x13,
+    ClearEccError = 0x14,
+    TempSensorCfg = 0x1a,
+    InterruptCfg = 0x1b,
+    TempHiLimitCfgLo = 0x1c,
+    TempHiLimitCfgHi = 0x1d,
+    TempLoLimitCfgLo = 0x1e,
+    TempLoLimitCfgHi = 0x1f,
+    TempCritHiLimitCfgLo = 0x20,
+    TempCritHiLimitCfgHi = 0x21,
+    TempCritLoLimitCfgLo = 0x22,
+    TempCritLoLimitCfgHi = 0x23,
+    DeviceStatus = 0x30,
+    CurrentTemperatureLo = 0x31,
+    CurrentTemperatureHi = 0x32,
+    TemperatureStatus = 0x33,
+    ErrorStatus = 0x34,
+    MuxConfig = 0x40,
+    MuxSelect = 0x41,
+}
+
+bitfield! {
+    #[derive(Copy, Clone, Eq, PartialEq)]
+    pub struct LocalInterfaceConfigRegister(u8);
+    external_pullup, set_external_pullup: 5;
+    ldo_voltage, set_ldo_voltage: 4, 3, 2;
+}
+
+bitfield! {
+    #[derive(Copy, Clone, Eq, PartialEq)]
+    pub struct MuxSelectRegister(u8);
+    channel1_enabled, set_channel1_enabled: 7;
+    channel0_enabled, set_channel0_enabled: 6;
+}
+
+pub struct Imx3112;
+
+fn write_reg_u8(
+    mux: &I2cMux<'_>,
+    controller: &I2cController<'_>,
+    reg: Register,
+    val: u8,
+    ctrl: &I2cControl,
+) -> Result<(), ResponseCode> {
+    controller
+        .write_read(
+            mux.address,
+            2,
+            |pos| Some(if pos == 0 { reg as u8 } else { val }),
+            ReadLength::Fixed(0),
+            |_, _| Some(()),
+            ctrl,
+        )
+        .map_err(|e| mux.error_code(e))
+}
+
+impl I2cMuxDriver for Imx3112 {
+    fn configure(
+        &self,
+        mux: &I2cMux<'_>,
+        controller: &I2cController<'_>,
+        gpio: &sys_api::Sys,
+        ctrl: &I2cControl,
+    ) -> Result<(), drv_i2c_api::ResponseCode> {
+        // Configure the mux to use external pull-ups
+        mux.configure(gpio)?;
+
+        let mut cfg = LocalInterfaceConfigRegister(0);
+        cfg.set_external_pullup(true);
+        write_reg_u8(
+            mux,
+            controller,
+            Register::LocalInterfaceCfg,
+            cfg.0,
+            ctrl,
+        )?;
+        let mut reg = MuxSelectRegister(0);
+        reg.set_channel0_enabled(true);
+        write_reg_u8(mux, controller, Register::MuxConfig, 0, ctrl)?;
+        write_reg_u8(mux, controller, Register::MuxSelect, reg.0, ctrl)?;
+        write_reg_u8(mux, controller, Register::MuxConfig, reg.0, ctrl)?;
+
+        Ok(())
+    }
+
+    fn enable_segment(
+        &self,
+        mux: &I2cMux<'_>,
+        controller: &I2cController<'_>,
+        segment: Option<Segment>,
+        ctrl: &I2cControl,
+    ) -> Result<(), ResponseCode> {
+        let mut reg = MuxSelectRegister(0);
+        match segment {
+            Some(Segment::S1) => reg.set_channel0_enabled(true),
+            Some(Segment::S2) => reg.set_channel1_enabled(true),
+            None => (),
+            _ => return Err(ResponseCode::SegmentNotFound),
+        }
+        // Enable only our desired output
+        write_reg_u8(mux, controller, Register::MuxConfig, reg.0, ctrl)?;
+        // Select our desired output
+        write_reg_u8(mux, controller, Register::MuxSelect, reg.0, ctrl)?;
+        Ok(())
+    }
+
+    fn reset(
+        &self,
+        mux: &I2cMux<'_>,
+        gpio: &sys_api::Sys,
+    ) -> Result<(), drv_i2c_api::ResponseCode> {
+        mux.reset(gpio)
+    }
+}
diff --git a/drv/stm32xx-i2c/src/lib.rs b/drv/stm32xx-i2c/src/lib.rs
index 3ad18c6d9b..4557a9cb24 100644
--- a/drv/stm32xx-i2c/src/lib.rs
+++ b/drv/stm32xx-i2c/src/lib.rs
@@ -35,6 +35,7 @@ pub type RegisterBlock = device::i2c1::RegisterBlock;
 ))]
 pub type Isr = device::i2c1::isr::R;
 
+pub mod imx3112;
 pub mod ltc4306;
 pub mod max7358;
 pub mod pca9545;
diff --git a/task/thermal/src/bsp/grapefruit.rs b/task/thermal/src/bsp/grapefruit.rs
index 9b989fe244..0642f8e4f7 100644
--- a/task/thermal/src/bsp/grapefruit.rs
+++ b/task/thermal/src/bsp/grapefruit.rs
@@ -22,7 +22,7 @@ use i2c_config::sensors;
 // Constants!
 
 // Air temperature sensors, which aren't used in the control loop
-const NUM_TEMPERATURE_SENSORS: usize = 0;
+const NUM_TEMPERATURE_SENSORS: usize = 1;
 
 // Temperature inputs (I2C devices), which are used in the control loop.
 pub const NUM_TEMPERATURE_INPUTS: usize = 1;
@@ -135,4 +135,9 @@ const INPUTS: [InputChannel; NUM_TEMPERATURE_INPUTS] = [InputChannel::new(
     ChannelType::MustBePresent,
 )];
 
-const MISC_SENSORS: [TemperatureSensor; NUM_TEMPERATURE_SENSORS] = [];
+const MISC_SENSORS: [TemperatureSensor; NUM_TEMPERATURE_SENSORS] =
+    [TemperatureSensor::new(
+        Device::CPU,
+        devices::sbtsi_cpu,
+        sensors::SBTSI_CPU_TEMPERATURE_SENSOR,
+    )];