[mt8167][thermal] Add DVFS support

ZX-2789
Test: wrote program to call the DVFS ioctls
Test: verified VPROC voltages with a multimeter
Test: used zx_clock_get_monotonic to time a uapp,
      time followed ARMPLL frequency as expected

Change-Id: I0e788139974a92c2278907a8400b97d4eaddbc6b
diff --git a/system/dev/board/mt8167s_ref/mt8167-thermal.cpp b/system/dev/board/mt8167s_ref/mt8167-thermal.cpp
index a706cb9..35bfb69 100644
--- a/system/dev/board/mt8167s_ref/mt8167-thermal.cpp
+++ b/system/dev/board/mt8167s_ref/mt8167-thermal.cpp
@@ -4,8 +4,9 @@
 
 #include <ddk/debug.h>
 #include <ddk/platform-defs.h>
-#include <soc/mt8167/mt8167-hw.h>
 #include <soc/mt8167/mt8167-clk.h>
+#include <soc/mt8167/mt8167-hw.h>
+#include <zircon/device/thermal.h>
 
 #include "mt8167.h"
 
@@ -14,11 +15,19 @@
 constexpr pbus_mmio_t thermal_mmios[] = {
     {
         .base = MT8167_THERMAL_BASE,
-        .length = MT8167_THERMAL_SIZE,
+        .length = MT8167_THERMAL_SIZE
     },
     {
         .base = MT8167_FUSE_BASE,
-        .length = MT8167_FUSE_SIZE,
+        .length = MT8167_FUSE_SIZE
+    },
+    {
+        .base = MT8167_AP_MIXED_SYS_BASE,
+        .length = MT8167_AP_MIXED_SYS_SIZE
+    },
+    {
+        .base = MT8167_PMIC_WRAP_BASE,
+        .length = MT8167_PMIC_WRAP_SIZE
     }
 };
 
@@ -28,9 +37,67 @@
     },
     {
         .clk = board_mt8167::kClkAuxAdc
+    },
+    {
+        .clk = board_mt8167::kClkPmicWrapAp
+    },
+    {
+        .clk = board_mt8167::kClkPmicWrap26M
     }
 };
 
