blob: 13e12f733f0ea5adccc4654a8bc4dd274c2e75a9 [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-voltage.h"
#include <lib/ddk/debug.h>
#include <lib/device-protocol/pdev-fidl.h>
#include <lib/zx/result.h>
#include <string.h>
#include <unistd.h>
#include <memory>
#include <fbl/alloc_checker.h>
namespace thermal {
namespace {
// Sleep for 200 microseconds inorder to let the voltage change
// take effect. Source: Amlogic SDK.
constexpr uint32_t kSleep = 200;
// Step up or down 3 steps in the voltage table while changing
// voltage and not directly. Source: Amlogic SDK
constexpr int kSteps = 3;
// Invalid index in the voltage-table
constexpr int kInvalidIndex = -1;
zx::result<fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm>> CreateAndEnablePwm(zx_device_t* parent,
const char* name) {
auto pwm_endpoints = fidl::CreateEndpoints<fuchsia_hardware_pwm::Pwm>();
if (pwm_endpoints.is_error()) {
zxlogf(ERROR, "fidl::CreateEndpoints failed - status: %s", pwm_endpoints.status_string());
return zx::error(pwm_endpoints.status_value());
}
zx_status_t status = device_connect_fragment_fidl_protocol(
parent, name, fuchsia_hardware_pwm::Service::Name, fuchsia_hardware_pwm::Service::Pwm::Name,
pwm_endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
zxlogf(ERROR, "failed to get cluster PWM fragment: %s", zx_status_get_string(status));
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm> client(std::move(pwm_endpoints->client));
auto result = client->Enable();
if (!result.ok()) {
zxlogf(ERROR, "Could not enable PWM: %s", result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
zxlogf(ERROR, "Could not enable PWM: %s", zx_status_get_string(result->error_value()));
return zx::error(result->error_value());
}
return zx::ok(std::move(client));
}
} // namespace
zx_status_t AmlVoltageRegulator::Create(
zx_device_t* parent, const fuchsia_hardware_thermal::wire::ThermalDeviceInfo& thermal_config,
const aml_thermal_info_t* thermal_info) {
auto pdev = ddk::PDevFidl::FromFragment(parent);
if (!pdev.is_valid()) {
zxlogf(ERROR, "aml-voltage: failed to get pdev protocol");
return ZX_ERR_NOT_SUPPORTED;
}
pdev_device_info_t device_info;
if (zx_status_t status = pdev.GetDeviceInfo(&device_info); status != ZX_OK) {
zxlogf(ERROR, "aml-voltage: failed to get GetDeviceInfo ");
return status;
}
auto pwm_client = CreateAndEnablePwm(parent, "pwm-a");
if (pwm_client.is_error()) {
return pwm_client.status_value();
}
big_cluster_pwm_ = std::move(*pwm_client);
big_little_ = thermal_config.big_little;
if (big_little_) {
auto pwm_client = CreateAndEnablePwm(parent, "pwm-ao-d");
if (pwm_client.is_error()) {
return pwm_client.status_value();
}
little_cluster_pwm_ = std::move(*pwm_client);
}
return Init(thermal_config, thermal_info);
}
zx_status_t AmlVoltageRegulator::Init(
fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm> big_cluster_pwm,
fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm> little_cluster_pwm,
const fuchsia_hardware_thermal::wire::ThermalDeviceInfo& thermal_config,
const aml_thermal_info_t* thermal_info) {
big_little_ = thermal_config.big_little;
big_cluster_pwm_ = std::move(big_cluster_pwm);
auto result = big_cluster_pwm_->Enable();
if (!result.ok()) {
zxlogf(ERROR, "Could not enable PWM: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Could not enable PWM: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
if (big_little_) {
little_cluster_pwm_ = std::move(little_cluster_pwm);
auto result = little_cluster_pwm_->Enable();
if (!result.ok()) {
zxlogf(ERROR, "Could not enable PWM: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Could not enable PWM: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
}
return Init(thermal_config, thermal_info);
}
zx_status_t AmlVoltageRegulator::Init(
const fuchsia_hardware_thermal::wire::ThermalDeviceInfo& thermal_config,
const aml_thermal_info_t* thermal_info) {
ZX_DEBUG_ASSERT(thermal_info);
// Get the voltage-table metadata.
memcpy(&thermal_info_, thermal_info, sizeof(aml_thermal_info_t));
current_big_cluster_voltage_index_ = kInvalidIndex;
current_little_cluster_voltage_index_ = kInvalidIndex;
uint32_t max_big_cluster_microvolt = 0;
uint32_t max_little_cluster_microvolt = 0;
const fuchsia_hardware_thermal::wire::OperatingPoint& big_cluster_opp =
thermal_config.opps[static_cast<uint32_t>(
fuchsia_hardware_thermal::wire::PowerDomain::kBigClusterPowerDomain)];
const fuchsia_hardware_thermal::wire::OperatingPoint& little_cluster_opp =
thermal_config.opps[static_cast<uint32_t>(
fuchsia_hardware_thermal::wire::PowerDomain::kLittleClusterPowerDomain)];
for (uint32_t i = 0; i < fuchsia_hardware_thermal::wire::kMaxDvfsOpps; i++) {
if (i < big_cluster_opp.count && big_cluster_opp.opp[i].volt_uv > max_big_cluster_microvolt) {
max_big_cluster_microvolt = big_cluster_opp.opp[i].volt_uv;
}
if (i < little_cluster_opp.count &&
little_cluster_opp.opp[i].volt_uv > max_little_cluster_microvolt) {
max_little_cluster_microvolt = little_cluster_opp.opp[i].volt_uv;
}
}
// Set the voltage to maximum to start with
if (zx_status_t status = SetBigClusterVoltage(max_big_cluster_microvolt); status != ZX_OK) {
return status;
}
if (big_little_) {
if (zx_status_t status = SetLittleClusterVoltage(max_little_cluster_microvolt);
status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t AmlVoltageRegulator::SetClusterVoltage(
int* current_voltage_index, const fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm>& pwm,
uint32_t microvolt) {
// Find the entry in the voltage-table.
int target_index;
for (target_index = 0; target_index < MAX_VOLTAGE_TABLE; target_index++) {
if (thermal_info_.voltage_table[target_index].microvolt == microvolt) {
break;
}
}
// Invalid voltage request.
if (target_index == MAX_VOLTAGE_TABLE) {
return ZX_ERR_INVALID_ARGS;
}
// If this is the first time we are setting up the voltage
// we directly set it.
if (*current_voltage_index < 0) {
// Update new duty cycle.
aml_pwm::mode_config on = {aml_pwm::Mode::kOn, {}};
fuchsia_hardware_pwm::wire::PwmConfig cfg = {
false, thermal_info_.voltage_pwm_period_ns,
static_cast<float>(thermal_info_.voltage_table[target_index].duty_cycle),
fidl::VectorView<uint8_t>::FromExternal(reinterpret_cast<uint8_t*>(&on), sizeof(on))};
auto result = pwm->SetConfig(cfg);
if (!result.ok()) {
zxlogf(ERROR, "Could not initialize PWM");
return result.status();
}
if (result.value().is_error()) {
zxlogf(ERROR, "Could not initialize PWM");
return result.value().error_value();
}
usleep(kSleep);
*current_voltage_index = target_index;
return ZX_OK;
}
// Otherwise we adjust to the target voltage step by step.
while (*current_voltage_index != target_index) {
if (*current_voltage_index < target_index) {
if (*current_voltage_index < target_index - kSteps) {
// Step up by 3 in the voltage table.
*current_voltage_index += kSteps;
} else {
*current_voltage_index = target_index;
}
} else {
if (*current_voltage_index > target_index + kSteps) {
// Step down by 3 in the voltage table.
*current_voltage_index -= kSteps;
} else {
*current_voltage_index = target_index;
}
}
// Update new duty cycle.
aml_pwm::mode_config on = {aml_pwm::Mode::kOn, {}};
fuchsia_hardware_pwm::wire::PwmConfig cfg = {
false, thermal_info_.voltage_pwm_period_ns,
static_cast<float>(thermal_info_.voltage_table[*current_voltage_index].duty_cycle),
fidl::VectorView<uint8_t>::FromExternal(reinterpret_cast<uint8_t*>(&on), sizeof(on))};
auto result = pwm->SetConfig(cfg);
if (!result.ok()) {
zxlogf(ERROR, "Could not initialize PWM");
return result.status();
}
if (result.value().is_error()) {
zxlogf(ERROR, "Could not initialize PWM");
return result.value().error_value();
}
usleep(kSleep);
}
return ZX_OK;
}
} // namespace thermal