blob: 5cb3ee27759098156f986ea15c07e6c2e6a5e6f0 [file] [log] [blame]
// Copyright 2024 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-device.h"
#include <lib/driver/logging/cpp/structured_logger.h>
#include <lib/zx/clock.h>
#include <cinttypes>
#include <cstddef>
#include <fbl/alloc_checker.h>
namespace buttons {
void ButtonsDevice::ButtonsInputReport::ToFidlInputReport(
fidl::WireTableBuilder<::fuchsia_input_report::wire::InputReport>& input_report,
fidl::AnyArena& allocator) const {
fidl::VectorView<fuchsia_input_report::wire::ConsumerControlButton> buttons_rpt(
allocator, fuchsia_input_report::wire::kConsumerControlMaxNumButtons);
size_t count = 0;
bool mic_mute = false;
bool cam_mute = false;
for (uint32_t id = 0; id < buttons.size(); id++) {
if (!buttons[id]) {
continue;
}
switch (static_cast<fuchsia_buttons::GpioButtonId>(id)) {
case fuchsia_buttons::GpioButtonId::kPower:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kPower;
count++;
break;
case fuchsia_buttons::GpioButtonId::kVolumeUp:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kVolumeUp;
count++;
break;
case fuchsia_buttons::GpioButtonId::kVolumeDown:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kVolumeDown;
count++;
break;
case fuchsia_buttons::GpioButtonId::kFdr:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kFactoryReset;
count++;
break;
case fuchsia_buttons::GpioButtonId::kMicMute:
if (!mic_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kMicMute;
count++;
mic_mute = true;
}
break;
case fuchsia_buttons::GpioButtonId::kCamMute:
if (!cam_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kCameraDisable;
count++;
cam_mute = true;
}
break;
case fuchsia_buttons::GpioButtonId::kMicAndCamMute:
if (!mic_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kMicMute;
count++;
mic_mute = true;
}
if (!cam_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kCameraDisable;
count++;
cam_mute = true;
}
break;
default:
FDF_LOG(ERROR, "Invalid Button ID encountered %d", id);
}
}
buttons_rpt.set_size(count);
auto consumer_control =
fuchsia_input_report::wire::ConsumerControlInputReport::Builder(allocator).pressed_buttons(
buttons_rpt);
input_report.event_time(event_time.get()).consumer_control(consumer_control.Build());
}
void ButtonsDevice::Notify(size_t button_index) {
auto result = GetInputReportInternal();
if (result.is_error()) {
FDF_LOG(ERROR, "GetInputReport failed %s", zx_status_get_string(result.error_value()));
} else if (!last_report_.has_value() || *last_report_ != result.value()) {
last_report_ = result.value();
readers_.SendReportToAllReaders(*last_report_);
if (debounce_states_[button_index].timestamp != zx::time::infinite_past()) {
const zx::duration latency =
zx::clock::get_monotonic() - debounce_states_[button_index].timestamp;
total_latency_ += latency;
report_count_++;
average_latency_usecs_.Set(total_latency_.to_usecs() / report_count_);
if (latency > max_latency_) {
max_latency_ = latency;
max_latency_usecs_.Set(max_latency_.to_usecs());
}
}
if (!last_report_->empty()) {
total_report_count_.Add(1);
last_event_timestamp_.Set(last_report_->event_time.get());
}
}
if (buttons_[button_index].id() == fuchsia_buttons::GpioButtonId::kFdr) {
FDF_LOG(INFO, "FDR (up and down buttons) pressed");
}
debounce_states_[button_index].enqueued = false;
debounce_states_[button_index].timestamp = zx::time::infinite_past();
}
int ButtonsDevice::Thread() {
thread_started_.Signal();
if (poll_period_ != zx::duration::infinite()) {
poll_timer_.set(zx::deadline_after(poll_period_), zx::duration(0));
poll_timer_.wait_async(port_, kPortKeyPollTimer, ZX_TIMER_SIGNALED, 0);
}
while (true) {
zx_port_packet_t packet;
zx_status_t status = port_.wait(zx::time::infinite(), &packet);
FDF_LOG(DEBUG, "msg received on port key %zu", packet.key);
if (status != ZX_OK) {
FDF_LOG(ERROR, "port wait failed %d", status);
return thrd_error;
}
if (packet.key == kPortKeyShutDown) {
FDF_LOG(INFO, "shutting down");
return thrd_success;
}
if (packet.key >= kPortKeyInterruptStart &&
packet.key < (kPortKeyInterruptStart + buttons_.size())) {
uint32_t type = static_cast<uint32_t>(packet.key - kPortKeyInterruptStart);
const std::optional<fuchsia_buttons::GpioType>& gpio_type = gpios_[type].config.type();
if (!gpio_type.has_value()) {
FDF_LOG(ERROR, "Config for gpio %" PRIu32 " missing type", type);
return thrd_error;
}
if (gpio_type.value().Which() == fuchsia_buttons::GpioType::Tag::kInterrupt) {
const zx::duration kLeaseTimeout = zx::msec(100);
wake_lease_.HandleInterrupt(kLeaseTimeout);
// We need to reconfigure the GPIO to catch the opposite polarity.
auto reconfig_result = ReconfigurePolarity(type, packet.key);
if (!reconfig_result.is_ok()) {
return reconfig_result.error_value();
}
debounce_states_[type].value = *reconfig_result;
// Notify
debounce_states_[type].timer.set(zx::deadline_after(zx::duration(kDebounceThresholdNs)),
zx::duration(0));
if (!debounce_states_[type].enqueued) {
debounce_states_[type].timer.wait_async(port_, kPortKeyTimerStart + type,
ZX_TIMER_SIGNALED, 0);
debounce_states_[type].timestamp = zx::time(packet.interrupt.timestamp);
}
debounce_states_[type].enqueued = true;
}
gpios_[type].irq.ack();
}
if (packet.key >= kPortKeyTimerStart && packet.key < (kPortKeyTimerStart + buttons_.size())) {
Notify(packet.key - kPortKeyTimerStart);
}
if (packet.key == kPortKeyPollTimer) {
for (size_t i = 0; i < gpios_.size(); i++) {
const std::optional<fuchsia_buttons::GpioType>& gpio_type = gpios_[i].config.type();
if (!gpio_type.has_value()) {
FDF_LOG(ERROR, "Config for gpio %zu missing type", i);
return thrd_error;
}
if (gpio_type.value().Which() != fuchsia_buttons::GpioType::Tag::kPoll) {
continue;
}
fidl::WireResult read_result = gpios_[i].client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %zu: %s", i,
read_result.status_string());
return read_result.status();
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %zu: %s", i,
zx_status_get_string(read_result->error_value()));
return read_result->error_value();
}
if (read_result.value()->value != debounce_states_[i].value) {
Notify(i);
}
debounce_states_[i].value = read_result.value()->value;
}
poll_timer_.set(zx::deadline_after(poll_period_), zx::duration(0));
poll_timer_.wait_async(port_, kPortKeyPollTimer, ZX_TIMER_SIGNALED, 0);
}
}
return thrd_success;
}
void ButtonsDevice::GetInputReportsReader(GetInputReportsReaderRequestView request,
GetInputReportsReaderCompleter::Sync& completer) {
auto initial_report = GetInputReportInternal();
if (initial_report.is_error()) {
FDF_LOG(ERROR, "Failed to get initial report %d", initial_report.error_value());
}
auto status = readers_.CreateReader(
dispatcher_, std::move(request->reader),
initial_report.is_ok() ? std::make_optional(initial_report.value()) : std::nullopt);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to create a reader %d", status);
}
}
void ButtonsDevice::GetDescriptor(GetDescriptorCompleter::Sync& completer) {
fidl::Arena<kFeatureAndDescriptorBufferSize> arena;
auto device_info = fuchsia_input_report::wire::DeviceInformation::Builder(arena);
device_info.vendor_id(static_cast<uint32_t>(fuchsia_input_report::VendorId::kGoogle));
// Product id is "HID" buttons only for backward compatibility with users of this driver.
// There is no HID support in this driver anymore.
device_info.product_id(
static_cast<uint32_t>(fuchsia_input_report::VendorGoogleProductId::kHidButtons));
const std::vector<fuchsia_input_report::ConsumerControlButton> buttons = {
fuchsia_input_report::ConsumerControlButton::kVolumeUp,
fuchsia_input_report::ConsumerControlButton::kVolumeDown,
fuchsia_input_report::ConsumerControlButton::kFactoryReset,
fuchsia_input_report::ConsumerControlButton::kCameraDisable,
fuchsia_input_report::ConsumerControlButton::kMicMute,
fuchsia_input_report::ConsumerControlButton::kPower};
const auto input = fuchsia_input_report::wire::ConsumerControlInputDescriptor::Builder(arena)
.buttons(buttons)
.Build();
const auto consumer_control =
fuchsia_input_report::wire::ConsumerControlDescriptor::Builder(arena).input(input).Build();
completer.Reply(fuchsia_input_report::wire::DeviceDescriptor::Builder(arena)
.device_information(device_info.Build())
.consumer_control(consumer_control)
.Build());
}
// Requires interrupts to be disabled for all rows/cols.
zx::result<bool> ButtonsDevice::MatrixScan(uint32_t row, uint32_t col, zx_duration_t delay) {
auto& gpio_col = gpios_[col];
{
fidl::WireResult result = gpio_col.client->SetBufferMode(
fuchsia_hardware_gpio::BufferMode::kInput); // Float column to find row in use.
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send SetBufferMode request to gpio %u: %s", col,
result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configuire gpio %u to input: %s", col,
zx_status_get_string(result->error_value()));
return zx::error(result->error_value());
}
}
zx::nanosleep(zx::deadline_after(zx::duration(delay)));
fidl::WireResult read_result = gpios_[row].client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %u: %s", row, read_result.status_string());
return zx::error(read_result.status());
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %u: %s", row,
zx_status_get_string(read_result->error_value()));
return zx::error(read_result->error_value());
}
{
const std::optional<fuchsia_buttons::GpioType>& type = gpio_col.config.type();
if (!type.has_value()) {
FDF_LOG(ERROR, "Gpio config missing type");
return zx::error(ZX_ERR_BAD_STATE);
}
const std::optional<uint8_t> output_value =
gpio_col.config.type()->matrix_output()->output_value();
if (!output_value.has_value()) {
FDF_LOG(ERROR, "Gpio config missing output value");
return zx::error(ZX_ERR_BAD_STATE);
}
fidl::WireResult result = gpio_col.client->SetBufferMode(
output_value.value() ? fuchsia_hardware_gpio::BufferMode::kOutputHigh
: fuchsia_hardware_gpio::BufferMode::kOutputLow);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send SetBufferMode request to gpio %u: %s", col,
result.status_string());
return zx::error(read_result.status());
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configuire gpio %u to output: %s", col,
zx_status_get_string(result->error_value()));
return zx::error(result->error_value());
}
}
FDF_LOG(DEBUG, "row %u col %u val %u", row, col, read_result.value()->value);
return zx::ok(read_result.value()->value);
}
zx::result<ButtonsDevice::ButtonsInputReport> ButtonsDevice::GetInputReportInternal() {
ButtonsInputReport input_rpt;
for (size_t i = 0; i < buttons_.size(); ++i) {
bool new_value = false; // A value true means a button is pressed.
const fuchsia_buttons::GpioButtonConfig& button = buttons_[i];
const std::optional<fuchsia_buttons::GpioButtonType>& button_type = button.type();
if (!button_type.has_value()) {
FDF_LOG(ERROR, "Button %zu missing type", i);
return zx::error(ZX_ERR_BAD_STATE);
}
const std::optional<uint8_t>& gpio_a_index = button.gpio_a_index();
if (!gpio_a_index.has_value()) {
FDF_LOG(ERROR, "Button %zu missing gpio A index", i);
return zx::error(ZX_ERR_BAD_STATE);
}
const std::optional<uint8_t>& gpio_delay = button.gpio_a_index();
if (!gpio_delay.has_value()) {
FDF_LOG(ERROR, "Button %zu missing gpio delay", i);
return zx::error(ZX_ERR_BAD_STATE);
}
switch (button_type.value().Which()) {
case fuchsia_buttons::GpioButtonType::Tag::kMatrix: {
const fuchsia_buttons::MatrixGpioButton& matrix = button_type.value().matrix().value();
const std::optional<uint8_t>& gpio_b_index = matrix.gpio_b_index();
if (!gpio_b_index.has_value()) {
FDF_LOG(ERROR, "Button %zu missing gpio B index", i);
return zx::error(ZX_ERR_BAD_STATE);
}
zx::result scan_result =
MatrixScan(gpio_a_index.value(), gpio_b_index.value(), gpio_delay.value());
if (!scan_result.is_ok()) {
return zx::error(scan_result.error_value());
}
new_value = *scan_result;
break;
}
case fuchsia_buttons::GpioButtonType::Tag::kDirect: {
const uint8_t gpio_index = gpio_a_index.value();
fidl::WireResult read_result = gpios_[gpio_index].client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %u: %s", gpio_index,
read_result.status_string());
return zx::error(read_result.status());
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %u: %s", gpio_index,
zx_status_get_string(read_result->error_value()));
return zx::error(read_result->error_value());
}
new_value = read_result.value()->value;
FDF_LOG(DEBUG, "GPIO direct read %u for button %zu", new_value, i);
break;
}
default:
FDF_LOG(ERROR, "Button %zu has unknown type %u", i, button_type->Which());
return zx::error(ZX_ERR_INTERNAL);
}
const std::optional<fuchsia_buttons::GpioFlag>& flags = gpios_[i].config.flags();
if (!flags.has_value()) {
FDF_LOG(ERROR, "Config for gpio %zu missing flags", i);
return zx::error(ZX_ERR_BAD_STATE);
}
if (flags.value() & fuchsia_buttons::GpioFlag::kInverted) {
new_value = !new_value;
}
FDF_LOG(DEBUG, "GPIO new value %u for button %zu", new_value, i);
const std::optional<fuchsia_buttons::GpioButtonId>& button_id = button.id();
if (!button_id.has_value()) {
FDF_LOG(ERROR, "Button %zu missing id", i);
return zx::error(ZX_ERR_BAD_STATE);
}
input_rpt.set(static_cast<uint32_t>(button_id.value()), new_value);
}
input_rpt.event_time = zx::clock::get_monotonic();
return zx::ok(input_rpt);
}
void ButtonsDevice::GetInputReport(GetInputReportRequestView request,
GetInputReportCompleter::Sync& completer) {
if (request->device_type != fuchsia_input_report::DeviceType::kConsumerControl) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
auto result = GetInputReportInternal();
if (!result.is_ok()) {
completer.ReplyError(result.error_value());
return;
}
fidl::Arena<> arena;
auto input_report = fuchsia_input_report::wire::InputReport::Builder(arena);
result->ToFidlInputReport(input_report, arena);
completer.ReplySuccess(input_report.Build());
}
zx::result<bool> ButtonsDevice::ReconfigurePolarity(size_t idx, uint64_t int_port) {
FDF_LOG(DEBUG, "gpio %zu port %zu", idx, int_port);
bool current = false, old;
auto& gpio = gpios_[idx];
fidl::WireResult read_result1 = gpio.client->Read();
if (!read_result1.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %zu: %s", idx,
read_result1.status_string());
return zx::error(read_result1.status());
}
if (read_result1->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %zu: %s", idx,
zx_status_get_string(read_result1->error_value()));
return zx::error(read_result1->error_value());
}
current = read_result1.value()->value;
fidl::Arena arena;
auto config = fuchsia_hardware_gpio::wire::InterruptConfiguration::Builder(arena)
.mode(current ? fuchsia_hardware_gpio::InterruptMode::kEdgeLow
: fuchsia_hardware_gpio::InterruptMode::kEdgeHigh)
.Build();
do {
{
fidl::WireResult result = gpio.client->ConfigureInterrupt(config);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ConfigureInterrupt request to gpio %zu: %s", idx,
result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to set interrupt configuration of gpio %zu: %s", idx,
zx_status_get_string(result->error_value()));
return zx::error(result->error_value());
}
}
old = current;
fidl::WireResult read_result2 = gpio.client->Read();
if (!read_result2.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %zu: %s", idx,
read_result2.status_string());
return zx::error(read_result2.status());
}
if (read_result2->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %zu: %s", idx,
zx_status_get_string(read_result2->error_value()));
return zx::error(read_result2->error_value());
}
current = read_result2.value()->value;
FDF_LOG(TRACE, "%zu old gpio %u new gpio %u", idx, old, current);
// If current switches after setup, we setup a new trigger for it (opposite edge).
} while (current != old);
return zx::ok(current);
}
zx_status_t ButtonsDevice::ConfigureInterrupt(size_t idx, uint64_t int_port) {
FDF_LOG(DEBUG, "gpio %zu port %zu", idx, int_port);
zx_status_t status;
bool current = false;
auto& gpio = gpios_[idx];
const fidl::WireResult read_result = gpio.client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %zu: %s", idx, read_result.status_string());
return read_result.status();
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %zu: %s", idx,
zx_status_get_string(read_result->error_value()));
return read_result->error_value();
}
current = read_result.value()->value;
{
const fidl::WireResult result = gpio.client->ReleaseInterrupt();
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ReleaseInterrupt request to gpio %zu: %s", idx,
result.status_string());
return result.status();
}
if (result->is_error() && result->error_value() != ZX_ERR_NOT_FOUND) {
FDF_LOG(ERROR, "Failed to release interrupt for gpio %zu: %s", idx,
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
fidl::Arena arena;
// We setup a trigger for the opposite of the current GPIO value.
auto interrupt_config = fuchsia_hardware_gpio::wire::InterruptConfiguration::Builder(arena)
.mode(current ? fuchsia_hardware_gpio::InterruptMode::kEdgeLow
: fuchsia_hardware_gpio::InterruptMode::kEdgeHigh)
.Build();
fidl::WireResult configure_result = gpio.client->ConfigureInterrupt(interrupt_config);
if (!configure_result.ok()) {
FDF_LOG(ERROR, "Failed to send ConfigureInterrupt request to gpio %zu: %s", idx,
configure_result.status_string());
return configure_result.status();
}
if (configure_result->is_error()) {
FDF_LOG(ERROR, "Failed to configure interrupt for gpio %zu: %s", idx,
zx_status_get_string(configure_result->error_value()));
return configure_result->error_value();
}
fuchsia_hardware_gpio::InterruptOptions interrupt_options = {};
const auto& flags = gpio.config.flags();
if (!flags.has_value()) {
FDF_LOG(ERROR, "Config for gpio %zu missing flags", idx);
return ZX_ERR_BAD_STATE;
}
if (flags.value() & fuchsia_buttons::GpioFlag::kWakeVector) {
interrupt_options |= fuchsia_hardware_gpio::InterruptOptions::kWakeable;
}
fidl::WireResult interrupt_result = gpio.client->GetInterrupt(interrupt_options);
if (!interrupt_result.ok()) {
FDF_LOG(ERROR, "Failed to send GetInterrupt request to gpio %zu: %s", idx,
interrupt_result.status_string());
return interrupt_result.status();
}
if (interrupt_result->is_error()) {
FDF_LOG(ERROR, "Failed to get interrupt for gpio %zu: %s", idx,
zx_status_get_string(interrupt_result->error_value()));
return interrupt_result->error_value();
}
gpio.irq = std::move(interrupt_result.value()->interrupt);
status = gpios_[idx].irq.bind(port_, int_port, 0);
if (status != ZX_OK) {
FDF_LOG(ERROR, "zx_interrupt_bind failed %d", status);
return status;
}
// To make sure polarity is correct in case it changed during configuration.
auto reconfig_result = ReconfigurePolarity(idx, int_port);
if (!reconfig_result.is_ok()) {
return reconfig_result.error_value();
}
return ZX_OK;
}
ButtonsDevice::ButtonsDevice(async_dispatcher_t* dispatcher,
std::vector<fuchsia_buttons::GpioButtonConfig> buttons,
std::vector<Gpio> gpios,
fidl::ClientEnd<fuchsia_power_system::ActivityGovernor> sag_client)
: dispatcher_(dispatcher),
buttons_(std::move(buttons)),
gpios_(std::move(gpios)),
wake_lease_(dispatcher, "buttons-driver-wake", std::move(sag_client), nullptr, true) {
ZX_ASSERT(Init() == ZX_OK);
}
zx_status_t ButtonsDevice::Init() {
zx_status_t status;
fbl::AllocChecker ac;
metrics_root_ = inspector_.GetRoot().CreateChild("hid-input-report-touch");
average_latency_usecs_ = metrics_root_.CreateUint("average_latency_usecs", 0);
max_latency_usecs_ = metrics_root_.CreateUint("max_latency_usecs", 0);
total_report_count_ = metrics_root_.CreateUint("total_report_count", 0);
last_event_timestamp_ = metrics_root_.CreateUint("last_event_timestamp", 0);
status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_);
if (status != ZX_OK) {
FDF_LOG(ERROR, "port_create failed %d", status);
return status;
}
debounce_states_ = fbl::Array(new (&ac) debounce_state[buttons_.size()], buttons_.size());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
for (auto& i : debounce_states_) {
i.enqueued = false;
zx::timer::create(0, ZX_CLOCK_MONOTONIC, &(i.timer));
i.value = false;
}
zx::timer::create(0, ZX_CLOCK_MONOTONIC, &poll_timer_);
// Check the metadata.
for (size_t i = 0; i < buttons_.size(); ++i) {
const fuchsia_buttons::GpioButtonConfig& button = buttons_[i];
if (!button.gpio_a_index()) {
FDF_LOG(ERROR, "Button %zu missing gpio A index", i);
return ZX_ERR_INTERNAL;
}
const uint8_t gpio_a_index = button.gpio_a_index().value();
if (gpio_a_index >= gpios_.size()) {
FDF_LOG(ERROR, "Invalid gpio A index %u", gpio_a_index);
return ZX_ERR_INTERNAL;
}
if (!button.type().has_value()) {
FDF_LOG(ERROR, "Button %zu missing id", i);
return ZX_ERR_INTERNAL;
}
const fuchsia_buttons::GpioButtonType& button_type = button.type().value();
if (button_type.Which() == fuchsia_buttons::GpioButtonType::Tag::kMatrix) {
const fuchsia_buttons::MatrixGpioButton& matrix = button_type.matrix().value();
if (!matrix.gpio_b_index().has_value()) {
FDF_LOG(ERROR, "Button %zu missing gpio B index", i);
return ZX_ERR_INTERNAL;
}
const uint8_t gpio_b_index = matrix.gpio_b_index().value();
if (gpio_b_index >= gpios_.size()) {
FDF_LOG(ERROR, "Invalid gpio B index %u", gpio_b_index);
return ZX_ERR_INTERNAL;
}
const Gpio& gpio_b = gpios_[gpio_b_index];
if (!gpio_b.config.type().has_value()) {
FDF_LOG(ERROR, "Config for gpio %u missing period", gpio_b_index);
return ZX_ERR_INTERNAL;
}
const fuchsia_buttons::GpioType& gpio_b_type = gpio_b.config.type().value();
if (gpio_b_type.Which() != fuchsia_buttons::GpioType::Tag::kMatrixOutput) {
FDF_LOG(ERROR, "Config for matrix gpio B has invalid type %u", gpio_b_type.Which());
return ZX_ERR_INTERNAL;
}
}
const Gpio& gpio_a = gpios_[gpio_a_index];
if (!gpio_a.config.type().has_value()) {
FDF_LOG(ERROR, "Config for gpio %u missing type", gpio_a_index);
return ZX_ERR_INTERNAL;
}
const fuchsia_buttons::GpioType& gpio_a_type = gpio_a.config.type().value();
switch (gpio_a_type.Which()) {
case fuchsia_buttons::GpioType::Tag::kInterrupt:
break;
case fuchsia_buttons::GpioType::Tag::kPoll: {
const fuchsia_buttons::PollGpio& poll = gpio_a_type.poll().value();
if (!poll.period().has_value()) {
FDF_LOG(ERROR, "Config for gpio %u missing period", gpio_a_index);
return ZX_ERR_INTERNAL;
}
const zx::duration poll_period = zx::duration(poll.period().value());
if (poll_period_ == zx::duration::infinite()) {
poll_period_ = poll_period;
}
if (poll_period != poll_period_) {
FDF_LOG(ERROR, "GPIOs must have the same poll period");
return ZX_ERR_INTERNAL;
}
break;
}
default:
FDF_LOG(ERROR, "Config for gpio %u has invalid type %d", gpio_a_index, gpio_a_type.Which());
return ZX_ERR_INTERNAL;
}
if (!button.id().has_value()) {
FDF_LOG(ERROR, "Button %zu missing id", i);
return ZX_ERR_INTERNAL;
}
const fuchsia_buttons::GpioButtonId button_id = button.id().value();
if (button_id == fuchsia_buttons::GpioButtonId::kFdr) {
FDF_LOG(INFO, "FDR (up and down buttons) setup to GPIO %u", gpio_a_index);
}
}
// Setup.
for (size_t i = 0; i < gpios_.size(); ++i) {
const Gpio& gpio = gpios_[i];
if (!gpio.config.type().has_value()) {
FDF_LOG(ERROR, "Config for gpio %zu missing type", i);
return ZX_ERR_BAD_STATE;
}
const fuchsia_buttons::GpioType& gpio_type = gpio.config.type().value();
switch (gpio_type.Which()) {
case fuchsia_buttons::GpioType::Tag::kMatrixOutput: {
const fuchsia_buttons::MatrixOutputGpio& matrix = gpio_type.matrix_output().value();
if (!matrix.output_value().has_value()) {
FDF_LOG(ERROR, "Config for gpio %zu missing output value", i);
return ZX_ERR_BAD_STATE;
}
const fidl::WireResult result = gpio.client->SetBufferMode(
matrix.output_value().value() ? fuchsia_hardware_gpio::BufferMode::kOutputHigh
: fuchsia_hardware_gpio::BufferMode::kOutputLow);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send SetBufferMode request to gpio %zu: %s", i,
result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configure gpio %zu to output: %s", i,
zx_status_get_string(result->error_value()));
return ZX_ERR_NOT_SUPPORTED;
}
break;
}
case fuchsia_buttons::GpioType::Tag::kInterrupt: {
const fidl::WireResult result =
gpio.client->SetBufferMode(fuchsia_hardware_gpio::BufferMode::kInput);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send SetBufferMode request to gpio %zu: %s", i,
result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configure gpio %zu to input: %s", i,
zx_status_get_string(result->error_value()));
return ZX_ERR_NOT_SUPPORTED;
}
status = ConfigureInterrupt(i, kPortKeyInterruptStart + i);
if (status != ZX_OK) {
return status;
}
break;
}
case fuchsia_buttons::GpioType::Tag::kPoll: {
const fidl::WireResult result =
gpio.client->SetBufferMode(fuchsia_hardware_gpio::BufferMode::kInput);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send SetBufferMode request to gpio %zu: %s", i,
result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configure gpio %zu to input: %s", i,
zx_status_get_string(result->error_value()));
return ZX_ERR_NOT_SUPPORTED;
}
break;
}
default:
FDF_LOG(ERROR, "Config for gpio %zu has unknown type %d", i, gpio_type.Which());
break;
}
}
auto f = [](void* arg) -> int { return reinterpret_cast<ButtonsDevice*>(arg)->Thread(); };
const int rc = thrd_create_with_name(&thread_, f, this, "buttons-thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void ButtonsDevice::ShutDown() {
zx_port_packet packet = {kPortKeyShutDown, ZX_PKT_TYPE_USER, ZX_OK, {}};
const zx_status_t status = port_.queue(&packet);
ZX_ASSERT(status == ZX_OK);
thread_started_.Wait();
thrd_join(thread_, NULL);
for (Gpio& gpio : gpios_) {
gpio.irq.destroy();
}
}
} // namespace buttons