+constexpr thermal_device_info_t thermal_dev_info = {
+    .active_cooling = false,
+    .passive_cooling = true,
+    .gpu_throttling = true,
+    .num_trip_points = 0,
+    .big_little = false,
+    .critical_temp = 0,
+    .trip_point_info = {},
+    .opps = {
+        [BIG_CLUSTER_POWER_DOMAIN] = {
+            // See section 3.6 (MTCMOS Domains) of the functional specification document.
+            .opp = {
+                [0] = {
+                    .freq_hz = 598000000,
+                    .volt_mv = 1150000
+                },
+                [1] = {
+                    .freq_hz = 747500000,
+                    .volt_mv = 1150000
+                },
+                [2] = {
+                    .freq_hz = 1040000000,
+                    .volt_mv = 1200000
+                },
+                [3] = {
+                    .freq_hz = 1196000000,
+                    .volt_mv = 1250000
+                },
+                [4] = {
+                    .freq_hz = 1300000000,
+                    .volt_mv = 1300000
+                },
+            },
+            .latency = 0,
+            .count = 5
+        },
+        [LITTLE_CLUSTER_POWER_DOMAIN] = {
+            .opp = {},
+            .latency = 0,
+            .count = 0
+        }
+    }
+};
+
+constexpr pbus_metadata_t thermal_metadata[] = {
+    {
+        .type = THERMAL_CONFIG_METADATA,
+        .data_buffer = &thermal_dev_info,
+        .data_size = sizeof(thermal_dev_info)
+    },
+};
+
 const pbus_dev_t thermal_dev = []() {
     pbus_dev_t thermal_dev = {};
     thermal_dev.name = "thermal";
@@ -41,8 +108,9 @@
     thermal_dev.mmio_count = countof(thermal_mmios);
     thermal_dev.clk_list = thermal_clks;
     thermal_dev.clk_count = countof(thermal_clks);
+    thermal_dev.metadata_list = thermal_metadata;
+    thermal_dev.metadata_count = countof(thermal_metadata);
     thermal_dev.bti_count = 0;
-    thermal_dev.metadata_count = 0;
     thermal_dev.irq_count = 0;
     thermal_dev.gpio_count = 0;
     return thermal_dev;
diff --git a/system/dev/board/mt8167s_ref/rules.mk b/system/dev/board/mt8167s_ref/rules.mk
index 9298512..f6b72ac 100644
--- a/system/dev/board/mt8167s_ref/rules.mk
+++ b/system/dev/board/mt8167s_ref/rules.mk
@@ -41,5 +41,6 @@
     system/banjo/ddk-protocol-gpio-impl \
     system/banjo/ddk-protocol-platform-bus \
     system/banjo/ddk-protocol-platform-device \
+    system/banjo/ddk-protocol-scpi \
 
 include make/module.mk
diff --git a/system/dev/clk/mtk-clk/mtk-clk.cpp b/system/dev/clk/mtk-clk/mtk-clk.cpp
index 2156d64..91ceda8 100644
--- a/system/dev/clk/mtk-clk/mtk-clk.cpp
+++ b/system/dev/clk/mtk-clk/mtk-clk.cpp
@@ -35,6 +35,8 @@
     [board_mt8167::kClkI2c0] = {.regs = kClkGatingCtrl1, .bit = 3},
     [board_mt8167::kClkI2c1] = {.regs = kClkGatingCtrl1, .bit = 4},
     [board_mt8167::kClkI2c2] = {.regs = kClkGatingCtrl1, .bit = 16},
+    [board_mt8167::kClkPmicWrapAp] = {.regs = kClkGatingCtrl1, .bit = 20},
+    [board_mt8167::kClkPmicWrap26M] = {.regs = kClkGatingCtrl1, .bit = 29},
     [board_mt8167::kClkAuxAdc] = {.regs = kClkGatingCtrl1, .bit = 30},
     [board_mt8167::kClkSlowMfg] = {.regs = kClkGatingCtrl8, .bit = 7},
     [board_mt8167::kClkAxiMfg] = {.regs = kClkGatingCtrl8, .bit = 6},
diff --git a/system/dev/lib/mt8167/include/soc/mt8167/mt8167-clk.h b/system/dev/lib/mt8167/include/soc/mt8167/mt8167-clk.h
index 2ca45fd..cddf2ae 100644
--- a/system/dev/lib/mt8167/include/soc/mt8167/mt8167-clk.h
+++ b/system/dev/lib/mt8167/include/soc/mt8167/mt8167-clk.h
@@ -11,6 +11,8 @@
     kClkI2c0,
     kClkI2c1,
     kClkI2c2,
+    kClkPmicWrapAp,
+    kClkPmicWrap26M,
     kClkAuxAdc,
     kClkSlowMfg,
     kClkAxiMfg,
diff --git a/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h b/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h
index 13eca18..1dad2cb 100644
--- a/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h
+++ b/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h
@@ -62,6 +62,9 @@
 #define MT8167_FUSE_BASE                                    0x10009000
 #define MT8167_FUSE_SIZE                                    0x1000
 
+#define MT8167_PMIC_WRAP_BASE                               0x1000f000
+#define MT8167_PMIC_WRAP_SIZE                               0x1000
+
 // Display Subsystem
 #define MT8167_DISP_OVL_BASE                                0x14007000
 #define MT8167_DISP_OVL_SIZE                                0x1000
diff --git a/system/dev/thermal/mtk-thermal/binding.c b/system/dev/thermal/mtk-thermal/binding.c
index 3508e33..df66df6 100644
--- a/system/dev/thermal/mtk-thermal/binding.c
+++ b/system/dev/thermal/mtk-thermal/binding.c
@@ -15,7 +15,6 @@
 };
 
 ZIRCON_DRIVER_BEGIN(mtk_thermal, mtk_thermal_driver_ops, "zircon", "0.1", 3)
-    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_SCPI),
     BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MEDIATEK),
     BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_MEDIATEK_THERMAL),
 ZIRCON_DRIVER_END(mtk_thermal)
