blob: a10f5e147f0d6c36292e31f527aa430aeaca9b14 [file] [log] [blame]
// Copyright 2020 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 "vs680-thermal.h"
#include <lib/device-protocol/pdev.h>
#include <lib/zx/clock.h>
#include <algorithm>
#include <ddk/debug.h>
#include <ddk/platform-defs.h>
#include <ddktl/fidl.h>
#include <ddktl/protocol/composite.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include "src/devices/thermal/drivers/vs680-thermal/vs680-thermal-bind.h"
#include "vs680-thermal-reg.h"
namespace {
using ::llcpp::fuchsia::hardware::thermal::OperatingPoint;
using ::llcpp::fuchsia::hardware::thermal::OperatingPointEntry;
constexpr OperatingPoint kOperatingPoints = {
.opp =
fidl::Array<OperatingPointEntry, ::llcpp::fuchsia::hardware::thermal::MAX_DVFS_OPPS>{
// TODO(bradenkell): This is the initial CPU frequency coming out of the bootloader. Add
// the other operating points when we have more information.
OperatingPointEntry{.freq_hz = 1'800'000'000, .volt_uv = 800'000},
},
.latency = 0,
.count = 1,
};
} // namespace
namespace thermal {
zx_status_t Vs680Thermal::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;
}
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;
}
zx::interrupt interrupt;
if ((status = pdev.GetInterrupt(0, &interrupt)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to get interrupt: %d", __func__, status);
return status;
}
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, "thermal");
if (!cpu_power.is_valid()) {
zxlogf(ERROR, "%s: Failed to get power protocol", __func__);
return ZX_ERR_NO_RESOURCES;
}
fbl::AllocChecker ac;
auto device = fbl::make_unique_checked<Vs680Thermal>(&ac, parent, *std::move(mmio),
std::move(interrupt), 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("vs680-thermal")) != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed: %d", __func__, status);
// Init() started the interrupt thread, call DdkRelease to stop it and destroy the device
// object.
device->DdkRelease();
}
__UNUSED auto* _ = device.release();
return status;
}
void Vs680Thermal::DdkRelease() {
interrupt_.destroy();
thrd_join(thread_, nullptr);
delete this;
}
zx_status_t Vs680Thermal::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();
}
zx_status_t Vs680Thermal::Init() {
TsenStatus::Get().ReadFrom(&mmio_).set_int_en(0).WriteTo(&mmio_);
uint32_t max_volt_uv = kOperatingPoints.opp[0].volt_uv;
uint32_t min_volt_uv = kOperatingPoints.opp[0].volt_uv;
for (uint32_t i = 1; i < kOperatingPoints.count; i++) {
max_volt_uv = std::max(max_volt_uv, kOperatingPoints.opp[i].volt_uv);
min_volt_uv = std::min(min_volt_uv, kOperatingPoints.opp[i].volt_uv);
}
zx_status_t status = cpu_power_.RegisterPowerDomain(min_volt_uv, max_volt_uv);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to register VCPU power domain: %d", __func__, status);
return status;
}
if ((status = SetOperatingPoint(kOperatingPoints.count - 1)) != ZX_OK) {
return status;
}
auto cb = [](void* arg) { return reinterpret_cast<Vs680Thermal*>(arg)->TemperatureThread(); };
if (thrd_create_with_name(&thread_, cb, this, "vs680-thermal-thread") != thrd_success) {
zxlogf(ERROR, "%s: Failed to create IRQ thread", __func__);
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void Vs680Thermal::GetInfo(GetInfoCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
void Vs680Thermal::GetDeviceInfo(GetDeviceInfoCompleter::Sync& completer) {
// TODO(bradenkell): Implement GetDeviceInfo.
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
void Vs680Thermal::GetDvfsInfo(PowerDomain power_domain, GetDvfsInfoCompleter::Sync& completer) {
if (power_domain == PowerDomain::BIG_CLUSTER_POWER_DOMAIN) {
OperatingPoint operating_points_copy = kOperatingPoints;
completer.Reply(ZX_OK, fidl::unowned_ptr(&operating_points_copy));
} else {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
}
void Vs680Thermal::GetTemperatureCelsius(GetTemperatureCelsiusCompleter::Sync& completer) {
completer.Reply(ZX_OK, static_cast<float>(temperature_millicelsius_) / 1000.0f);
}
void Vs680Thermal::GetStateChangeEvent(GetStateChangeEventCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
void Vs680Thermal::GetStateChangePort(GetStateChangePortCompleter::Sync& completer) {
// TODO(bradenkell): Implement GetStateChangePort.
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
void Vs680Thermal::SetTripCelsius(uint32_t id, float temp,
SetTripCelsiusCompleter::Sync& completer) {
// TODO(bradenkell): Implement SetTripCelsius.
completer.Reply(ZX_ERR_NOT_SUPPORTED);
}
void Vs680Thermal::GetDvfsOperatingPoint(PowerDomain power_domain,
GetDvfsOperatingPointCompleter::Sync& completer) {
if (power_domain == PowerDomain::BIG_CLUSTER_POWER_DOMAIN) {
completer.Reply(ZX_OK, operating_point_);
} else {
completer.Reply(ZX_ERR_NOT_SUPPORTED, 0);
}
}
void Vs680Thermal::SetDvfsOperatingPoint(uint16_t op_idx, PowerDomain power_domain,
SetDvfsOperatingPointCompleter::Sync& completer) {
if (power_domain == PowerDomain::BIG_CLUSTER_POWER_DOMAIN) {
completer.Reply(SetOperatingPoint(op_idx));
} else {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
}
}
void Vs680Thermal::GetFanLevel(GetFanLevelCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, 0);
}
void Vs680Thermal::SetFanLevel(uint32_t fan_level, SetFanLevelCompleter::Sync& completer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
}
zx_status_t Vs680Thermal::SetOperatingPoint(uint16_t op_idx) {
if (op_idx >= kOperatingPoints.count) {
return ZX_ERR_OUT_OF_RANGE;
}
const OperatingPointEntry current = kOperatingPoints.opp[operating_point_];
const OperatingPointEntry next = kOperatingPoints.opp[op_idx];
zx_status_t status;
if (next.freq_hz > current.freq_hz) {
uint32_t actual_voltage = 0;
if ((status = cpu_power_.RequestVoltage(next.volt_uv, &actual_voltage))) {
zxlogf(ERROR, "%s: Failed to set CPU voltage to %u: %d", __func__, next.volt_uv, status);
return status;
}
if (actual_voltage != next.volt_uv) {
zxlogf(ERROR, "%s: Failed to set CPU voltage to %u", __func__, next.volt_uv);
return ZX_ERR_INTERNAL;
}
if ((status = cpu_clock_.SetRate(next.freq_hz)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to set CPU clock rate to %u: %d", __func__, next.freq_hz, status);
return status;
}
} else {
if ((status = cpu_clock_.SetRate(next.freq_hz)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to set CPU clock rate to %u: %d", __func__, next.freq_hz, status);
return status;
}
uint32_t actual_voltage = 0;
if ((status = cpu_power_.RequestVoltage(next.volt_uv, &actual_voltage))) {
zxlogf(ERROR, "%s: Failed to set CPU voltage to %u: %d", __func__, next.volt_uv, status);
return status;
}
if (actual_voltage != next.volt_uv) {
zxlogf(ERROR, "%s: Failed to set CPU voltage to %u", __func__, next.volt_uv);
return ZX_ERR_INTERNAL;
}
}
operating_point_ = op_idx;
return ZX_OK;
}
int Vs680Thermal::TemperatureThread() {
for (;;) {
const zx::time next_poll_time = zx::clock::get_monotonic() + poll_interval_;
TsenStatus::Get().ReadFrom(&mmio_).set_int_en(1).WriteTo(&mmio_);
TsenCtrl::Get().ReadFrom(&mmio_).set_ena(1).WriteTo(&mmio_).set_clk_en(1).WriteTo(&mmio_);
zx_status_t status = interrupt_.wait(nullptr);
if (status == ZX_ERR_CANCELED) {
break;
} else if (status != ZX_OK) {
zxlogf(INFO, "%s: Interrupt wait returned %d", __func__, status);
break;
}
const int64_t sensor_data = TsenData::Get().ReadFrom(&mmio_).data();
TsenStatus::Get().ReadFrom(&mmio_).set_data_rdy(0).WriteTo(&mmio_);
TsenCtrl::Get().ReadFrom(&mmio_).set_ena(0).WriteTo(&mmio_).set_clk_en(0).WriteTo(&mmio_);
int64_t temperature = (18439 * sensor_data) / 1000;
temperature = ((80705 - temperature) * sensor_data) / 1000;
temperature = ((185010 - temperature) * sensor_data) / 1000;
temperature = ((328430 - temperature) * sensor_data) / 1000;
temperature -= 48690;
temperature_millicelsius_ = temperature;
const zx::time current_time = zx::clock::get_monotonic();
if (next_poll_time > current_time) {
zx::nanosleep(zx::deadline_after(next_poll_time - current_time));
}
}
return thrd_success;
}
} // namespace thermal
static constexpr zx_driver_ops_t vs680_thermal_driver_ops = []() -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = thermal::Vs680Thermal::Create;
return ops;
}();
ZIRCON_DRIVER(vs680_thermal, vs680_thermal_driver_ops, "zircon", "0.1");