blob: b51a003c52e3c7ef88f0ea1904f51bd77ee0dc06 [file] [log] [blame]
// 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 "aml-thermal.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/metadata.h>
#include <ddktl/protocol/composite.h>
#include <fbl/auto_call.h>
#include <fbl/unique_ptr.h>
#include <hw/reg.h>
#include <string.h>
#include <threads.h>
#include <zircon/syscalls/port.h>
#include <utility>
namespace thermal {
zx_status_t AmlThermal::SetTarget(uint32_t opp_idx) {
if (opp_idx >= fuchsia_hardware_thermal_MAX_DVFS_OPPS) {
return ZX_ERR_INVALID_ARGS;
}
// Get current settings.
uint32_t old_voltage = voltage_regulator_->GetVoltage();
uint32_t old_frequency = cpufreq_scaling_->GetFrequency();
// Get new settings.
uint32_t new_voltage = opp_info_.opps[opp_idx].volt_uv;
uint32_t new_frequency = opp_info_.opps[opp_idx].freq_hz;
zxlogf(INFO, "Scaling from %d MHz, %u mV, --> %d MHz, %u mV\n", old_frequency / 1000000,
old_voltage / 1000, new_frequency / 1000000, new_voltage / 1000);
// If new settings are same as old, don't do anything.
if (new_frequency == old_frequency) {
return ZX_OK;
}
zx_status_t status;
// Increasing CPU Frequency from current value, so we first change the voltage.
if (new_frequency > old_frequency) {
status = voltage_regulator_->SetVoltage(new_voltage);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-thermal: Could not change CPU voltage: %d\n", status);
return status;
}
}
// Now let's change CPU frequency.
status = cpufreq_scaling_->SetFrequency(new_frequency);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-thermal: Could not change CPU frequency: %d\n", status);
// Failed to change CPU frequency, change back to old
// voltage before returning.
status = voltage_regulator_->SetVoltage(old_voltage);
if (status != ZX_OK) {
return status;
}
return status;
}
// Decreasing CPU Frequency from current value, changing voltage after frequency.
if (new_frequency < old_frequency) {
status = voltage_regulator_->SetVoltage(new_voltage);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-thermal: Could not change CPU voltage: %d\n", status);
return status;
}
}
return ZX_OK;
}
zx_status_t AmlThermal::Create(void* ctx, zx_device_t* device) {
ddk::CompositeProtocolClient composite(device);
if (!composite.is_valid()) {
zxlogf(ERROR, "%s: failed to get composite protocol\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
// zeroth component is pdev
zx_device_t* pdev;
size_t actual;
composite.GetComponents(&pdev, 1, &actual);
if (actual != 1) {
zxlogf(ERROR, "%s: failed to get pdev component\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
// Get the voltage-table & opp metadata.
aml_opp_info_t opp_info;
zx_status_t status =
device_get_metadata(device, DEVICE_METADATA_PRIVATE, &opp_info, sizeof(opp_info_), &actual);
if (status != ZX_OK || actual != sizeof(opp_info_)) {
zxlogf(ERROR, "aml-thermal: Could not get voltage-table metadata %d\n", status);
return status;
}
// Get the thermal policy metadata.
fuchsia_hardware_thermal_ThermalDeviceInfo thermal_config;
status = device_get_metadata(device, DEVICE_METADATA_THERMAL_CONFIG, &thermal_config,
sizeof(fuchsia_hardware_thermal_ThermalDeviceInfo), &actual);
if (status != ZX_OK || actual != sizeof(fuchsia_hardware_thermal_ThermalDeviceInfo)) {
zxlogf(ERROR, "aml-thermal: Could not get thermal config metadata %d\n", status);
return status;
}
fbl::AllocChecker ac;
auto tsensor = fbl::make_unique_checked<AmlTSensor>(&ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Initialize Temperature Sensor.
status = tsensor->InitSensor(pdev, thermal_config);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-thermal: Could not initialize Temperature Sensor: %d\n", status);
return status;
}
// Create the voltage regulator.
auto voltage_regulator = fbl::make_unique_checked<AmlVoltageRegulator>(&ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Initialize Temperature Sensor.
status = voltage_regulator->Init(pdev, &opp_info);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-thermal: Could not initialize Voltage Regulator: %d\n", status);
return status;
}
// Create the CPU frequency scaling object.
auto cpufreq_scaling = fbl::make_unique_checked<AmlCpuFrequency>(&ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Initialize CPU frequency scaling.
status = cpufreq_scaling->Init(device);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-thermal: Could not initialize CPU freq. scaling: %d\n", status);
return status;
}
auto thermal_device = fbl::make_unique_checked<AmlThermal>(
&ac, device, std::move(tsensor), std::move(voltage_regulator), std::move(cpufreq_scaling),
std::move(opp_info), std::move(thermal_config));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = thermal_device->DdkAdd("thermal");
if (status != ZX_OK) {
zxlogf(ERROR, "aml-thermal: Could not create thermal device: %d\n", status);
return status;
}
// Set the default CPU frequency.
// We could be running Zircon only, or thermal daemon might not
// run, so we manually set the CPU frequency here.
uint32_t opp_idx = thermal_device->thermal_config_.trip_point_info[0].big_cluster_dvfs_opp;
status = thermal_device->SetTarget(opp_idx);
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the memory for dev.
__UNUSED auto ptr = thermal_device.release();
return ZX_OK;
}
zx_status_t AmlThermal::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
return fuchsia_hardware_thermal_Device_dispatch(this, txn, msg, &fidl_ops);
}
zx_status_t AmlThermal::GetInfo(fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceGetInfo_reply(txn, ZX_ERR_NOT_SUPPORTED, nullptr);
}
zx_status_t AmlThermal::GetDeviceInfo(fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceGetDeviceInfo_reply(txn, ZX_OK, &thermal_config_);
}
zx_status_t AmlThermal::GetDvfsInfo(fuchsia_hardware_thermal_PowerDomain power_domain,
fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceGetDvfsInfo_reply(txn, ZX_ERR_NOT_SUPPORTED, nullptr);
}
zx_status_t AmlThermal::GetTemperatureCelsius(fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceGetTemperatureCelsius_reply(
txn, ZX_OK, tsensor_->ReadTemperatureCelsius());
}
zx_status_t AmlThermal::GetStateChangeEvent(fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceGetStateChangeEvent_reply(txn, ZX_ERR_NOT_SUPPORTED,
ZX_HANDLE_INVALID);
}
zx_status_t AmlThermal::GetStateChangePort(fidl_txn_t* txn) {
zx_handle_t handle;
zx_status_t status = tsensor_->GetStateChangePort(&handle);
return fuchsia_hardware_thermal_DeviceGetStateChangePort_reply(txn, status, handle);
}
zx_status_t AmlThermal::SetTripCelsius(uint32_t id, float temp, fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceSetTripCelsius_reply(txn, ZX_ERR_NOT_SUPPORTED);
}
zx_status_t AmlThermal::GetDvfsOperatingPoint(fuchsia_hardware_thermal_PowerDomain power_domain,
fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceGetDvfsOperatingPoint_reply(txn, ZX_ERR_NOT_SUPPORTED, 0);
}
zx_status_t AmlThermal::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) {
return fuchsia_hardware_thermal_DeviceSetDvfsOperatingPoint_reply(txn, ZX_ERR_INVALID_ARGS);
}
return fuchsia_hardware_thermal_DeviceSetDvfsOperatingPoint_reply(txn, SetTarget(op_idx));
}
zx_status_t AmlThermal::GetFanLevel(fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceGetFanLevel_reply(txn, ZX_ERR_NOT_SUPPORTED, 0);
}
zx_status_t AmlThermal::SetFanLevel(uint32_t fan_level, fidl_txn_t* txn) {
return fuchsia_hardware_thermal_DeviceSetFanLevel_reply(txn, ZX_ERR_NOT_SUPPORTED);
}
void AmlThermal::DdkUnbind() { DdkRemove(); }
void AmlThermal::DdkRelease() { delete this; }
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = AmlThermal::Create;
return ops;
}();
} // namespace thermal
// clang-format off
ZIRCON_DRIVER_BEGIN(aml_thermal, thermal::driver_ops, "aml-thermal", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S905D2),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_THERMAL),
ZIRCON_DRIVER_END(aml_thermal)