diff --git a/system/dev/thermal/mtk-thermal/mtk-thermal-reg.h b/system/dev/thermal/mtk-thermal/mtk-thermal-reg.h
index 3fa89fe..2997583 100644
--- a/system/dev/thermal/mtk-thermal/mtk-thermal-reg.h
+++ b/system/dev/thermal/mtk-thermal/mtk-thermal-reg.h
@@ -8,6 +8,65 @@
 
 namespace thermal {
 
+class ArmPllCon1 : public hwreg::RegisterBase<ArmPllCon1, uint32_t> {
+public:
+    static constexpr uint32_t kDiv1  = 0;
+    static constexpr uint32_t kDiv2  = 1;
+    static constexpr uint32_t kDiv4  = 2;
+    static constexpr uint32_t kDiv8  = 3;
+    static constexpr uint32_t kDiv16 = 4;
+
+    static constexpr uint32_t kPcwFracBits = 14;
+
+    static constexpr uint32_t kPllSrcClk = 26000000;
+
+    static auto Get() { return hwreg::RegisterAddr<ArmPllCon1>(0x104); }
+
+    uint32_t frequency() const {
+        uint64_t freq = static_cast<uint64_t>(pcw()) * kPllSrcClk;
+        return static_cast<uint32_t>(freq >> (kPcwFracBits + div()));
+    }
+
+    ArmPllCon1& set_frequency(uint32_t freq_hz) {
+        set_change(1);
+        set_div(kDiv1);
+        uint64_t pcw_value = static_cast<uint64_t>(freq_hz) << (kPcwFracBits + div());
+        set_pcw(static_cast<uint32_t>(pcw_value / kPllSrcClk));
+        return *this;
+    }
+
+    DEF_BIT(31, change);
+    DEF_FIELD(26, 24, div);
+    DEF_FIELD(20, 0, pcw);
+};
+
+class PmicCmd : public hwreg::RegisterBase<PmicCmd, uint32_t> {
+public:
+    static auto Get() { return hwreg::RegisterAddr<PmicCmd>(0xa0); }
+
+    DEF_BIT(31, write);
+    DEF_FIELD(30, 16, addr);
+    DEF_FIELD(15, 0, data);
+};
+
+class PmicReadData : public hwreg::RegisterBase<PmicReadData, uint32_t> {
+public:
+    static constexpr uint32_t kStateIdle  = 0;
+    static constexpr uint32_t kStateValid = 6;
+
+    static auto Get() { return hwreg::RegisterAddr<PmicReadData>(0xa4); }
+
+    DEF_FIELD(18, 16, status);
+    DEF_FIELD(15, 0, data);
+};
+
+class PmicValidClear : public hwreg::RegisterBase<PmicValidClear, uint32_t> {
+public:
+    static auto Get() { return hwreg::RegisterAddr<PmicValidClear>(0xa8); }
+
+    DEF_BIT(0, valid_clear);
+};
+
 class TempMonCtl0 : public hwreg::RegisterBase<TempMonCtl0, uint32_t> {
 public:
     static auto Get() { return hwreg::RegisterAddr<TempMonCtl0>(0x00); }
@@ -220,4 +279,28 @@
     static constexpr uint32_t kVtsOffset = 3350;
 };
 
+// The following classes represent registers on the MT6392 PMIC.
+class VprocCon10 : public hwreg::RegisterBase<VprocCon10, uint16_t> {
+private:
+    static constexpr uint16_t kMaxVoltageStep = 0x7f;
+    static constexpr uint32_t kVoltageStepUv = 6250;
+
+public:
+    static constexpr uint32_t kMinVoltageUv = 700000;
+    static constexpr uint32_t kMaxVoltageUv = kMinVoltageUv + (kVoltageStepUv * kMaxVoltageStep);
+
+    static auto Get() { return hwreg::RegisterAddr<VprocCon10>(0x110); }
+
+    uint32_t voltage() const {
+        return (voltage_step() * kVoltageStepUv) + kMinVoltageUv;
+    }
+
+    VprocCon10& set_voltage(uint32_t volt_uv) {
+        set_voltage_step(static_cast<uint16_t>((volt_uv - kMinVoltageUv) / kVoltageStepUv));
+        return *this;
+    }
+
+    DEF_FIELD(6, 0, voltage_step);
+};
+
 }  // namespace thermal
