blob: 11bd442f39ba45c30068e9c628594d5d29cce9fa [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 "buttons.h"
#include <fidl/fuchsia.buttons/cpp/fidl.h>
#include <fidl/fuchsia.driver.compat/cpp/wire.h>
#include <lib/ddk/metadata.h>
#include <lib/driver/compat/cpp/device_server.h>
#include <lib/driver/compat/cpp/metadata.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/platform-device/cpp/pdev.h>
#include <cinttypes>
#include <fbl/alloc_checker.h>
namespace buttons {
zx::result<> Buttons::Start() {
zx::result pdev_client_end =
incoming()->Connect<fuchsia_hardware_platform_device::Service::Device>("pdev");
if (pdev_client_end.is_error()) {
FDF_LOG(ERROR, "Failed to connect to platform device: %s", pdev_client_end.status_string());
return pdev_client_end.take_error();
}
fdf::PDev pdev{std::move(pdev_client_end.value())};
zx::result metadata_result = pdev.GetFidlMetadata<fuchsia_buttons::GpioButtonsMetadata>();
if (metadata_result.is_error()) {
FDF_LOG(ERROR, "Failed to get metadata: %s", metadata_result.status_string());
return metadata_result.take_error();
}
fuchsia_buttons::GpioButtonsMetadata& metadata = metadata_result.value();
if (!metadata.gpios().has_value()) {
FDF_LOG(ERROR, "Metadata missing gpios");
return zx::error(ZX_ERR_INTERNAL);
}
const std::span<const fuchsia_buttons::GpioConfig>& gpio_configs = metadata.gpios().value();
std::vector<ButtonsDevice::Gpio> gpios;
gpios.reserve(gpio_configs.size());
if (!metadata.buttons().has_value()) {
FDF_LOG(ERROR, "Metadata missing buttons");
return zx::error(ZX_ERR_INTERNAL);
}
std::vector<fuchsia_buttons::GpioButtonConfig> buttons = std::move(metadata.buttons().value());
for (size_t i = 0; i < gpio_configs.size(); ++i) {
const char* name;
const auto& button = buttons[i];
if (!button.id().has_value()) {
FDF_LOG(ERROR, "Button %zu missing id", i);
return zx::error(ZX_ERR_INTERNAL);
}
const fuchsia_buttons::GpioButtonId& button_id = button.id().value();
switch (button_id) {
case fuchsia_buttons::GpioButtonId::kVolumeUp:
name = "volume-up";
break;
case fuchsia_buttons::GpioButtonId::kVolumeDown:
name = "volume-down";
break;
case fuchsia_buttons::GpioButtonId::kFdr:
name = "volume-both";
break;
case fuchsia_buttons::GpioButtonId::kMicMute:
case fuchsia_buttons::GpioButtonId::kMicAndCamMute:
name = "mic-privacy";
break;
case fuchsia_buttons::GpioButtonId::kCamMute:
name = "cam-mute";
break;
case fuchsia_buttons::GpioButtonId::kPower:
name = "power";
break;
case fuchsia_buttons::GpioButtonId::kPlayPause:
name = "play-pause";
break;
case fuchsia_buttons::GpioButtonId::kKeyA:
name = "key-a";
break;
case fuchsia_buttons::GpioButtonId::kKeyM:
name = "key-m";
break;
case fuchsia_buttons::GpioButtonId::kFunction:
name = "function";
break;
default:
FDF_LOG(ERROR, "Button %zu has unknown id: %" PRIu32, i, static_cast<uint32_t>(button_id));
return zx::error(ZX_ERR_NOT_SUPPORTED);
};
zx::result gpio_client = incoming()->Connect<fuchsia_hardware_gpio::Service::Device>(name);
if (gpio_client.is_error() || !gpio_client->is_valid()) {
FDF_LOG(ERROR, "Connect to GPIO %s failed: %s", name, gpio_client.status_string());
return gpio_client.take_error();
}
gpios.emplace_back(ButtonsDevice::Gpio{
.client{std::move(gpio_client.value())}, .irq{}, .config = gpio_configs[i]});
}
fidl::ClientEnd<fuchsia_power_system::ActivityGovernor> sag_client;
if (config_.suspend_enabled()) {
auto sag_result = incoming()->Connect<fuchsia_power_system::ActivityGovernor>();
if (sag_result.is_ok() && sag_result->is_valid()) {
sag_client = std::move(sag_result.value());
} else {
FDF_LOG(
ERROR,
"Failed to connect to fuchsia.power.system.ActivityGovernor: %s; system may incorrectly enter suspend.",
sag_result.status_string());
}
}
device_ = std::make_unique<buttons::ButtonsDevice>(dispatcher(), std::move(buttons),
std::move(gpios), std::move(sag_client));
auto result = outgoing()->component().AddUnmanagedProtocol<fuchsia_input_report::InputDevice>(
input_report_bindings_.CreateHandler(device_.get(), dispatcher(),
fidl::kIgnoreBindingClosure),
kDeviceName);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to add input report service: %s", result.status_string());
return result.take_error();
}
if (zx::result result = CreateDevfsNode(); result.is_error()) {
FDF_LOG(ERROR, "Failed to export to devfs: %s", result.status_string());
return result.take_error();
}
return zx::ok();
}
void Buttons::PrepareStop(fdf::PrepareStopCompleter completer) {
device_->ShutDown();
completer(zx::ok());
}
zx::result<> Buttons::CreateDevfsNode() {
fidl::Arena arena;
zx::result connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
return connector.take_error();
}
auto devfs = fuchsia_driver_framework::wire::DevfsAddArgs::Builder(arena)
.connector(std::move(connector.value()))
.class_name("input-report");
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, kDeviceName)
.devfs_args(devfs.Build())
.Build();
auto controller_endpoints = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
zx::result node_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::Node>();
ZX_ASSERT_MSG(node_endpoints.is_ok(), "Failed to create node endpoints: %s",
node_endpoints.status_string());
fidl::WireResult result = fidl::WireCall(node())->AddChild(
args, std::move(controller_endpoints.server), std::move(node_endpoints->server));
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to add child %s", result.status_string());
return zx::error(result.status());
}
controller_.Bind(std::move(controller_endpoints.client));
node_.Bind(std::move(node_endpoints->client));
return zx::ok();
}
} // namespace buttons
FUCHSIA_DRIVER_EXPORT(buttons::Buttons);