| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "mtk-thermal.h" |
| |
| #include <fuchsia/hardware/clock/cpp/banjo.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/device-protocol/pdev.h> |
| #include <zircon/rights.h> |
| #include <zircon/threads.h> |
| |
| #include <cmath> |
| #include <memory> |
| |
| #include <lib/ddk/metadata.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <soc/mt8167/mt8167-hw.h> |
| |
| #include "src/devices/thermal/drivers/mtk-thermal/mtk-thermal-bind.h" |
| |
| namespace { |
| |
| constexpr uint32_t kTsCon1Addr = 0x10018604; |
| constexpr uint32_t kAuxAdcCon1SetAddr = 0x11003008; |
| constexpr uint32_t kAuxAdcCon1ClrAddr = 0x1100300c; |
| constexpr uint32_t kAuxAdcDat11Addr = 0x11003040; |
| constexpr uint32_t kAuxAdcChannel = 11; |
| constexpr uint32_t kAuxAdcBits = 12; |
| |
| constexpr uint32_t kSensorCount = 3; |
| |
| constexpr uint32_t kSrcClkFreq = 66'000'000; |
| constexpr uint32_t kSrcClkDivider = 256; |
| |
| constexpr uint32_t FreqToPeriodUnits(uint32_t freq_hz, uint32_t period) { |
| return (kSrcClkFreq / (kSrcClkDivider * (period + 1) * freq_hz)) - 1; |
| } |
| |
| constexpr uint32_t kThermalPeriod = 1023; |
| constexpr uint32_t kFilterInterval = 0; |
| constexpr uint32_t kSenseInterval = FreqToPeriodUnits(10, kThermalPeriod); |
| constexpr uint32_t kAhbPollPeriod = FreqToPeriodUnits(10, kThermalPeriod); |
| |
| constexpr int32_t FixedPoint(int32_t value) { return (value * 10000) >> 12; } |
| |
| constexpr int32_t RawWithGain(int32_t raw, int32_t gain) { |
| return (FixedPoint(raw) * 10000) / gain; |
| } |
| |
| constexpr int32_t TempWithoutGain(int32_t temp, int32_t gain) { |
| return (((temp * gain) / 10000) << 12) / 10000; |
| } |
| |
| } // namespace |
| |
| namespace thermal { |
| |
| zx_status_t MtkThermal::Create(void* context, zx_device_t* parent) { |
| zx_status_t status; |
| auto pdev = ddk::PDev::FromFragment(parent); |
| if (!pdev.is_valid()) { |
| zxlogf(ERROR, "%s: ZX_PROTOCOL_PDEV not available", __FILE__); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| pdev_device_info_t info; |
| if ((status = pdev.GetDeviceInfo(&info)) != ZX_OK) { |
| zxlogf(ERROR, "%s: pdev_get_device_info failed", __FILE__); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> mmio; |
| if ((status = pdev.MapMmio(0, &mmio)) != ZX_OK) { |
| zxlogf(ERROR, "%s: MapMmio failed", __FILE__); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> fuse_mmio; |
| if ((status = pdev.MapMmio(1, &fuse_mmio)) != ZX_OK) { |
| zxlogf(ERROR, "%s: MapMmio failed", __FILE__); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> pll_mmio; |
| if ((status = pdev.MapMmio(2, &pll_mmio)) != ZX_OK) { |
| zxlogf(ERROR, "%s: MapMmio failed", __FILE__); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> pmic_mmio; |
| if ((status = pdev.MapMmio(3, &pmic_mmio)) != ZX_OK) { |
| zxlogf(ERROR, "%s: MapMmio failed", __FILE__); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> infracfg_mmio; |
| if ((status = pdev.MapMmio(4, &infracfg_mmio)) != ZX_OK) { |
| zxlogf(ERROR, "%s: MapMmio failed", __FILE__); |
| return status; |
| } |
| |
| size_t actual; |
| fuchsia_hardware_thermal_ThermalDeviceInfo thermal_info; |
| status = device_get_metadata(parent, DEVICE_METADATA_THERMAL_CONFIG, &thermal_info, |
| sizeof(thermal_info), &actual); |
| if (status != ZX_OK || actual != sizeof(thermal_info)) { |
| zxlogf(ERROR, "%s: device_get_metadata failed", __FILE__); |
| return status == ZX_OK ? ZX_ERR_INTERNAL : status; |
| } |
| |
| zx::interrupt irq; |
| if ((status = pdev.GetInterrupt(0, &irq)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to get interrupt", __FILE__); |
| return status; |
| } |
| |
| zx::port port; |
| if ((status = zx::port::create(0, &port)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create port", __FILE__); |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<MtkThermal> device( |
| new (&ac) MtkThermal(parent, std::move(*mmio), std::move(*pll_mmio), std::move(*pmic_mmio), |
| std::move(*infracfg_mmio), pdev, thermal_info, std::move(port), |
| std::move(irq), TempCalibration0::Get().ReadFrom(&(*fuse_mmio)), |
| TempCalibration1::Get().ReadFrom(&(*fuse_mmio)), |
| TempCalibration2::Get().ReadFrom(&(*fuse_mmio)))); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: MtkThermal alloc failed", __FILE__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((status = device->Init()) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = device->DdkAdd("mtk-thermal")) != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkAdd failed", __FILE__); |
| return status; |
| } |
| |
| __UNUSED auto* dummy = device.release(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t MtkThermal::Init() { |
| auto fragment_count = DdkGetFragmentCount(); |
| |
| composite_device_fragment_t fragments[fragment_count]; |
| size_t actual; |
| DdkGetFragments(fragments, fragment_count, &actual); |
| if (fragment_count != actual) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // zeroth fragment is pdev |
| for (uint32_t i = 1; i < fragment_count; i++) { |
| clock_protocol_t clock; |
| auto status = device_get_protocol(fragments[i].device, ZX_PROTOCOL_CLOCK, &clock); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to get clock %u", __FILE__, i); |
| return status; |
| } |
| |
| status = clock_enable(&clock); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to enable clock %u", __FILE__, i); |
| return status; |
| } |
| } |
| |
| // Set the initial DVFS operating point. The bootloader sets it to 1.001 GHz @ 1.2 V. |
| uint32_t op_idx = |
| thermal_info_.opps[fuchsia_hardware_thermal_PowerDomain_BIG_CLUSTER_POWER_DOMAIN].count - 1; |
| auto status = SetDvfsOpp(static_cast<uint16_t>(op_idx)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| TempMonCtl0::Get().ReadFrom(&mmio_).disable_all().WriteTo(&mmio_); |
| |
| TempMsrCtl0::Get() |
| .ReadFrom(&mmio_) |
| .set_msrctl0(TempMsrCtl0::kSample1) |
| .set_msrctl1(TempMsrCtl0::kSample1) |
| .set_msrctl2(TempMsrCtl0::kSample1) |
| .set_msrctl3(TempMsrCtl0::kSample1) |
| .WriteTo(&mmio_); |
| |
| TempAhbTimeout::Get().FromValue(0xffffffff).WriteTo(&mmio_); |
| TempAdcPnp::Get(0).FromValue(0).WriteTo(&mmio_); |
| TempAdcPnp::Get(1).FromValue(1).WriteTo(&mmio_); |
| TempAdcPnp::Get(2).FromValue(2).WriteTo(&mmio_); |
| |
| // Set the thermal controller to read from the spare registers, then wait for the dummy sensor |
| // reading to end up in TempMsr0-2. |
| TempMonCtl1::Get().ReadFrom(&mmio_).set_period(1).WriteTo(&mmio_); |
| TempMonCtl2::Get().ReadFrom(&mmio_).set_sen_interval(1).WriteTo(&mmio_); |
| TempAhbPoll::Get().FromValue(1).WriteTo(&mmio_); |
| |
| constexpr uint32_t dummy_temp = (1 << kAuxAdcBits) - 1; |
| TempSpare::Get(0).FromValue(dummy_temp | (1 << kAuxAdcBits)).WriteTo(&mmio_); |
| |
| TempPnpMuxAddr::Get().FromValue(TempSpare::Get(2).addr() + MT8167_THERMAL_BASE).WriteTo(&mmio_); |
| TempAdcMuxAddr::Get().FromValue(TempSpare::Get(2).addr() + MT8167_THERMAL_BASE).WriteTo(&mmio_); |
| TempAdcEnAddr::Get().FromValue(TempSpare::Get(1).addr() + MT8167_THERMAL_BASE).WriteTo(&mmio_); |
| TempAdcValidAddr::Get().FromValue(TempSpare::Get(0).addr() + MT8167_THERMAL_BASE).WriteTo(&mmio_); |
| TempAdcVoltAddr::Get().FromValue(TempSpare::Get(0).addr() + MT8167_THERMAL_BASE).WriteTo(&mmio_); |
| |
| TempRdCtrl::Get().ReadFrom(&mmio_).set_diff(TempRdCtrl::kValidVoltageSame).WriteTo(&mmio_); |
| TempAdcValidMask::Get() |
| .ReadFrom(&mmio_) |
| .set_polarity(TempAdcValidMask::kActiveHigh) |
| .set_pos(kAuxAdcBits) |
| .WriteTo(&mmio_); |
| TempAdcVoltageShift::Get().FromValue(0).WriteTo(&mmio_); |
| TempMonCtl0::Get().ReadFrom(&mmio_).enable_all().WriteTo(&mmio_); |
| |
| for (uint32_t i = 0; i < kSensorCount; i++) { |
| auto msr = TempMsr::Get(i).ReadFrom(&mmio_); |
| for (; msr.valid() == 0 || msr.reading() != dummy_temp; msr.ReadFrom(&mmio_)) { |
| } |
| } |
| |
| TempMonCtl0::Get().ReadFrom(&mmio_).disable_all().WriteTo(&mmio_); |
| |
| // Set the thermal controller to get temperature readings from the aux ADC. |
| TempMonCtl1::Get().ReadFrom(&mmio_).set_period(kThermalPeriod).WriteTo(&mmio_); |
| TempMonCtl2::Get() |
| .ReadFrom(&mmio_) |
| .set_sen_interval(kSenseInterval) |
| .set_filt_interval(kFilterInterval) |
| .WriteTo(&mmio_); |
| TempAhbPoll::Get().FromValue(kAhbPollPeriod).WriteTo(&mmio_); |
| |
| TempAdcEn::Get().FromValue(1 << kAuxAdcChannel).WriteTo(&mmio_); |
| TempAdcMux::Get().FromValue(1 << kAuxAdcChannel).WriteTo(&mmio_); |
| |
| TempPnpMuxAddr::Get().FromValue(kTsCon1Addr).WriteTo(&mmio_); |
| TempAdcEnAddr::Get().FromValue(kAuxAdcCon1SetAddr).WriteTo(&mmio_); |
| TempAdcMuxAddr::Get().FromValue(kAuxAdcCon1ClrAddr).WriteTo(&mmio_); |
| TempAdcValidAddr::Get().FromValue(kAuxAdcDat11Addr).WriteTo(&mmio_); |
| TempAdcVoltAddr::Get().FromValue(kAuxAdcDat11Addr).WriteTo(&mmio_); |
| |
| TempAdcWriteCtrl::Get().ReadFrom(&mmio_).set_mux_write_en(1).set_pnp_write_en(1).WriteTo(&mmio_); |
| |
| TempMonCtl0::Get().ReadFrom(&mmio_).enable_real().WriteTo(&mmio_); |
| |
| TempMsrCtl0::Get() |
| .ReadFrom(&mmio_) |
| .set_msrctl0(TempMsrCtl0::kSample4Drop2) |
| .set_msrctl1(TempMsrCtl0::kSample4Drop2) |
| .set_msrctl2(TempMsrCtl0::kSample4Drop2) |
| .set_msrctl3(TempMsrCtl0::kSample4Drop2) |
| .WriteTo(&mmio_); |
| |
| return StartThread(); |
| } |
| |
| 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_); |
| } |
| |
| float MtkThermal::RawToTemperature(uint32_t raw, uint32_t sensor) { |
| int32_t vts = cal2_fuse_.get_vts3(); |
| if (sensor == 0) { |
| vts = cal0_fuse_.get_vts0(); |
| } else if (sensor == 1) { |
| vts = cal0_fuse_.get_vts1(); |
| } else if (sensor == 2) { |
| vts = cal2_fuse_.get_vts2(); |
| } |
| |
| // See misc/mediatek/thermal/mt8167/mtk_ts_cpu.c in the Linux kernel source. |
| int32_t gain = 10000 + FixedPoint(cal1_fuse_.get_adc_gain()); |
| int32_t vts_with_gain = RawWithGain(vts - cal1_fuse_.get_adc_offset(), gain); |
| int32_t slope = cal0_fuse_.slope_sign() == 0 ? cal0_fuse_.slope() : -cal0_fuse_.slope(); |
| |
| int32_t temp_c = ((RawWithGain(raw - cal1_fuse_.get_adc_offset(), gain) - vts_with_gain) * 5) / 6; |
| temp_c = (temp_c * 100) / (165 + (cal1_fuse_.id() == 0 ? 0 : slope)); |
| return static_cast<float>(cal0_fuse_.temp_offset() - temp_c) / 10.0f; |
| } |
| |
| uint32_t MtkThermal::TemperatureToRaw(float temp, uint32_t sensor) { |
| int32_t vts = cal2_fuse_.get_vts3(); |
| if (sensor == 0) { |
| vts = cal0_fuse_.get_vts0(); |
| } else if (sensor == 1) { |
| vts = cal0_fuse_.get_vts1(); |
| } else if (sensor == 2) { |
| vts = cal2_fuse_.get_vts2(); |
| } |
| |
| int32_t gain = 10000 + FixedPoint(cal1_fuse_.get_adc_gain()); |
| int32_t vts_with_gain = RawWithGain(vts - cal1_fuse_.get_adc_offset(), gain); |
| int32_t slope = cal0_fuse_.slope_sign() == 0 ? cal0_fuse_.slope() : -cal0_fuse_.slope(); |
| |
| int32_t temp_c = static_cast<int32_t>(cal0_fuse_.temp_offset()) - |
| static_cast<int32_t>(std::round(temp * 10.0f)); |
| temp_c = (temp_c * (165 + (cal1_fuse_.id() == 0 ? 0 : slope))) / 100; |
| return TempWithoutGain(((temp_c * 6) / 5) + vts_with_gain, gain) + cal1_fuse_.get_adc_offset(); |
| } |
| |
| uint32_t MtkThermal::GetRawHot(float temp) { |
| // Find the ADC value corresponding to this temperature for each sensor. ADC values are |
| // inversely proportional to temperature, so the maximum represents the lowest temperature |
| // required to hit the trip point. |
| |
| uint32_t raw_max = 0; |
| for (uint32_t i = 0; i < kSensorCount; i++) { |
| uint32_t raw = TemperatureToRaw(temp, i); |
| if (raw > raw_max) { |
| raw_max = raw; |
| } |
| } |
| |
| return raw_max; |
| } |
| |
| uint32_t MtkThermal::GetRawCold(float temp) { |
| uint32_t raw_min = UINT32_MAX; |
| for (uint32_t i = 0; i < kSensorCount; i++) { |
| uint32_t raw = TemperatureToRaw(temp, i); |
| if (raw < raw_min) { |
| raw_min = raw; |
| } |
| } |
| |
| return raw_min; |
| } |
| |
| float MtkThermal::ReadTemperatureSensors() { |
| uint32_t sensor_values[kSensorCount]; |
| for (uint32_t i = 0; i < countof(sensor_values); i++) { |
| auto msr = TempMsr::Get(i).ReadFrom(&mmio_); |
| while (!msr.valid()) { |
| msr.ReadFrom(&mmio_); |
| } |
| |
| sensor_values[i] = msr.reading(); |
| } |
| |
| float temp = RawToTemperature(sensor_values[0], 0); |
| for (uint32_t i = 1; i < countof(sensor_values); i++) { |
| float sensor_temp = RawToTemperature(sensor_values[i], i); |
| if (sensor_temp > temp) { |
| temp = sensor_temp; |
| } |
| } |
| |
| return temp; |
| } |
| |
| zx_status_t MtkThermal::SetDvfsOpp(uint16_t op_idx) { |
| const fuchsia_hardware_thermal_OperatingPoint& opps = |
| thermal_info_.opps[fuchsia_hardware_thermal_PowerDomain_BIG_CLUSTER_POWER_DOMAIN]; |
| if (op_idx >= opps.count) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| uint32_t new_freq = opps.opp[op_idx].freq_hz; |
| uint32_t new_volt = opps.opp[op_idx].volt_uv; |
| |
| if (new_volt > VprocCon10::kMaxVoltageUv || new_volt < VprocCon10::kMinVoltageUv) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| fbl::AutoLock lock(&dvfs_lock_); |
| |
| 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; |
| } |
| |
| // Switch to a stable clock before changing the ARMPLL frequency. |
| auto infra_mux = InfraCfgClkMux::Get().ReadFrom(&infracfg_mmio_); |
| infra_mux.set_ifr_mux_sel(InfraCfgClkMux::kIfrClk26M).WriteTo(&infracfg_mmio_); |
| |
| armpll.set_frequency(new_freq).WriteTo(&pll_mmio_); |
| |
| // Wait for the PLL to stabilize. |
| zx::nanosleep(zx::deadline_after(zx::usec(20))); |
| |
| if (new_freq > old_freq) { |
| PmicWrite(vproc.reg_value(), vproc.reg_addr()); |
| infra_mux.set_ifr_mux_sel(InfraCfgClkMux::kIfrClkArmPll).WriteTo(&infracfg_mmio_); |
| } else { |
| infra_mux.set_ifr_mux_sel(InfraCfgClkMux::kIfrClkArmPll).WriteTo(&infracfg_mmio_); |
| PmicWrite(vproc.reg_value(), vproc.reg_addr()); |
| } |
| |
| current_op_idx_ = op_idx; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t MtkThermal::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_Device_dispatch(this, txn, msg, &fidl_ops); |
| } |
| |
| zx_status_t MtkThermal::GetInfo(fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_DeviceGetInfo_reply(txn, ZX_ERR_NOT_SUPPORTED, nullptr); |
| } |
| |
| zx_status_t MtkThermal::GetDeviceInfo(fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_DeviceGetDeviceInfo_reply(txn, ZX_OK, &thermal_info_); |
| } |
| |
| zx_status_t MtkThermal::GetDvfsInfo(fuchsia_hardware_thermal_PowerDomain power_domain, |
| fidl_txn_t* txn) { |
| if (power_domain != fuchsia_hardware_thermal_PowerDomain_BIG_CLUSTER_POWER_DOMAIN) { |
| fuchsia_hardware_thermal_DeviceGetDvfsInfo_reply(txn, ZX_ERR_NOT_SUPPORTED, nullptr); |
| } |
| |
| const fuchsia_hardware_thermal_OperatingPoint* info = |
| &thermal_info_.opps[fuchsia_hardware_thermal_PowerDomain_BIG_CLUSTER_POWER_DOMAIN]; |
| return fuchsia_hardware_thermal_DeviceGetDvfsInfo_reply(txn, ZX_OK, info); |
| } |
| |
| zx_status_t MtkThermal::GetTemperatureCelsius(fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_DeviceGetTemperatureCelsius_reply(txn, ZX_OK, |
| ReadTemperatureSensors()); |
| } |
| |
| zx_status_t MtkThermal::GetStateChangeEvent(fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_DeviceGetStateChangeEvent_reply(txn, ZX_ERR_NOT_SUPPORTED, |
| ZX_HANDLE_INVALID); |
| } |
| |
| zx_status_t MtkThermal::GetStateChangePort(fidl_txn_t* txn) { |
| zx::port dup; |
| zx_status_t status = GetPort(&dup); |
| return fuchsia_hardware_thermal_DeviceGetStateChangePort_reply(txn, status, dup.release()); |
| } |
| |
| zx_status_t MtkThermal::SetTripCelsius(uint32_t id, float temp, fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_DeviceSetTripCelsius_reply(txn, ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| zx_status_t MtkThermal::GetDvfsOperatingPoint(fuchsia_hardware_thermal_PowerDomain power_domain, |
| fidl_txn_t* txn) { |
| if (power_domain != fuchsia_hardware_thermal_PowerDomain_BIG_CLUSTER_POWER_DOMAIN) { |
| fuchsia_hardware_thermal_DeviceGetDvfsOperatingPoint_reply(txn, ZX_ERR_NOT_SUPPORTED, 0); |
| } |
| |
| return fuchsia_hardware_thermal_DeviceGetDvfsOperatingPoint_reply(txn, ZX_OK, get_dvfs_opp()); |
| } |
| |
| zx_status_t MtkThermal::SetDvfsOperatingPoint(uint16_t op_idx, |
| fuchsia_hardware_thermal_PowerDomain power_domain, |
| fidl_txn_t* txn) { |
| if (power_domain != fuchsia_hardware_thermal_PowerDomain_BIG_CLUSTER_POWER_DOMAIN) { |
| fuchsia_hardware_thermal_DeviceSetDvfsOperatingPoint_reply(txn, ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| return fuchsia_hardware_thermal_DeviceSetDvfsOperatingPoint_reply(txn, SetDvfsOpp(op_idx)); |
| } |
| |
| zx_status_t MtkThermal::GetFanLevel(fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_DeviceGetFanLevel_reply(txn, ZX_ERR_NOT_SUPPORTED, 0); |
| } |
| |
| zx_status_t MtkThermal::SetFanLevel(uint32_t fan_level, fidl_txn_t* txn) { |
| return fuchsia_hardware_thermal_DeviceSetFanLevel_reply(txn, ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| zx_status_t MtkThermal::SetTripPoint(size_t trip_pt) { |
| zx_port_packet_t packet; |
| packet.type = ZX_PKT_TYPE_USER; |
| packet.key = trip_pt; |
| |
| zx_status_t status = port_.queue(&packet); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Faild to queue packet", __FILE__); |
| return status; |
| } |
| |
| uint32_t raw_hot = 0; |
| uint32_t raw_cold = 0xfff; |
| |
| if (trip_pt > 0) { |
| raw_cold = GetRawCold(thermal_info_.trip_point_info[trip_pt - 1].down_temp_celsius); |
| } |
| if (trip_pt < thermal_info_.num_trip_points - 1) { |
| raw_hot = GetRawHot(thermal_info_.trip_point_info[trip_pt + 1].up_temp_celsius); |
| } |
| |
| // Update the hot and cold interrupt thresholds for the new trip point. |
| TempHotThreshold::Get().ReadFrom(&mmio_).set_threshold(raw_hot).WriteTo(&mmio_); |
| TempHotToNormalThreshold::Get().ReadFrom(&mmio_).set_threshold(raw_hot).WriteTo(&mmio_); |
| TempColdThreshold::Get().ReadFrom(&mmio_).set_threshold(raw_cold).WriteTo(&mmio_); |
| |
| return ZX_OK; |
| } |
| |
| int MtkThermal::Thread() { |
| const fuchsia_hardware_thermal_ThermalTemperatureInfo* trip_pts = thermal_info_.trip_point_info; |
| |
| TempProtCtl::Get().ReadFrom(&mmio_).set_strategy(TempProtCtl::kStrategyMaximum).WriteTo(&mmio_); |
| TempProtStage3::Get() |
| .FromValue(0) |
| .set_threshold(GetRawHot(thermal_info_.critical_temp_celsius)) |
| .WriteTo(&mmio_); |
| |
| float temp = ReadTemperatureSensors(); |
| TempMsrCtl1::Get().ReadFrom(&mmio_).pause_real().WriteTo(&mmio_); |
| |
| // Set the initial trip point based on the current temperature. |
| size_t trip_pt = 0; |
| for (; trip_pt < thermal_info_.num_trip_points - 1; trip_pt++) { |
| if (temp < trip_pts[trip_pt + 1].up_temp_celsius) { |
| break; |
| } |
| } |
| |
| size_t last_trip_pt = trip_pt; |
| SetTripPoint(trip_pt); |
| |
| TempMonInt::Get() |
| .ReadFrom(&mmio_) |
| .set_hot_en_0(1) |
| .set_cold_en_0(1) |
| .set_hot_en_1(1) |
| .set_cold_en_1(1) |
| .set_hot_en_2(1) |
| .set_cold_en_2(1) |
| .set_stage_3_en(1) |
| .WriteTo(&mmio_); |
| |
| TempMsrCtl1::Get().ReadFrom(&mmio_).resume_real().WriteTo(&mmio_); |
| |
| while (1) { |
| zx_status_t status = WaitForInterrupt(); |
| if (status == ZX_ERR_CANCELED) { |
| return thrd_success; |
| } else if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: IRQ wait failed", __FILE__); |
| return thrd_error; |
| } |
| |
| auto int_status = TempMonIntStatus::Get().ReadFrom(&mmio_); |
| |
| auto int_enable = TempMonInt::Get().ReadFrom(&mmio_); |
| uint32_t int_enable_old = int_enable.reg_value(); |
| int_enable.set_reg_value(0).WriteTo(&mmio_); |
| |
| // Read the current temperature then pause periodic measurements so we don't get out of sync |
| // with the hardware. |
| temp = ReadTemperatureSensors(); |
| TempMsrCtl1::Get().ReadFrom(&mmio_).pause_real().WriteTo(&mmio_); |
| |
| if (int_status.stage_3()) { |
| trip_pt = thermal_info_.num_trip_points - 1; |
| if (SetDvfsOpp(0) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to set safe operating point", __FILE__); |
| return thrd_error; |
| } |
| } else if (int_status.hot_0() || int_status.hot_1() || int_status.hot_2()) { |
| // Skip to the appropriate trip point for the current temperature. |
| for (; trip_pt < thermal_info_.num_trip_points - 1; trip_pt++) { |
| if (temp < trip_pts[trip_pt + 1].up_temp_celsius) { |
| break; |
| } |
| } |
| } else if (int_status.cold_0() || int_status.cold_1() || int_status.cold_2()) { |
| for (; trip_pt > 0; trip_pt--) { |
| if (temp > trip_pts[trip_pt - 1].down_temp_celsius) { |
| break; |
| } |
| } |
| } |
| |
| if (trip_pt != last_trip_pt) { |
| SetTripPoint(trip_pt); |
| } |
| |
| last_trip_pt = trip_pt; |
| |
| int_enable.set_reg_value(int_enable_old).WriteTo(&mmio_); |
| TempMsrCtl1::Get().ReadFrom(&mmio_).resume_real().WriteTo(&mmio_); |
| } |
| |
| return thrd_success; |
| } |
| |
| zx_status_t MtkThermal::WaitForInterrupt() { return irq_.wait(nullptr); } |
| |
| zx_status_t MtkThermal::StartThread() { |
| return thrd_status_to_zx_status(thrd_create_with_name( |
| &thread_, [](void* arg) -> int { return reinterpret_cast<MtkThermal*>(arg)->Thread(); }, this, |
| "mtk-thermal-thread")); |
| } |
| |
| zx_status_t MtkThermal::StopThread() { |
| irq_.destroy(); |
| JoinThread(); |
| return ZX_OK; |
| } |
| |
| void MtkThermal::DdkRelease() { |
| StopThread(); |
| delete this; |
| } |
| |
| } // namespace thermal |
| |
| static constexpr zx_driver_ops_t mtk_thermal_driver_ops = []() -> zx_driver_ops_t { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = thermal::MtkThermal::Create; |
| return ops; |
| }(); |
| |
| ZIRCON_DRIVER(mtk_thermal, mtk_thermal_driver_ops, "zircon", "0.1"); |