diff --git a/system/dev/thermal/mtk-thermal/mtk-thermal.cpp b/system/dev/thermal/mtk-thermal/mtk-thermal.cpp
index a5ef300..ded8389 100644
--- a/system/dev/thermal/mtk-thermal/mtk-thermal.cpp
+++ b/system/dev/thermal/mtk-thermal/mtk-thermal.cpp
@@ -8,7 +8,6 @@
 #include <ddktl/pdev.h>
 #include <fbl/unique_ptr.h>
 #include <soc/mt8167/mt8167-hw.h>
-#include <zircon/device/thermal.h>
 
 #include "mtk-thermal-reg.h"
 
@@ -70,19 +69,41 @@
 
     std::optional<ddk::MmioBuffer> mmio;
     if ((status = pdev.MapMmio(0, &mmio)) != ZX_OK) {
-        zxlogf(ERROR, "%s: pdev_map_mmio_buffer2 failed\n", __FILE__);
+        zxlogf(ERROR, "%s: MapMmio failed\n", __FILE__);
         return status;
     }
 
     std::optional<ddk::MmioBuffer> fuse_mmio;
     if ((status = pdev.MapMmio(1, &fuse_mmio)) != ZX_OK) {
-        zxlogf(ERROR, "%s: pdev_map_mmio_buffer2 failed\n", __FILE__);
+        zxlogf(ERROR, "%s: MapMmio failed\n", __FILE__);
         return status;
     }
 
+    std::optional<ddk::MmioBuffer> pll_mmio;
+    if ((status = pdev.MapMmio(2, &pll_mmio)) != ZX_OK) {
+        zxlogf(ERROR, "%s: MapMmio failed\n", __FILE__);
+        return status;
+    }
+
+    std::optional<ddk::MmioBuffer> pmic_mmio;
+    if ((status = pdev.MapMmio(3, &pmic_mmio)) != ZX_OK) {
+        zxlogf(ERROR, "%s: MapMmio failed\n", __FILE__);
+        return status;
+    }
+
+    thermal_device_info_t thermal_info;
+    size_t actual;
+    status = device_get_metadata(parent, THERMAL_CONFIG_METADATA, &thermal_info,
+                                 sizeof(thermal_info), &actual);
+    if (status != ZX_OK || actual != sizeof(thermal_info)) {
+        zxlogf(ERROR, "%s: device_get_metadata failed\n", __FILE__);
+        return status == ZX_OK ? ZX_ERR_INTERNAL : status;
+    }
+
     fbl::AllocChecker ac;
     fbl::unique_ptr<MtkThermal> device(
-        new (&ac) MtkThermal(parent, std::move(*mmio), std::move(*fuse_mmio), clk, info));
+        new (&ac) MtkThermal(parent, std::move(*mmio), std::move(*fuse_mmio), std::move(*pll_mmio),
+                             std::move(*pmic_mmio), clk, info, thermal_info));
     if (!ac.check()) {
         zxlogf(ERROR, "%s: MtkThermal alloc failed\n", __FILE__);
         return ZX_ERR_NO_MEMORY;
@@ -111,6 +132,17 @@
         }
     }
 
+    // Set the initial DVFS operating point. The bootloader sets it to 1.001 GHz @ 1.2 V.
+    constexpr dvfs_info_t dvfs_info = {
+        .op_idx = 0,
+        .power_domain = BIG_CLUSTER_POWER_DOMAIN
+    };
+
+    zx_status_t status = SetDvfsOpp(&dvfs_info);
+    if (status != ZX_OK) {
+        return status;
+    }
+
     TempMonCtl0::Get().ReadFrom(&mmio_).disable_all().WriteTo(&mmio_);
 
     TempMsrCtl0::Get()
@@ -190,6 +222,26 @@
     return ZX_OK;
 }
 
