blob: 5e29a5a1af2ba9d908fbd0720ed697b642b303bc [file] [log] [blame]
// Copyright 2021 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 "src/devices/power/drivers/aml-pwm-regulator/aml-pwm-regulator.h"
#include <lib/ddk/metadata.h>
#include <lib/driver/compat/cpp/metadata.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <string>
#include <bind/fuchsia/cpp/bind.h>
#include <bind/fuchsia/regulator/cpp/bind.h>
namespace {
const std::string_view kDriverName = "aml-pwm-regulator";
} // namespace
namespace aml_pwm_regulator {
AmlPwmRegulator::AmlPwmRegulator(const VregMetadata& metadata,
fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm> pwm_proto_client,
AmlPwmRegulatorDriver* driver)
: name_(std::string(metadata.name().data(), metadata.name().size())),
min_voltage_uv_(metadata.min_voltage_uv()),
voltage_step_uv_(metadata.voltage_step_uv()),
num_steps_(metadata.num_steps()),
current_step_(metadata.num_steps()),
pwm_proto_client_(std::move(pwm_proto_client)) {}
void AmlPwmRegulator::SetVoltageStep(SetVoltageStepRequestView request,
SetVoltageStepCompleter::Sync& completer) {
if (request->step >= num_steps_) {
FDF_LOG(ERROR, "Requested step (%u) is larger than allowed (total number of steps %u).",
request->step, num_steps_);
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (request->step == current_step_) {
completer.ReplySuccess();
return;
}
auto config_result = pwm_proto_client_->GetConfig();
if (!config_result.ok() || config_result->is_error()) {
auto status = config_result.ok() ? config_result->error_value() : config_result.status();
FDF_LOG(ERROR, "Unable to get PWM config. %s", zx_status_get_string(status));
completer.ReplyError(status);
return;
}
if (config_result->value()->config.period_ns == 0) {
FDF_LOG(ERROR, "PWM period config of 0ns is invalid.");
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
aml_pwm::mode_config on = {aml_pwm::Mode::kOn, {}};
fuchsia_hardware_pwm::wire::PwmConfig cfg = {
.polarity = config_result->value()->config.polarity,
.period_ns = config_result->value()->config.period_ns,
.duty_cycle =
static_cast<float>((num_steps_ - 1 - request->step) * 100.0 / ((num_steps_ - 1) * 1.0)),
.mode_config =
fidl::VectorView<uint8_t>::FromExternal(reinterpret_cast<uint8_t*>(&on), sizeof(on)),
};
auto result = pwm_proto_client_->SetConfig(cfg);
if (!result.ok()) {
FDF_LOG(ERROR, "Unable to configure PWM. %s", result.status_string());
completer.ReplyError(result.status());
return;
}
if (result->is_error()) {
FDF_LOG(ERROR, "Unable to configure PWM. %s", zx_status_get_string(result->error_value()));
completer.ReplyError(result->error_value());
return;
}
current_step_ = request->step;
completer.ReplySuccess();
}
void AmlPwmRegulator::GetVoltageStep(GetVoltageStepCompleter::Sync& completer) {
completer.Reply(current_step_);
}
void AmlPwmRegulator::GetRegulatorParams(GetRegulatorParamsCompleter::Sync& completer) {
completer.Reply(min_voltage_uv_, voltage_step_uv_, num_steps_);
}
zx::result<std::unique_ptr<AmlPwmRegulator>> AmlPwmRegulator::Create(
const VregMetadata& metadata, AmlPwmRegulatorDriver* driver) {
auto connect_result = driver->incoming()->Connect<fuchsia_hardware_pwm::Service::Pwm>("pwm");
if (connect_result.is_error()) {
FDF_LOG(ERROR, "Unable to connect to fidl protocol - status: %s",
connect_result.status_string());
return connect_result.take_error();
}
std::string name{metadata.name().data(), metadata.name().size()};
fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm> pwm_proto_client(
std::move(connect_result.value()));
auto result = pwm_proto_client->Enable();
if (!result.ok()) {
FDF_LOG(ERROR, "VREG(%s): Unable to enable PWM - %s", name.c_str(), result.status_string());
return zx::error(ZX_ERR_INTERNAL);
}
if (result->is_error()) {
FDF_LOG(ERROR, "VREG(%s): Unable to enable PWM - %s", name.c_str(),
zx_status_get_string(result->error_value()));
return result->take_error();
}
auto device = std::make_unique<AmlPwmRegulator>(metadata, std::move(pwm_proto_client), driver);
// Initialize our compat server.
{
zx::result<> result = device->compat_server_.Initialize(driver->incoming(), driver->outgoing(),
driver->node_name(), name);
if (result.is_error()) {
return result.take_error();
}
}
{
auto result = driver->outgoing()->AddService<fuchsia_hardware_vreg::Service>(
fuchsia_hardware_vreg::Service::InstanceHandler({
.vreg = device->bindings_.CreateHandler(
device.get(), fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure),
}),
name);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to add Device service %s", result.status_string());
return result.take_error();
}
}
fidl::Arena arena;
auto offers = device->compat_server_.CreateOffers2(arena);
offers.push_back(fdf::MakeOffer2<fuchsia_hardware_vreg::Service>(arena, name));
fidl::VectorView<fuchsia_driver_framework::wire::NodeProperty> properties(arena, 1);
properties[0] = fdf::MakeProperty(arena, bind_fuchsia_regulator::NAME, name);
const auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, name)
.offers2(arena, std::move(offers))
.properties(properties)
.Build();
zx::result controller_endpoints =
fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
if (!controller_endpoints.is_ok()) {
FDF_LOG(ERROR, "Failed to create controller endpoints: %s",
controller_endpoints.status_string());
return controller_endpoints.take_error();
}
fidl::WireResult add_result =
fidl::WireCall(driver->node())->AddChild(args, std::move(controller_endpoints->server), {});
if (!add_result.ok()) {
FDF_LOG(ERROR, "Failed to add child: %s", result.status_string());
return zx::error(add_result.status());
}
device->controller_.Bind(std::move(controller_endpoints->client));
return zx::ok(std::move(device));
}
AmlPwmRegulatorDriver::AmlPwmRegulatorDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher)
: fdf::DriverBase(kDriverName, std::move(start_args), std::move(driver_dispatcher)) {}
zx::result<> AmlPwmRegulatorDriver::Start() {
fidl::Arena arena;
auto decoded = compat::GetMetadata<fuchsia_hardware_vreg::wire::VregMetadata>(
incoming(), arena, DEVICE_METADATA_VREG, "pdev");
if (decoded.is_error()) {
FDF_LOG(ERROR, "Failed to get vreg metadata: %s", decoded.status_string());
return decoded.take_error();
}
const auto& metadata = *decoded.value();
// Validate
if (!metadata.has_name() || !metadata.has_min_voltage_uv() || !metadata.has_voltage_step_uv() ||
!metadata.has_num_steps()) {
FDF_LOG(ERROR, "Metadata incomplete");
return zx::error(ZX_ERR_INTERNAL);
}
// Build Voltage Regulator
auto regulator = AmlPwmRegulator::Create(metadata, this);
if (regulator.is_error()) {
return regulator.take_error();
}
regulators_ = std::move(*regulator);
return zx::ok();
}
} // namespace aml_pwm_regulator
FUCHSIA_DRIVER_EXPORT(aml_pwm_regulator::AmlPwmRegulatorDriver);