[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