+uint16_t MtkThermal::PmicRead(uint32_t addr) {
+    while (PmicReadData::Get().ReadFrom(&pmic_mmio_).status() != PmicReadData::kStateIdle) {}
+
+    PmicCmd::Get().FromValue(0).set_write(0).set_addr(addr).WriteTo(&pmic_mmio_);
+
+    auto pmic_read = PmicReadData::Get().FromValue(0);
+    while (pmic_read.ReadFrom(&pmic_mmio_).status() != PmicReadData::kStateValid) {}
+
+    uint16_t ret = static_cast<uint16_t>(pmic_read.data());
+
+    PmicValidClear::Get().ReadFrom(&pmic_mmio_).set_valid_clear(1).WriteTo(&pmic_mmio_);
+
+    return ret;
+}
+
+void MtkThermal::PmicWrite(uint16_t data, uint32_t addr) {
+    while (PmicReadData::Get().ReadFrom(&pmic_mmio_).status() != PmicReadData::kStateIdle) {}
+    PmicCmd::Get().FromValue(0).set_write(1).set_addr(addr).set_data(data).WriteTo(&pmic_mmio_);
+}
+
 uint32_t MtkThermal::RawToTemperature(uint32_t raw, int sensor) {
     auto cal0 = TempCalibration0::Get().ReadFrom(&fuse_mmio_);
     auto cal1 = TempCalibration1::Get().ReadFrom(&fuse_mmio_);
@@ -230,6 +282,45 @@
     return ZX_OK;
 }
 
