blob: 4be6ef3034ab236757db841dd5a3fc80773aa02c [file] [log] [blame]
// Copyright 2019 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 "as370-thermal.h"
#include <lib/device-protocol/pdev.h>
#include <lib/zx/time.h>
#include <ddk/device.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <ddktl/fidl.h>
#include <ddktl/protocol/composite.h>
#include <fbl/alloc_checker.h>
#include "as370-thermal-reg.h"
#include "src/devices/thermal/drivers/as370-thermal/as370-thermal-bind.h"
namespace {
constexpr uint32_t kEocLoopTimeout = 20000;
constexpr zx::duration kEocLoopSleepTime = zx::usec(100);
constexpr float SensorReadingToTemperature(int32_t reading) {
reading = reading * 251802 / 4096 - 85525;
return static_cast<float>(reading) / 1000.0f;
}
} // namespace
namespace thermal {
using llcpp::fuchsia::hardware::thermal::OperatingPoint;
zx_status_t As370Thermal::Create(void* ctx, zx_device_t* parent) {
ddk::CompositeProtocolClient composite(parent);
if (!composite.is_valid()) {
zxlogf(ERROR, "%s: Failed to get composite protocol", __func__);
return ZX_ERR_NO_RESOURCES;
}
ddk::PDev pdev(composite);
if (!pdev.is_valid()) {
zxlogf(ERROR, "%s: Failed to get platform device protocol", __func__);
return ZX_ERR_NO_RESOURCES;
}
ddk::ClockProtocolClient cpu_clock(composite, "clock");
if (!cpu_clock.is_valid()) {
zxlogf(ERROR, "%s: Failed to get clock protocol", __func__);
return ZX_ERR_NO_RESOURCES;
}
ddk::PowerProtocolClient cpu_power(composite, "power");
if (!cpu_power.is_valid()) {
zxlogf(ERROR, "%s: Failed to get power protocol", __func__);
return ZX_ERR_NO_RESOURCES;
}
std::optional<ddk::MmioBuffer> mmio;
zx_status_t status = pdev.MapMmio(0, &mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to map MMIO: %d", __func__, status);
return status;
}
size_t actual_size = 0;
ThermalDeviceInfo device_info = {};
if ((status = device_get_metadata(parent, DEVICE_METADATA_THERMAL_CONFIG, &device_info,
sizeof(device_info), &actual_size)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to get metadata: %d", __func__, status);
return status;
}
if (actual_size != sizeof(device_info)) {
zxlogf(ERROR, "%s: Metadata size mismatch", __func__);
return ZX_ERR_BAD_STATE;
}
fbl::AllocChecker ac;
auto device = fbl::make_unique_checked<As370Thermal>(&ac, parent, *std::move(mmio), device_info,
cpu_clock, cpu_power);
if (!ac.check()) {
zxlogf(ERROR, "%s: Failed to allocate device memory", __func__);
return ZX_ERR_NO_MEMORY;
}
if ((status = device->Init()) != ZX_OK) {
return status;
}
if ((status = device->DdkAdd("as370-thermal")) != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed: %d", __func__, status);
return status;
}
__UNUSED auto* dummy = device.release();
return ZX_OK;
}
zx_status_t As370Thermal::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
llcpp::fuchsia::hardware::thermal::Device::Dispatch(this, msg, &transaction);
return transaction.Status();
}
void As370Thermal::GetInfo(GetInfoCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, nullptr);
}
void As370Thermal::GetDeviceInfo(GetDeviceInfoCompleter::Sync& completer) {
ThermalDeviceInfo device_info_copy = device_info_;
completer.Reply(ZX_OK, fidl::unowned_ptr(&device_info_copy));
}
void As370Thermal::GetDvfsInfo(PowerDomain power_domain, GetDvfsInfoCompleter::Sync& completer) {
if (power_domain != PowerDomain::BIG_CLUSTER_POWER_DOMAIN) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, nullptr);
} else {
OperatingPoint dvfs_info_copy = device_info_.opps[static_cast<uint32_t>(power_domain)];
completer.Reply(ZX_OK, fidl::unowned_ptr(&dvfs_info_copy));
}
}
void As370Thermal::GetTemperatureCelsius(GetTemperatureCelsiusCompleter::Sync& completer) {
PvtCtrl::Get()
.ReadFrom(&mmio_)
.set_pmos_sel(0)
.set_nmos_sel(0)
.set_voltage_sel(0)
.set_temperature_sel(1)
.WriteTo(&mmio_)
.set_enable(1)
.WriteTo(&mmio_)
.set_power_down(0)
.WriteTo(&mmio_);
auto pvt_status = PvtStatus::Get().FromValue(0);
for (uint32_t i = 0; i < kEocLoopTimeout && pvt_status.ReadFrom(&mmio_).eoc() == 0; i++) {
zx::nanosleep(zx::deadline_after(kEocLoopSleepTime));
}
PvtCtrl::Get().FromValue(0).set_power_down(1).WriteTo(&mmio_);
if (pvt_status.eoc() == 0) {
zxlogf(ERROR, "%s: Timed out waiting for temperature reading", __func__);
completer.Reply(ZX_ERR_TIMED_OUT, 0.0f);
} else {
completer.Reply(ZX_OK, SensorReadingToTemperature(pvt_status.data()));
}
}
void As370Thermal::GetStateChangeEvent(GetStateChangeEventCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
void As370Thermal::GetStateChangePort(GetStateChangePortCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
void As370Thermal::SetTripCelsius(uint32_t id, float temp,
SetTripCelsiusCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
}
void As370Thermal::GetDvfsOperatingPoint(PowerDomain power_domain,
GetDvfsOperatingPointCompleter::Sync& completer) {
if (power_domain != PowerDomain::BIG_CLUSTER_POWER_DOMAIN) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, 0);
} else {
completer.Reply(ZX_OK, operating_point_);
}
}
void As370Thermal::SetDvfsOperatingPoint(uint16_t op_idx, PowerDomain power_domain,
SetDvfsOperatingPointCompleter::Sync& completer) {
if (power_domain != PowerDomain::BIG_CLUSTER_POWER_DOMAIN) {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
} else if (op_idx >= device_info_.opps[static_cast<uint32_t>(power_domain)].count) {
completer.Reply(ZX_ERR_INVALID_ARGS);
} else {
completer.Reply(SetOperatingPoint(op_idx));
}
}
void As370Thermal::GetFanLevel(GetFanLevelCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, 0);
}
void As370Thermal::SetFanLevel(uint32_t fan_level, SetFanLevelCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
}
zx_status_t As370Thermal::Init() {
PvtCtrl::Get().FromValue(0).set_power_down(1).WriteTo(&mmio_);
const OperatingPoint& operating_points =
device_info_.opps[static_cast<uint32_t>(PowerDomain::BIG_CLUSTER_POWER_DOMAIN)];
const auto max_operating_point = static_cast<uint16_t>(operating_points.count - 1);
zx_status_t status = cpu_power_.RegisterPowerDomain(
operating_points.opp[0].volt_uv, operating_points.opp[max_operating_point].volt_uv);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to register power domain: %d", __func__, status);
return status;
}
return SetOperatingPoint(max_operating_point);
}
zx_status_t As370Thermal::SetOperatingPoint(uint16_t op_idx) {
const auto& opps =
device_info_.opps[static_cast<uint32_t>(PowerDomain::BIG_CLUSTER_POWER_DOMAIN)].opp;
zx_status_t status;
uint32_t actual_voltage = 0;
if (opps[op_idx].freq_hz > opps[operating_point_].freq_hz) {
if ((status = cpu_power_.RequestVoltage(opps[op_idx].volt_uv, &actual_voltage)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to set voltage: %d", __func__, status);
return status;
}
if (actual_voltage != opps[op_idx].volt_uv) {
zxlogf(ERROR, "%s: Failed to set exact voltage: set %u, wanted %u", __func__, actual_voltage,
opps[op_idx].volt_uv);
return ZX_ERR_BAD_STATE;
}
if ((status = cpu_clock_.SetRate(opps[op_idx].freq_hz)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to set CPU frequency: %d", __func__, status);
return status;
}
} else {
if ((status = cpu_clock_.SetRate(opps[op_idx].freq_hz)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to set CPU frequency: %d", __func__, status);
return status;
}
if ((status = cpu_power_.RequestVoltage(opps[op_idx].volt_uv, &actual_voltage)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to set voltage: %d", __func__, status);
return status;
}
if (actual_voltage != opps[op_idx].volt_uv) {
zxlogf(ERROR, "%s: Failed to set exact voltage: set %u, wanted %u", __func__, actual_voltage,
opps[op_idx].volt_uv);
return ZX_ERR_BAD_STATE;
}
}
operating_point_ = op_idx;
return ZX_OK;
}
} // namespace thermal
static constexpr zx_driver_ops_t as370_thermal_driver_ops = []() -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = thermal::As370Thermal::Create;
return ops;
}();
ZIRCON_DRIVER(as370_thermal, as370_thermal_driver_ops, "zircon", "0.1");