blob: 43ca7c4b91db8b875890ec2977f512131e2c28cc [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 "aml-cpu.h"
#include <lib/ddk/platform-defs.h>
#include <lib/driver/component/cpp/driver_base.h>
#include <lib/mmio/mmio.h>
#include <zircon/syscalls/smc.h>
#include <vector>
namespace amlogic_cpu {
namespace {
constexpr uint32_t kCpuGetDvfsTableIndexFuncId = 0x82000088;
constexpr uint64_t kDefaultClusterId = 0;
constexpr uint32_t kInitialOpp = fuchsia_hardware_cpu_ctrl::wire::kDeviceOperatingPointP0;
} // namespace
zx_status_t GetPopularVoltageTable(const zx::resource& smc_resource, uint32_t* metadata_type) {
if (smc_resource.is_valid()) {
zx_smc_parameters_t smc_params = {};
smc_params.func_id = kCpuGetDvfsTableIndexFuncId;
smc_params.arg1 = kDefaultClusterId;
zx_smc_result_t smc_result;
zx_status_t status = zx_smc_call(smc_resource.get(), &smc_params, &smc_result);
if (status != ZX_OK) {
FDF_LOG(ERROR, "zx_smc_call failed: %s", zx_status_get_string(status));
return status;
}
switch (smc_result.arg0) {
case amlogic_cpu::OppTable1:
*metadata_type = DEVICE_METADATA_AML_OP_1_POINTS;
break;
case amlogic_cpu::OppTable2:
*metadata_type = DEVICE_METADATA_AML_OP_2_POINTS;
break;
case amlogic_cpu::OppTable3:
*metadata_type = DEVICE_METADATA_AML_OP_3_POINTS;
break;
default:
*metadata_type = DEVICE_METADATA_AML_OP_POINTS;
break;
}
FDF_LOG(INFO, "Dvfs using table%ld.\n", smc_result.arg0);
}
return ZX_OK;
}
zx::result<AmlCpuConfiguration> LoadConfiguration(ddk::PDevFidl& pdev) {
zx_status_t st;
AmlCpuConfiguration config;
std::optional<fdf::MmioBuffer> mmio_buffer;
st = pdev.MapMmio(0, &mmio_buffer);
if (st != ZX_OK) {
FDF_LOG(ERROR, "aml-cpu: Failed to map mmio: %s", zx_status_get_string(st));
return zx::error(st);
}
config.info = {};
st = pdev.GetDeviceInfo(&config.info);
if (st != ZX_OK) {
FDF_LOG(ERROR, "Failed to get DeviceInfo: %s", zx_status_get_string(st));
return zx::error(st);
}
zx::resource smc_resource = {};
config.metadata_type = DEVICE_METADATA_AML_OP_POINTS;
config.fragments_per_pf_domain = kFragmentsPerPfDomain;
zx_off_t cpu_version_offset = kCpuVersionOffset;
if (config.info.pid == PDEV_PID_AMLOGIC_A5) {
st = pdev.GetSmc(0, &smc_resource);
if (st != ZX_OK) {
FDF_LOG(ERROR, "Failed to get smc: %s", zx_status_get_string(st));
return zx::error(st);
}
st = GetPopularVoltageTable(smc_resource, &config.metadata_type);
if (st != ZX_OK) {
FDF_LOG(ERROR, "Failed to get popular voltage table: %s", zx_status_get_string(st));
return zx::error(st);
}
config.fragments_per_pf_domain = kFragmentsPerPfDomainA5;
cpu_version_offset = kCpuVersionOffsetA5;
} else if (config.info.pid == PDEV_PID_AMLOGIC_A1) {
config.fragments_per_pf_domain = kFragmentsPerPfDomainA1;
cpu_version_offset = kCpuVersionOffsetA1;
}
config.cpu_version_packed = mmio_buffer->Read32(cpu_version_offset);
config.has_div16_clients = config.fragments_per_pf_domain == kFragmentsPerPfDomain;
// For A1, the CPU power is VDD_CORE, which share with other module.
// The fixed voltage is 0.8v, we can't adjust it dynamically.
config.has_power_client = config.info.pid != PDEV_PID_AMLOGIC_A1;
return zx::ok(config);
}
std::vector<operating_point_t> PerformanceDomainOpPoints(const perf_domain_t& perf_domain,
std::vector<operating_point>& op_points) {
std::vector<operating_point_t> pd_op_points;
std::copy_if(op_points.begin(), op_points.end(), std::back_inserter(pd_op_points),
[&perf_domain](const operating_point_t& op) { return op.pd_id == perf_domain.id; });
// Order operating points from highest frequency to lowest because Operating Point 0 is the
// fastest.
std::sort(pd_op_points.begin(), pd_op_points.end(),
[](const operating_point_t& a, const operating_point_t& b) {
// Use voltage as a secondary sorting key.
if (a.freq_hz == b.freq_hz) {
return a.volt_uv > b.volt_uv;
}
return a.freq_hz > b.freq_hz;
});
return pd_op_points;
}
zx_status_t AmlCpu::SetCurrentOperatingPointInternal(uint32_t requested_opp, uint32_t* out_opp) {
std::scoped_lock lock(lock_);
if (requested_opp >= operating_points_.size()) {
FDF_LOG(ERROR, "Requested opp is out of bounds, opp = %u\n", requested_opp);
return ZX_ERR_OUT_OF_RANGE;
}
if (!out_opp) {
FDF_LOG(ERROR, "out_opp may not be null");
return ZX_ERR_INVALID_ARGS;
}
// There is no condition under which this function will return ZX_OK but out_opp will not
// be requested_opp so we're going to go ahead and set that up front.
*out_opp = requested_opp;
const operating_point_t& target_opp = operating_points_[requested_opp];
const operating_point_t& initial_opp = operating_points_[current_operating_point_];
FDF_LOG(INFO, "Scaling from %u MHz %u mV to %u MHz %u mV", initial_opp.freq_hz / 1000000,
initial_opp.volt_uv / 1000, target_opp.freq_hz / 1000000, target_opp.volt_uv / 1000);
if (initial_opp.freq_hz == target_opp.freq_hz && initial_opp.volt_uv == target_opp.volt_uv) {
// Nothing to be done.
return ZX_OK;
}
if (target_opp.volt_uv > initial_opp.volt_uv) {
// If we're increasing the voltage we need to do it before setting the
// frequency.
ZX_ASSERT(pwr_.is_valid());
fidl::WireResult result = pwr_->RequestVoltage(target_opp.volt_uv);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send RequestVoltage request: %s", result.status_string());
return result.error().status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "RequestVoltage call returned error: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
uint32_t actual_voltage = result->value()->actual_voltage;
if (actual_voltage != target_opp.volt_uv) {
FDF_LOG(ERROR, "Actual voltage does not match, requested = %u, got = %u", target_opp.volt_uv,
actual_voltage);
return ZX_OK;
}
}
// Set the frequency next.
fidl::WireResult result = cpuscaler_->SetRate(target_opp.freq_hz);
if (!result.ok() || result->is_error()) {
FDF_LOG(ERROR, "Could not set CPU frequency: %s", result.FormatDescription().c_str());
// Put the voltage back if frequency scaling fails.
if (pwr_.is_valid()) {
fidl::WireResult result = pwr_->RequestVoltage(initial_opp.volt_uv);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send RequestVoltage request: %s", result.status_string());
return result.error().status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to reset CPU voltage, st = %s, Voltage and frequency mismatch!",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
if (!result.ok()) {
return result.status();
}
return result->error_value();
}
// If we're decreasing the voltage, then we do it after the frequency has been
// reduced to avoid undervolt conditions.
if (target_opp.volt_uv < initial_opp.volt_uv) {
ZX_ASSERT(pwr_.is_valid());
fidl::WireResult result = pwr_->RequestVoltage(target_opp.volt_uv);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send RequestVoltage request: %s", result.status_string());
return result.error().status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "RequestVoltage call returned error: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
uint32_t actual_voltage = result->value()->actual_voltage;
if (actual_voltage != target_opp.volt_uv) {
FDF_LOG(ERROR,
"Failed to set cpu voltage, requested = %u, got = %u. "
"Voltage and frequency mismatch!",
target_opp.volt_uv, actual_voltage);
return ZX_OK;
}
}
FDF_LOG(INFO, "Success\n");
current_operating_point_ = requested_opp;
return ZX_OK;
}
zx_status_t AmlCpu::Init(fidl::ClientEnd<fuchsia_hardware_clock::Clock> plldiv16,
fidl::ClientEnd<fuchsia_hardware_clock::Clock> cpudiv16,
fidl::ClientEnd<fuchsia_hardware_clock::Clock> cpuscaler,
fidl::ClientEnd<fuchsia_hardware_power::Device> pwr) {
cpuscaler_.Bind(std::move(cpuscaler));
if (plldiv16.is_valid()) {
plldiv16_.Bind(std::move(plldiv16));
fidl::WireResult result = plldiv16_->Enable();
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send request to enable plldiv16: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to enable plldiv16: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
}
if (cpudiv16.is_valid()) {
cpudiv16_.Bind(std::move(cpudiv16));
fidl::WireResult result = cpudiv16_->Enable();
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send request to enable cpudiv16: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to enable cpudiv16: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
}
if (pwr.is_valid()) {
pwr_.Bind(std::move(pwr));
fidl::WireResult voltage_range_result = pwr_->GetSupportedVoltageRange();
if (!voltage_range_result.ok()) {
FDF_LOG(ERROR, "Failed to send GetSupportedVoltageRange request: %s",
voltage_range_result.status_string());
return voltage_range_result.status();
}
if (voltage_range_result->is_error()) {
FDF_LOG(ERROR, "GetSupportedVoltageRange returned error: %s",
zx_status_get_string(voltage_range_result->error_value()));
return voltage_range_result->error_value();
}
uint32_t max_voltage = voltage_range_result->value()->max;
uint32_t min_voltage = voltage_range_result->value()->min;
fidl::WireResult register_result = pwr_->RegisterPowerDomain(min_voltage, max_voltage);
if (!register_result.ok()) {
FDF_LOG(ERROR, "Failed to send RegisterPowerDomain request: %s",
register_result.status_string());
return voltage_range_result.status();
}
if (register_result->is_error()) {
FDF_LOG(ERROR, "RegisterPowerDomain returned error: %s",
zx_status_get_string(register_result->error_value()));
return register_result->error_value();
}
}
uint32_t actual;
// Returns ZX_ERR_OUT_OF_RANGE if `operating_points_` is empty.
zx_status_t result = SetCurrentOperatingPointInternal(kInitialOpp, &actual);
if (result != ZX_OK) {
FDF_LOG(ERROR, "Failed to set initial opp, st = %d", result);
return result;
}
if (actual != kInitialOpp) {
FDF_LOG(ERROR, "Failed to set initial opp, requested = %u, actual = %u", kInitialOpp, actual);
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void AmlCpu::SetCpuInfo(uint32_t cpu_version_packed) {
const uint8_t major_revision = (cpu_version_packed >> 24) & 0xff;
const uint8_t minor_revision = (cpu_version_packed >> 8) & 0xff;
const uint8_t cpu_package_id = (cpu_version_packed >> 20) & 0x0f;
FDF_LOG(INFO, "major revision number: 0x%x", major_revision);
FDF_LOG(INFO, "minor revision number: 0x%x", minor_revision);
FDF_LOG(INFO, "cpu package id number: 0x%x", cpu_package_id);
cpu_info_.CreateUint("cpu_major_revision", major_revision, &inspector_);
cpu_info_.CreateUint("cpu_minor_revision", minor_revision, &inspector_);
cpu_info_.CreateUint("cpu_package_id", cpu_package_id, &inspector_);
}
void AmlCpu::GetOperatingPointInfo(GetOperatingPointInfoRequestView request,
GetOperatingPointInfoCompleter::Sync& completer) {
auto operating_points = GetOperatingPoints();
if (request->opp >= operating_points.size()) {
FDF_LOG(INFO, "Requested an operating point that's out of bounds, %u\n", request->opp);
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
fuchsia_hardware_cpu_ctrl::wire::CpuOperatingPointInfo result;
result.frequency_hz = operating_points[request->opp].freq_hz;
result.voltage_uv = operating_points[request->opp].volt_uv;
completer.ReplySuccess(result);
}
void AmlCpu::SetCurrentOperatingPoint(SetCurrentOperatingPointRequestView request,
SetCurrentOperatingPointCompleter::Sync& completer) {
uint32_t out_opp = 0;
zx_status_t status = SetCurrentOperatingPointInternal(request->requested_opp, &out_opp);
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
completer.ReplySuccess(out_opp);
}
}
void AmlCpu::GetCurrentOperatingPoint(GetCurrentOperatingPointCompleter::Sync& completer) {
completer.Reply(GetCurrentOperatingPoint());
}
void AmlCpu::GetOperatingPointCount(GetOperatingPointCountCompleter::Sync& completer) {
completer.ReplySuccess(GetOperatingPointCount());
}
void AmlCpu::GetNumLogicalCores(GetNumLogicalCoresCompleter::Sync& completer) {
completer.Reply(GetCoreCount());
}
void AmlCpu::GetLogicalCoreId(GetLogicalCoreIdRequestView request,
GetLogicalCoreIdCompleter::Sync& completer) {
// Placeholder.
completer.Reply(0);
}
} // namespace amlogic_cpu