blob: f17e3fb2d3d5d2db1b532ff0edea6187a0c42cba [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 "power.h"
#include <fuchsia/hardware/powerimpl/cpp/banjo.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <memory>
#include <bind/fuchsia/power/cpp/bind.h>
#include <ddk/metadata/power.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
void GetUniqueId(uint64_t* id) {
static std::atomic<size_t> unique_id = 0;
*id = unique_id.fetch_add(1);
}
namespace power {
void PowerDeviceFragmentChild::RegisterPowerDomain(RegisterPowerDomainRequestView request,
RegisterPowerDomainCompleter::Sync& completer) {
zx_status_t status = power_device_->RegisterPowerDomain(
fragment_device_id_, request->min_needed_voltage, request->max_supported_voltage);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void PowerDeviceFragmentChild::UnregisterPowerDomain(
UnregisterPowerDomainCompleter::Sync& completer) {
zx_status_t status = power_device_->UnregisterPowerDomain(fragment_device_id_);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void PowerDeviceFragmentChild::GetPowerDomainStatus(
GetPowerDomainStatusCompleter::Sync& completer) {
power_domain_status_t out_status;
zx_status_t status = power_device_->GetPowerDomainStatus(fragment_device_id_, &out_status);
if (status == ZX_OK) {
completer.ReplySuccess(static_cast<fuchsia_hardware_power::PowerDomainStatus>(out_status));
} else {
completer.ReplyError(status);
}
}
void PowerDeviceFragmentChild::GetSupportedVoltageRange(
GetSupportedVoltageRangeCompleter::Sync& completer) {
uint32_t out_min, out_max;
zx_status_t status =
power_device_->GetSupportedVoltageRange(fragment_device_id_, &out_min, &out_max);
if (status == ZX_OK) {
completer.ReplySuccess(out_min, out_max);
} else {
completer.ReplyError(status);
}
}
void PowerDeviceFragmentChild::RequestVoltage(RequestVoltageRequestView request,
RequestVoltageCompleter::Sync& completer) {
uint32_t out_actual_voltage;
zx_status_t status =
power_device_->RequestVoltage(fragment_device_id_, request->voltage, &out_actual_voltage);
if (status == ZX_OK) {
completer.ReplySuccess(out_actual_voltage);
} else {
completer.ReplyError(status);
}
}
void PowerDeviceFragmentChild::GetCurrentVoltage(GetCurrentVoltageRequestView request,
GetCurrentVoltageCompleter::Sync& completer) {
uint32_t out_current_voltage;
zx_status_t status =
power_device_->GetCurrentVoltage(fragment_device_id_, request->index, &out_current_voltage);
if (status == ZX_OK) {
completer.ReplySuccess(out_current_voltage);
} else {
completer.ReplyError(status);
}
}
void PowerDeviceFragmentChild::WritePmicCtrlReg(WritePmicCtrlRegRequestView request,
WritePmicCtrlRegCompleter::Sync& completer) {
zx_status_t status =
power_device_->WritePmicCtrlReg(fragment_device_id_, request->reg_addr, request->value);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void PowerDeviceFragmentChild::ReadPmicCtrlReg(ReadPmicCtrlRegRequestView request,
ReadPmicCtrlRegCompleter::Sync& completer) {
uint32_t out_value;
zx_status_t status =
power_device_->ReadPmicCtrlReg(fragment_device_id_, request->reg_addr, &out_value);
if (status == ZX_OK) {
completer.ReplySuccess(out_value);
} else {
completer.ReplyError(status);
}
}
PowerDeviceFragmentChild* PowerDevice::GetFragmentChildLocked(uint64_t fragment_device_id) {
for (auto& child : children_) {
if (child->fragment_device_id() == fragment_device_id) {
return child.get();
}
}
return nullptr;
}
uint32_t PowerDevice::GetDependentCount() {
fbl::AutoLock al(&power_device_lock_);
return GetDependentCountLocked();
}
uint32_t PowerDevice::GetDependentCountLocked() {
uint32_t count = 0;
for (const auto& child : children_) {
if (child->registered()) {
count++;
}
}
return count;
}
zx_status_t PowerDevice::GetSuitableVoltageLocked(uint32_t voltage, uint32_t* suitable_voltage) {
uint32_t min_voltage_all_children = min_voltage_uV_;
uint32_t max_voltage_all_children = max_voltage_uV_;
for (auto& child : children_) {
if (child->registered()) {
min_voltage_all_children = std::max(min_voltage_all_children, child->min_needed_voltage_uV());
max_voltage_all_children =
std::min(max_voltage_all_children, child->max_supported_voltage_uV());
}
}
if (min_voltage_all_children > max_voltage_all_children) {
zxlogf(ERROR, "Supported voltage ranges of all the dependents do not intersect.");
return ZX_ERR_NOT_FOUND;
}
*suitable_voltage = voltage;
if (voltage < min_voltage_all_children) {
*suitable_voltage = min_voltage_all_children;
}
if (voltage > max_voltage_all_children) {
*suitable_voltage = max_voltage_all_children;
}
return ZX_OK;
}
zx_status_t PowerDevice::RegisterPowerDomain(uint64_t fragment_device_id,
uint32_t min_needed_voltage_uV,
uint32_t max_supported_voltage_uV) {
zx_status_t status = ZX_OK;
fbl::AutoLock al(&power_device_lock_);
PowerDeviceFragmentChild* child = GetFragmentChildLocked(fragment_device_id);
ZX_DEBUG_ASSERT(child != nullptr);
child->set_min_needed_voltage_uV(std::max(min_needed_voltage_uV, min_voltage_uV_));
child->set_max_supported_voltage_uV(std::min(max_supported_voltage_uV, max_voltage_uV_));
if (child->registered()) {
return ZX_OK;
}
child->set_registered(true);
if (GetDependentCountLocked() == 1) {
// First dependent. Make sure parent is enabled by registering for it.
if (parent_power_.is_valid()) {
fidl::WireResult result =
fidl::WireCall(parent_power_)->RegisterPowerDomain(min_voltage_uV_, max_voltage_uV_);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send request for register with parent power domain: %s",
result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Failed to register with parent power domain: %s",
zx_status_get_string(result->error_value()));
return result.value().error_value();
}
}
status = power_impl_.EnablePowerDomain(index_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to enabled this power domain");
return status;
}
}
return ZX_OK;
}
zx_status_t PowerDevice::UnregisterPowerDomain(uint64_t fragment_device_id) {
zx_status_t status = ZX_OK;
fbl::AutoLock al(&power_device_lock_);
PowerDeviceFragmentChild* child = GetFragmentChildLocked(fragment_device_id);
ZX_DEBUG_ASSERT(child != nullptr);
if (!child->registered()) {
return ZX_ERR_UNAVAILABLE;
}
child->set_registered(false);
if (GetDependentCountLocked() == 0) {
status = power_impl_.DisablePowerDomain(index_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to disable power domain");
return status;
}
if (parent_power_.is_valid() && status == ZX_OK) {
fidl::WireResult result = fidl::WireCall(parent_power_)->UnregisterPowerDomain();
if (!result.ok()) {
zxlogf(ERROR, "Failed to send request for unregister with parent power domain: %s",
result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Failed to unregister with parent power domain: %s",
zx_status_get_string(result->error_value()));
return result.value().error_value();
}
}
}
return ZX_OK;
}
zx_status_t PowerDevice::GetPowerDomainStatus(uint64_t fragment_device_id,
power_domain_status_t* out_status) {
return power_impl_.GetPowerDomainStatus(index_, out_status);
}
zx_status_t PowerDevice::GetSupportedVoltageRange(uint64_t fragment_device_id,
uint32_t* min_voltage, uint32_t* max_voltage) {
if (fixed_) {
return ZX_ERR_NOT_SUPPORTED;
}
*min_voltage = min_voltage_uV_;
*max_voltage = max_voltage_uV_;
return ZX_OK;
}
zx_status_t PowerDevice::RequestVoltage(uint64_t fragment_device_id, uint32_t voltage,
uint32_t* actual_voltage) {
fbl::AutoLock al(&power_device_lock_);
if (fixed_) {
return ZX_ERR_NOT_SUPPORTED;
}
if (voltage < min_voltage_uV_ || voltage > max_voltage_uV_) {
zxlogf(ERROR, "The voltage is not within supported voltage range of the power domain");
return ZX_ERR_INVALID_ARGS;
}
PowerDeviceFragmentChild* child = GetFragmentChildLocked(fragment_device_id);
ZX_DEBUG_ASSERT(child != nullptr);
if (!child->registered()) {
zxlogf(ERROR, "The device is not registered for the power domain");
return ZX_ERR_UNAVAILABLE;
}
uint32_t suitable_voltage = voltage;
zx_status_t status = GetSuitableVoltageLocked(voltage, &suitable_voltage);
if (status != ZX_OK) {
zxlogf(ERROR,
"Unable to find a suitable voltage that matches all dependents of power domain\n");
return status;
}
return power_impl_.RequestVoltage(index_, suitable_voltage, actual_voltage);
}
zx_status_t PowerDevice::GetCurrentVoltage(uint64_t fragment_device_id, uint32_t index,
uint32_t* current_voltage) {
fbl::AutoLock al(&power_device_lock_);
return power_impl_.GetCurrentVoltage(index_, current_voltage);
}
zx_status_t PowerDevice::WritePmicCtrlReg(uint64_t fragment_device_id, uint32_t reg_addr,
uint32_t value) {
fbl::AutoLock al(&power_device_lock_);
return power_impl_.WritePmicCtrlReg(index_, reg_addr, value);
}
zx_status_t PowerDevice::ReadPmicCtrlReg(uint64_t fragment_device_id, uint32_t reg_addr,
uint32_t* out_value) {
fbl::AutoLock al(&power_device_lock_);
return power_impl_.ReadPmicCtrlReg(index_, reg_addr, out_value);
}
void PowerDevice::DdkRelease() { delete this; }
fit::function<void(fidl::ServerEnd<fuchsia_hardware_power::Device>)> PowerDevice::GetHandler() {
return [this](fidl::ServerEnd<fuchsia_hardware_power::Device> server_end) {
fbl::AutoLock al(&power_device_lock_);
fbl::AllocChecker ac;
uint64_t id = 0;
GetUniqueId(&id);
std::unique_ptr<PowerDeviceFragmentChild> child(new (&ac) PowerDeviceFragmentChild(id, this));
if (!ac.check()) {
zxlogf(ERROR, "Failed to allocate PowerDeviceFragmentChild.");
return;
}
children_.push_back(std::move(child));
PowerDeviceFragmentChild* child_ptr = children_.back().get();
auto close_handler = [this, id](fidl::UnbindInfo info) {
fbl::AutoLock al(&power_device_lock_);
for (auto iter = children_.begin(); iter != children_.end(); iter++) {
if (iter->get()->fragment_device_id() == id) {
children_.erase(iter);
return;
}
}
zxlogf(ERROR, "Unable to find the child with the given fragment id.");
};
bindings_.AddBinding(dispatcher_, std::move(server_end), child_ptr, std::move(close_handler));
};
}
zx_status_t PowerDevice::Serve(fidl::ServerEnd<fuchsia_io::Directory> server_end) {
fuchsia_hardware_power::Service::InstanceHandler handler({
.device = GetHandler(),
});
auto result = outgoing_.AddService<fuchsia_hardware_power::Service>(std::move(handler));
if (result.is_error()) {
zxlogf(ERROR, "Failed to add service to the outgoing directory");
return result.status_value();
}
result = outgoing_.Serve(std::move(server_end));
if (result.is_error()) {
zxlogf(ERROR, "Failed to add service to the outgoing directory");
return result.status_value();
}
return ZX_OK;
}
zx_status_t PowerDevice::Create(void* ctx, zx_device_t* parent) {
auto power_domain = ddk::GetMetadata<power_domain_t>(parent, DEVICE_METADATA_POWER_DOMAINS);
if (!power_domain.is_ok()) {
zxlogf(ERROR, "Failed to get metadata: %s", power_domain.status_string());
return power_domain.error_value();
}
auto index = power_domain->index;
char name[20];
snprintf(name, sizeof(name), "power-%u", index);
ddk::PowerImplProtocolClient power_impl(parent, "power-impl");
if (!power_impl.is_valid()) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_POWER_IMPL not available", __func__);
return ZX_ERR_NO_RESOURCES;
}
// This is optional.
fidl::ClientEnd<fuchsia_hardware_power::Device> parent_power;
zx::result parent_power_result =
ddk::Device<void>::DdkConnectFragmentFidlProtocol<fuchsia_hardware_power::Service::Device>(
parent, "power-parent");
if (parent_power_result.is_ok()) {
zxlogf(INFO, "Connected to optional power-parent.");
parent_power = std::move(*parent_power_result);
}
uint32_t min_voltage = 0, max_voltage = 0;
bool fixed = false;
zx_status_t status = power_impl.GetSupportedVoltageRange(index, &min_voltage, &max_voltage);
if (status != ZX_OK && status != ZX_ERR_NOT_SUPPORTED) {
return status;
}
if (status == ZX_ERR_NOT_SUPPORTED) {
fixed = true;
}
fbl::AllocChecker ac;
std::unique_ptr<PowerDevice> dev(new (&ac) PowerDevice(
parent, index, power_impl, std::move(parent_power), min_voltage, max_voltage, fixed));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_device_str_prop_t props[] = {
{bind_fuchsia_power::POWER_DOMAIN.c_str(), str_prop_int_val(index)},
};
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
status = dev->Serve(std::move(endpoints.value().server));
if (status != ZX_OK) {
return status;
}
std::array offers = {
fuchsia_hardware_power::Service::Name,
};
status = dev->DdkAdd(ddk::DeviceAddArgs(name)
.set_fidl_service_offers(offers)
.set_outgoing_dir(endpoints->client.TakeChannel())
.set_flags(DEVICE_ADD_ALLOW_MULTI_COMPOSITE)
.set_str_props(props));
if (status != ZX_OK) {
return status;
}
// dev is now owned by devmgr.
[[maybe_unused]] auto ptr = dev.release();
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = PowerDevice::Create;
return ops;
}();
} // namespace power
// clang-format off
ZIRCON_DRIVER(generic-power, power::driver_ops, "zircon", "0.1");
//clang-format on