blob: e70b8383d714c4e34181ad4be8aa6fd3ceca4e53 [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 <memory>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <ddktl/fidl.h>
#include <ddktl/protocol/composite.h>
#include <ddktl/protocol/thermal.h>
namespace {
using llcpp::fuchsia::device::MAX_DEVICE_PERFORMANCE_STATES;
using llcpp::fuchsia::hardware::thermal::PowerDomain;
__UNUSED constexpr size_t kFragmentPdev = 0;
constexpr size_t kFragmentThermal = 1;
constexpr size_t kFragmentCount = 2;
uint16_t PstateToOperatingPoint(const uint32_t pstate, const size_t n_operating_points) {
ZX_ASSERT(pstate < n_operating_points);
ZX_ASSERT(n_operating_points < MAX_DEVICE_PERFORMANCE_STATES);
// Operating points are indexed 0 to N-1.
return static_cast<uint16_t>(n_operating_points - pstate - 1);
}
} // namespace
namespace amlogic_cpu {
zx_status_t AmlCpu::Create(void* context, zx_device_t* parent) {
zx_status_t status;
ddk::CompositeProtocolClient composite(parent);
if (!composite.is_valid()) {
zxlogf(ERROR, "%s: failed to get composite protocol", __func__);
return ZX_ERR_INTERNAL;
}
zx_device_t* devices[kFragmentCount];
size_t actual;
composite.GetFragments(devices, kFragmentCount, &actual);
if (actual != kFragmentCount) {
zxlogf(ERROR, "%s: Expected to get %lu fragments, actually got %lu", __func__,
kFragmentCount, actual);
return ZX_ERR_INTERNAL;
}
// Initialize an array with the maximum possible number of PStates since we
// determine the actual number of PStates at runtime by querying the thermal
// driver.
device_performance_state_info_t perf_states[MAX_DEVICE_PERFORMANCE_STATES];
for (size_t i = 0; i < MAX_DEVICE_PERFORMANCE_STATES; i++) {
perf_states[i].state_id = static_cast<uint8_t>(i);
perf_states[i].restore_latency = 0;
}
// The Thermal Driver is our parent and it exports an interface with one
// method (Connect) which allows us to connect to its FIDL interface.
zx_device_t* device = devices[kFragmentThermal];
ddk::ThermalProtocolClient thermal_client;
status = ddk::ThermalProtocolClient::CreateFromDevice(device, &thermal_client);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-cpu: Failed to get thermal protocol client, st = %d", status);
return status;
}
// This channel pair will be used to talk to the Thermal Device's FIDL
// interface.
zx::channel channel_local, channel_remote;
status = zx::channel::create(0, &channel_local, &channel_remote);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-cpu: Failed to create channel pair, st = %d", status);
return status;
}
// Pass one end of the channel to the Thermal driver. The thermal driver will
// serve its FIDL interface over this channel.
status = thermal_client.Connect(std::move(channel_remote));
if (status != ZX_OK) {
zxlogf(ERROR, "aml-cpu: failed to connect to thermal driver, st = %d", status);
return status;
}
fuchsia_thermal::Device::SyncClient thermal_fidl_client(std::move(channel_local));
auto device_info = thermal_fidl_client.GetDeviceInfo();
if (device_info.status() != ZX_OK) {
zxlogf(ERROR, "aml-cpu: failed to get device info, st = %d", device_info.status());
return device_info.status();
}
const fuchsia_thermal::ThermalDeviceInfo* info = device_info->info.get();
// Hack: Only support one DVFS domain in this driver. When only one domain is
// supported, it is published as the "Big" domain, so we check that the Little
// domain is unpopulated.
constexpr size_t kLittleDomainIndex = 1u;
static_assert(static_cast<size_t>(PowerDomain::LITTLE_CLUSTER_POWER_DOMAIN) ==
kLittleDomainIndex);
if (info->opps[kLittleDomainIndex].count != 0) {
zxlogf(ERROR, "aml-cpu: this driver only supports one dvfs domain.");
return ZX_ERR_INTERNAL;
}
// Make sure we don't have more operating points than available performance states.
const fuchsia_thermal::OperatingPoint& opps = info->opps[0];
if (opps.count > MAX_DEVICE_PERFORMANCE_STATES) {
zxlogf(ERROR, "aml-cpu: cpu device has more operating points than we support");
return ZX_ERR_INTERNAL;
}
const uint8_t perf_state_count = static_cast<uint8_t>(opps.count);
zxlogf(INFO, "aml-cpu: Creating CPU Device with %u operating poitns", opps.count);
auto cpu_device = std::make_unique<AmlCpu>(device, std::move(thermal_fidl_client));
status = cpu_device->DdkAdd("cpu", // name
DEVICE_ADD_NON_BINDABLE, // flags
nullptr, 0, // props & propcount
ZX_PROTOCOL_CPU_CTRL, // protocol id
nullptr, // proxy_args
ZX_HANDLE_INVALID, // client remote
nullptr, 0, // Power states & count
perf_states, perf_state_count // Perf states & count
);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-cpu: Failed to add cpu device, st = %d", status);
return status;
}
// Intentionally leak this device because it's owned by the driver framework.
__UNUSED auto unused = cpu_device.release();
return ZX_OK;
}
zx_status_t AmlCpu::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
fuchsia_cpuctrl::Device::Dispatch(this, msg, &transaction);
return transaction.Status();
}
void AmlCpu::DdkRelease() { delete this; }
zx_status_t AmlCpu::DdkSetPerformanceState(uint32_t requested_state, uint32_t* out_state) {
zx_status_t status;
fuchsia_thermal::OperatingPoint opps;
status = GetThermalOperatingPoints(&opps);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to get Thermal operating poitns, st = %d", __func__, status);
return status;
}
if (requested_state >= opps.count) {
zxlogf(ERROR, "%s: Requested device performance state is out of bounds", __func__);
return ZX_ERR_OUT_OF_RANGE;
}
const uint16_t pstate = PstateToOperatingPoint(requested_state, opps.count);
const auto result =
thermal_client_.SetDvfsOperatingPoint(pstate, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
if (!result.ok() || result->status != ZX_OK) {
zxlogf(ERROR, "%s: failed to set dvfs operating point.", __func__);
return ZX_ERR_INTERNAL;
}
*out_state = requested_state;
return ZX_OK;
}
zx_status_t AmlCpu::DdkConfigureAutoSuspend(bool enable, uint8_t requested_sleep_state) {
return ZX_ERR_NOT_SUPPORTED;
}
void AmlCpu::GetPerformanceStateInfo(uint32_t state,
GetPerformanceStateInfoCompleter::Sync completer) {
// Get all performance states.
zx_status_t status;
fuchsia_thermal::OperatingPoint opps;
status = GetThermalOperatingPoints(&opps);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to get Thermal operating poitns, st = %d", __func__, status);
completer.ReplyError(status);
}
// Make sure that the state is in bounds?
if (state >= opps.count) {
zxlogf(ERROR, "%s: requested pstate index out of bounds, requested = %u, count = %u",
__func__, state, opps.count);
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
const uint16_t pstate = PstateToOperatingPoint(state, opps.count);
llcpp::fuchsia::hardware::cpu::ctrl::CpuPerformanceStateInfo result;
result.frequency_hz = opps.opp[pstate].freq_hz;
result.voltage_uv = opps.opp[pstate].volt_uv;
completer.ReplySuccess(result);
}
zx_status_t AmlCpu::GetThermalOperatingPoints(fuchsia_thermal::OperatingPoint* out) {
auto result = thermal_client_.GetDeviceInfo();
if (!result.ok() || result->status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to get thermal device info", __func__);
return ZX_ERR_INTERNAL;
}
fuchsia_thermal::ThermalDeviceInfo* info = result->info.get();
// We only support one DVFS cluster on Astro.
if (info->opps[1].count != 0) {
zxlogf(ERROR, "%s: thermal driver reported more than one dvfs domain?", __func__);
return ZX_ERR_INTERNAL;
}
memcpy(out, &info->opps[0], sizeof(*out));
return ZX_OK;
}
void AmlCpu::GetNumLogicalCores(GetNumLogicalCoresCompleter::Sync completer) {
unsigned int result = zx_system_get_num_cpus();
completer.Reply(result);
}
void AmlCpu::GetLogicalCoreId(uint64_t index, GetLogicalCoreIdCompleter::Sync completer) {
// Placeholder.
completer.Reply(0);
}
} // 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_BEGIN(aml_cpu, aml_cpu_driver_ops, "zircon", "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_CPU),
ZIRCON_DRIVER_END(aml_cpu)