+zx_status_t MtkThermal::SetDvfsOpp(const dvfs_info_t* opp) {
+    if (opp->power_domain >= MAX_DVFS_DOMAINS) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    const scpi_opp_t& opps = thermal_info_.opps[opp->power_domain];
+    if (opp->op_idx >= opps.count) {
+        return ZX_ERR_OUT_OF_RANGE;
+    }
+
+    uint32_t new_freq = opps.opp[opp->op_idx].freq_hz;
+    uint32_t new_volt = opps.opp[opp->op_idx].volt_mv;
+
+    if (new_volt > VprocCon10::kMaxVoltageUv || new_volt < VprocCon10::kMinVoltageUv) {
+        return ZX_ERR_OUT_OF_RANGE;
+    }
+
+    auto armpll = ArmPllCon1::Get().ReadFrom(&pll_mmio_);
+    uint32_t old_freq = armpll.frequency();
+
+    auto vproc = VprocCon10::Get().FromValue(0).set_voltage(new_volt);
+    if (vproc.voltage() != new_volt) {
+        // The requested voltage is not a multiple of the voltage step.
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    if (new_freq > old_freq) {
+        PmicWrite(vproc.reg_value(), vproc.reg_addr());
+        armpll.set_frequency(new_freq).WriteTo(&pll_mmio_);
+    } else {
+        armpll.set_frequency(new_freq).WriteTo(&pll_mmio_);
+        PmicWrite(vproc.reg_value(), vproc.reg_addr());
+    }
+
+    current_opp_idx_ = opp->op_idx;
+
+    return ZX_OK;
+}
+
 zx_status_t MtkThermal::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
                                  size_t out_len, size_t* actual) {
     switch (op) {
@@ -240,16 +331,61 @@
 
         *actual = sizeof(uint32_t);
         return GetTemperature(reinterpret_cast<uint32_t*>(out_buf));
+
+    case IOCTL_THERMAL_GET_DEVICE_INFO:
+        if (out_len != sizeof(thermal_info_)) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        memcpy(out_buf, &thermal_info_, sizeof(thermal_info_));
+        *actual = sizeof(thermal_info_);
+        return ZX_OK;
+
+    case IOCTL_THERMAL_SET_DVFS_OPP:
+        if (in_len != sizeof(dvfs_info_t)) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        return SetDvfsOpp(reinterpret_cast<const dvfs_info_t*>(in_buf));
+
+    case IOCTL_THERMAL_GET_DVFS_INFO: {
+        if (in_len != sizeof(uint32_t) || out_len != sizeof(thermal_info_.opps[0])) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        uint32_t domain = *reinterpret_cast<const uint32_t*>(in_buf);
+        if (domain >= MAX_DVFS_DOMAINS) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        memcpy(out_buf, &thermal_info_.opps[domain], sizeof(thermal_info_.opps[0]));
+        *actual = sizeof(thermal_info_.opps[0]);
+        return ZX_OK;
+    }
+
+    case IOCTL_THERMAL_GET_DVFS_OPP: {
+        if (in_len != sizeof(uint32_t) || out_len != sizeof(uint32_t)) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        uint32_t domain = *reinterpret_cast<const uint32_t*>(in_buf);
+        if (domain != BIG_CLUSTER_POWER_DOMAIN) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        uint32_t* opp_idx = reinterpret_cast<uint32_t*>(out_buf);
+
+        *opp_idx = current_opp_idx_;
+        *actual = sizeof(*opp_idx);
+        return ZX_OK;
+    }
+
     // TODO(bradenkell): Implement the rest of these.
     case IOCTL_THERMAL_GET_INFO:
     case IOCTL_THERMAL_SET_TRIP:
     case IOCTL_THERMAL_GET_STATE_CHANGE_EVENT:
     case IOCTL_THERMAL_GET_STATE_CHANGE_PORT:
-    case IOCTL_THERMAL_GET_DEVICE_INFO:
     case IOCTL_THERMAL_SET_FAN_LEVEL:
-    case IOCTL_THERMAL_SET_DVFS_OPP:
-    case IOCTL_THERMAL_GET_DVFS_INFO:
-    case IOCTL_THERMAL_GET_DVFS_OPP:
     case IOCTL_THERMAL_GET_FAN_LEVEL:
     default:
         break;
diff --git a/system/dev/thermal/mtk-thermal/mtk-thermal.h b/system/dev/thermal/mtk-thermal/mtk-thermal.h
index dd05b4f..e292b02 100644
--- a/system/dev/thermal/mtk-thermal/mtk-thermal.h
+++ b/system/dev/thermal/mtk-thermal/mtk-thermal.h
@@ -9,6 +9,7 @@
 #include <ddktl/mmio.h>
 #include <ddktl/protocol/clk.h>
 #include <ddktl/protocol/empty-protocol.h>
+#include <zircon/device/thermal.h>
 
 namespace thermal {
 
@@ -26,19 +27,30 @@
 
 private:
     MtkThermal(zx_device_t* parent, ddk::MmioBuffer mmio, ddk::MmioBuffer fuse_mmio,
-               const ddk::ClkProtocolProxy& clk, const pdev_device_info_t info)
-        : DeviceType(parent), mmio_(std::move(mmio)), fuse_mmio_(std::move(fuse_mmio)), clk_(clk),
-          clk_count_(info.clk_count) {}
+               ddk::MmioBuffer pll_mmio, ddk::MmioBuffer pmic_mmio,
+               const ddk::ClkProtocolProxy& clk, const pdev_device_info_t& info,
+               const thermal_device_info_t& thermal_info)
+        : DeviceType(parent), mmio_(std::move(mmio)), fuse_mmio_(std::move(fuse_mmio)),
+          pll_mmio_(std::move(pll_mmio)), pmic_mmio_(std::move(pmic_mmio)), clk_(clk),
+          clk_count_(info.clk_count), thermal_info_(thermal_info) {}
 
     zx_status_t GetTemperature(uint32_t* temp);
+    zx_status_t SetDvfsOpp(const dvfs_info_t* opp);
     zx_status_t Init();
 
     uint32_t RawToTemperature(uint32_t raw, int sensor);
 
+    uint16_t PmicRead(uint32_t addr);
+    void PmicWrite(uint16_t data, uint32_t addr);
+
     ddk::MmioBuffer mmio_;
     ddk::MmioBuffer fuse_mmio_;
+    ddk::MmioBuffer pll_mmio_;
+    ddk::MmioBuffer pmic_mmio_;
     ddk::ClkProtocolProxy clk_;
     const uint32_t clk_count_;
+    const thermal_device_info_t thermal_info_;
+    uint32_t current_opp_idx_ = 0;
 };
 
 }  // namespace thermal