| // 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 <bind/fuchsia/cpp/bind.h> |
| |
| namespace { |
| |
| const std::string_view kDriverName = "aml-pwm-regulator"; |
| |
| } // namespace |
| |
| namespace aml_pwm_regulator { |
| |
| AmlPwmRegulator::AmlPwmRegulator(const PwmVregMetadataEntry& vreg_range, |
| fidl::WireSyncClient<fuchsia_hardware_pwm::Pwm> pwm_proto_client, |
| AmlPwmRegulatorDriver* driver, std::string_view name) |
| : pwm_index_(vreg_range.pwm_index()), |
| period_ns_(vreg_range.period_ns()), |
| min_voltage_uv_(vreg_range.min_voltage_uv()), |
| voltage_step_uv_(vreg_range.voltage_step_uv()), |
| num_steps_(vreg_range.num_steps()), |
| current_step_(vreg_range.num_steps()), |
| name_(name), |
| 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; |
| } |
| |
| aml_pwm::mode_config on = {aml_pwm::Mode::kOn, {}}; |
| fuchsia_hardware_pwm::wire::PwmConfig cfg = { |
| .polarity = false, |
| .period_ns = 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 PwmVregMetadataEntry& metadata_entry, AmlPwmRegulatorDriver* driver) { |
| uint32_t idx = metadata_entry.pwm_index(); |
| char name[20]; |
| snprintf(name, sizeof(name), "pwm-%u", idx); |
| |
| auto connect_result = driver->incoming()->Connect<fuchsia_hardware_pwm::Service::Pwm>(name); |
| 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(); |
| } |
| |
| 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, "Unable to enable PWM %u, %s", idx, result.status_string()); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "Unable to enable PWM %u, %s", idx, zx_status_get_string(result->error_value())); |
| return result->take_error(); |
| } |
| snprintf(name, sizeof(name), "pwm-%u-regulator", idx); |
| |
| auto device = |
| std::make_unique<AmlPwmRegulator>(metadata_entry, std::move(pwm_proto_client), driver, name); |
| |
| // 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, device->name_)); |
| |
| fidl::VectorView<fuchsia_driver_framework::wire::NodeProperty> properties(arena, 1); |
| properties[0] = fdf::MakeProperty(arena, 0x0A50 /* BIND_PWM_ID */, idx); |
| |
| 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::Metadata>( |
| 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_pwm_vreg()) { |
| FDF_LOG(ERROR, "Metadata incomplete"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| for (const auto& pwm_vreg : metadata.pwm_vreg()) { |
| if (!pwm_vreg.has_pwm_index() || !pwm_vreg.has_period_ns() || !pwm_vreg.has_min_voltage_uv() || |
| !pwm_vreg.has_voltage_step_uv() || !pwm_vreg.has_num_steps()) { |
| FDF_LOG(ERROR, "Metadata incomplete"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| } |
| // Build Voltage Regulators |
| for (const auto& pwm_vreg : metadata.pwm_vreg()) { |
| auto regulator = AmlPwmRegulator::Create(pwm_vreg, this); |
| if (regulator.is_error()) { |
| return regulator.take_error(); |
| } |
| regulators_.push_back(std::move(*regulator)); |
| } |
| return zx::ok(); |
| } |
| |
| } // namespace aml_pwm_regulator |
| |
| FUCHSIA_DRIVER_EXPORT(aml_pwm_regulator::AmlPwmRegulatorDriver); |