blob: 8c3e20cd20dcf15442024ee57b6cd894bd528f4c [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/debug.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-protocol/pdev.h>
#include <lib/inspect/cpp/inspector.h>
#include <lib/mmio/mmio.h>
#include <memory>
#include <vector>
#include <ddktl/fidl.h>
#include <fbl/string_buffer.h>
#include "src/devices/cpu/drivers/aml-cpu/aml-cpu-bind.h"
namespace amlogic_cpu {
namespace {
// Fragments are provided to this driver in groups of 4. Fragments are provided as follows:
// [4 fragments for cluster 0]
// [4 fragments for cluster 1]
// [...]
// [4 fragments for cluster n]
// Each fragment is a combination of the fixed string + id.
constexpr size_t kFragmentsPerPfDomain = 4;
constexpr zx_off_t kCpuVersionOffset = 0x220;
} // namespace
zx_status_t AmlCpu::Create(void* context, zx_device_t* parent) {
zx_status_t st;
// Get the metadata for the performance domains.
auto perf_doms = ddk::GetMetadataArray<perf_domain_t>(parent, DEVICE_METADATA_AML_PERF_DOMAINS);
if (!perf_doms.is_ok()) {
zxlogf(ERROR, "%s: Failed to get performance domains from board driver, st = %d", __func__,
perf_doms.error_value());
return perf_doms.error_value();
}
auto op_points = ddk::GetMetadataArray<operating_point_t>(parent, DEVICE_METADATA_AML_OP_POINTS);
if (!op_points.is_ok()) {
zxlogf(ERROR, "%s: Failed to get operating point from board driver, st = %d", __func__,
op_points.error_value());
return op_points.error_value();
}
// Make sure we have the right number of fragments.
const uint32_t fragment_count = device_get_fragment_count(parent);
zxlogf(DEBUG, "%s: GetFragmentCount = %u", __func__, fragment_count);
if ((perf_doms->size() * kFragmentsPerPfDomain) + 1 != fragment_count) {
zxlogf(ERROR,
"%s: Expected %lu fragments for each %lu performance domains for a total of %lu "
"fragments but got %u instead",
__func__, kFragmentsPerPfDomain, perf_doms->size(),
perf_doms->size() * kFragmentsPerPfDomain, fragment_count);
return ZX_ERR_INTERNAL;
}
// Map AOBUS registers
auto pdev = ddk::PDev::FromFragment(parent);
if (!pdev.is_valid()) {
zxlogf(ERROR, "Failed to get platform device fragment");
return ZX_ERR_NO_RESOURCES;
}
std::optional<fdf::MmioBuffer> mmio_buffer;
if ((st = pdev.MapMmio(0, &mmio_buffer)) != ZX_OK) {
zxlogf(ERROR, "aml-cpu: Failed to map mmio, st = %d", st);
return st;
}
const uint32_t cpu_version_packed = mmio_buffer->Read32(kCpuVersionOffset);
// Build and publish each performance domain.
for (const perf_domain_t& perf_domain : perf_doms.value()) {
fbl::StringBuffer<32> fragment_name;
fragment_name.AppendPrintf("clock-pll-div16-%02d", perf_domain.id);
ddk::ClockProtocolClient pll_div16_client;
if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
&pll_div16_client)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to create pll_div_16 clock client, st = %d", __func__, st);
return st;
}
fragment_name.Resize(0);
fragment_name.AppendPrintf("clock-cpu-div16-%02d", perf_domain.id);
ddk::ClockProtocolClient cpu_div16_client;
if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
&cpu_div16_client)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to create cpu_div_16 clock client, st = %d", __func__, st);
return st;
}
fragment_name.Resize(0);
fragment_name.AppendPrintf("clock-cpu-scaler-%02d", perf_domain.id);
ddk::ClockProtocolClient cpu_scaler_client;
if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
&cpu_scaler_client)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to create cpu_scaler clock client, st = %d", __func__, st);
return st;
}
fragment_name.Resize(0);
fragment_name.AppendPrintf("power-%02d", perf_domain.id);
ddk::PowerProtocolClient power_client;
if ((st = ddk::PowerProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
&power_client)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to create power client, st = %d", __func__, st);
return st;
}
// Vector of operating points that belong to this power domain.
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;
});
const size_t perf_state_count = pd_op_points.size();
auto perf_states = std::make_unique<device_performance_state_info_t[]>(perf_state_count);
for (size_t j = 0; j < perf_state_count; j++) {
perf_states[j].state_id = static_cast<uint8_t>(j);
perf_states[j].restore_latency = 0;
}
auto device =
std::make_unique<AmlCpu>(parent, std::move(pll_div16_client), std::move(cpu_div16_client),
std::move(cpu_scaler_client), std::move(power_client),
std::move(pd_op_points), perf_domain.core_count);
st = device->Init();
if (st != ZX_OK) {
zxlogf(ERROR, "%s: Failed to initialize device, st = %d", __func__, st);
return st;
}
device->SetCpuInfo(cpu_version_packed);
st = device->DdkAdd(ddk::DeviceAddArgs(perf_domain.name)
.set_flags(DEVICE_ADD_NON_BINDABLE)
.set_proto_id(ZX_PROTOCOL_CPU_CTRL)
.set_performance_states({perf_states.get(), perf_state_count})
.set_inspect_vmo(device->inspector_.DuplicateVmo()));
if (st != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed, st = %d", __func__, st);
return st;
}
__UNUSED auto ptr = device.release();
}
return ZX_OK;
}
void AmlCpu::DdkRelease() { delete this; }
zx_status_t AmlCpu::DdkSetPerformanceState(uint32_t requested_state, uint32_t* out_state) {
if (requested_state >= operating_points_.size()) {
zxlogf(ERROR, "%s: Requested performance state is out of bounds, state = %u\n", __func__,
requested_state);
return ZX_ERR_OUT_OF_RANGE;
}
if (!out_state) {
zxlogf(ERROR, "%s: out_state may not be null", __func__);
return ZX_ERR_INVALID_ARGS;
}
// There is no condition under which this function will return ZX_OK but out_state will not
// be requested_state so we're going to go ahead and set that up front.
*out_state = requested_state;
const operating_point_t& target_state = operating_points_[requested_state];
const operating_point_t& initial_state = operating_points_[current_pstate_];
zxlogf(INFO, "%s: Scaling from %u MHz %u mV to %u MHz %u mV", __func__,
initial_state.freq_hz / 1000000, initial_state.volt_uv / 1000,
target_state.freq_hz / 1000000, target_state.volt_uv / 1000);
if (initial_state.freq_hz == target_state.freq_hz &&
initial_state.volt_uv == target_state.volt_uv) {
// Nothing to be done.
return ZX_OK;
}
zx_status_t st;
if (target_state.volt_uv > initial_state.volt_uv) {
// If we're increasing the voltage we need to do it before setting the
// frequency.
uint32_t actual_voltage;
st = pwr_.RequestVoltage(target_state.volt_uv, &actual_voltage);
if (st != ZX_OK || actual_voltage != target_state.volt_uv) {
zxlogf(ERROR, "%s: Failed to set cpu voltage, requested = %u, got = %u, st = %d", __func__,
target_state.volt_uv, actual_voltage, st);
return st;
}
}
// Set the frequency next.
st = cpuscaler_.SetRate(target_state.freq_hz);
if (st != ZX_OK) {
zxlogf(ERROR, "%s: Could not set CPU frequency, st = %d\n", __func__, st);
// Put the voltage back if frequency scaling fails.
uint32_t actual_voltage;
zx_status_t vt_st = pwr_.RequestVoltage(initial_state.volt_uv, &actual_voltage);
if (vt_st != ZX_OK) {
zxlogf(ERROR, "%s: Failed to reset CPU voltage, st = %d, Voltage and frequency mismatch!",
__func__, vt_st);
return vt_st;
}
return st;
}
// If we're decreasing the voltage, then we do it after the frequency has been
// reduced to avoid undervolt conditions.
if (target_state.volt_uv < initial_state.volt_uv) {
uint32_t actual_voltage;
st = pwr_.RequestVoltage(target_state.volt_uv, &actual_voltage);
if (st != ZX_OK || actual_voltage != target_state.volt_uv) {
zxlogf(ERROR,
"%s: Failed to set cpu voltage, requested = %u, got = %u, st = %d. "
"Voltage and frequency mismatch!",
__func__, target_state.volt_uv, actual_voltage, st);
return st;
}
}
zxlogf(INFO, "%s: Success\n", __func__);
current_pstate_ = requested_state;
return ZX_OK;
}
zx_status_t AmlCpu::Init() {
zx_status_t result;
constexpr uint32_t kInitialPstate = 0;
result = plldiv16_.Enable();
if (result != ZX_OK) {
zxlogf(ERROR, "%s: Failed to enable plldiv16, st = %d", __func__, result);
return result;
}
result = cpudiv16_.Enable();
if (result != ZX_OK) {
zxlogf(ERROR, "%s: Failed to enable cpudiv16_, st = %d", __func__, result);
return result;
}
uint32_t min_voltage, max_voltage;
pwr_.GetSupportedVoltageRange(&min_voltage, &max_voltage);
pwr_.RegisterPowerDomain(min_voltage, max_voltage);
uint32_t actual;
result = DdkSetPerformanceState(kInitialPstate, &actual);
if (result != ZX_OK) {
zxlogf(ERROR, "%s: Failed to set initial performance state, st = %d", __func__, result);
return result;
}
if (actual != kInitialPstate) {
zxlogf(ERROR, "%s: Failed to set initial performance state, requested = %u, actual = %u",
__func__, kInitialPstate, actual);
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t AmlCpu::DdkConfigureAutoSuspend(bool enable, uint8_t requested_sleep_state) {
return ZX_ERR_NOT_SUPPORTED;
}
void AmlCpu::GetPerformanceStateInfo(GetPerformanceStateInfoRequestView request,
GetPerformanceStateInfoCompleter::Sync& completer) {
if (request->state >= operating_points_.size()) {
zxlogf(INFO, "%s: Requested an operating point that's out of bounds, %u\n", __func__,
request->state);
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
fuchsia_hardware_cpu_ctrl::wire::CpuPerformanceStateInfo result;
result.frequency_hz = operating_points_[request->state].freq_hz;
result.voltage_uv = operating_points_[request->state].volt_uv;
completer.ReplySuccess(result);
}
void AmlCpu::GetNumLogicalCores(GetNumLogicalCoresRequestView request,
GetNumLogicalCoresCompleter::Sync& completer) {
completer.Reply(core_count_);
}
void AmlCpu::GetLogicalCoreId(GetLogicalCoreIdRequestView request,
GetLogicalCoreIdCompleter::Sync& completer) {
// Placeholder.
completer.Reply(0);
}
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;
zxlogf(INFO, "major revision number: 0x%x", major_revision);
zxlogf(INFO, "minor revision number: 0x%x", minor_revision);
zxlogf(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_);
}
} // namespace amlogic_cpu
static constexpr zx_driver_ops_t aml_cpu_driver_ops = []() {
zx_driver_ops_t result = {};
result.version = DRIVER_OPS_VERSION;
result.bind = amlogic_cpu::AmlCpu::Create;
return result;
}();
// clang-format off
ZIRCON_DRIVER(aml_cpu, aml_cpu_driver_ops, "zircon", "0.1");