| // 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); |