| // 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 "hid-buttons.h" |
| |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/types.h> |
| |
| #include <cstdint> |
| #include <memory> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/metadata.h> |
| #include <ddk/metadata/buttons.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/buttons.h> |
| #include <ddktl/protocol/composite.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_call.h> |
| #include <hid/descriptor.h> |
| |
| namespace buttons { |
| |
| namespace { |
| |
| uint32_t to_bit_mask(ButtonType type) { return 1 << static_cast<uint8_t>(type); } |
| |
| // Takes in a BUTTON_ID_ value and returns a bitmask of ButtonTypes that are associated with this |
| // button id. Bit position corresponds to ButtonType e.g (1 << ButtonType::VOLUME_UP) is the bit |
| // for the volume_up button type. |
| uint32_t ButtonIdToButtonTypeBitMask(uint8_t button_id) { |
| switch (button_id) { |
| case BUTTONS_ID_VOLUME_UP: |
| return to_bit_mask(ButtonType::VOLUME_UP); |
| case BUTTONS_ID_VOLUME_DOWN: |
| return to_bit_mask(ButtonType::VOLUME_DOWN); |
| case BUTTONS_ID_FDR: |
| return to_bit_mask(ButtonType::RESET); |
| case BUTTONS_ID_MIC_MUTE: |
| return to_bit_mask(ButtonType::MUTE); |
| case BUTTONS_ID_PLAY_PAUSE: |
| return to_bit_mask(ButtonType::PLAY_PAUSE); |
| case BUTTONS_ID_KEY_A: |
| return to_bit_mask(ButtonType::KEY_A); |
| case BUTTONS_ID_KEY_M: |
| return to_bit_mask(ButtonType::KEY_M); |
| case BUTTONS_ID_CAM_MUTE: |
| return to_bit_mask(ButtonType::CAM_MUTE); |
| default: |
| return 0; |
| } |
| } |
| |
| bool input_reports_are_equal(const buttons_input_rpt_t& lhs, const buttons_input_rpt_t& rhs) { |
| return (lhs.rpt_id == rhs.rpt_id && lhs.volume_up == rhs.volume_up && |
| lhs.volume_down == rhs.volume_down && lhs.reset == rhs.reset && lhs.mute == rhs.mute && |
| lhs.camera_access_disabled == rhs.camera_access_disabled); |
| } |
| |
| } // namespace |
| |
| void HidButtonsDevice::Notify(uint32_t button_index) { |
| // HID Report |
| buttons_input_rpt_t input_rpt; |
| size_t out_len; |
| zx_status_t status = |
| HidbusGetReport(0, BUTTONS_RPT_ID_INPUT, &input_rpt, sizeof(input_rpt), &out_len); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s HidbusGetReport failed %d", __FUNCTION__, status); |
| } else if (!input_reports_are_equal(last_report_, input_rpt)) { |
| fbl::AutoLock lock(&client_lock_); |
| if (client_.is_valid()) { |
| client_.IoQueue(&input_rpt, sizeof(buttons_input_rpt_t), zx_clock_get_monotonic()); |
| last_report_ = input_rpt; |
| } |
| } |
| if (buttons_[button_index].id == BUTTONS_ID_FDR) { |
| zxlogf(INFO, "FDR (up and down buttons) pressed"); |
| } |
| |
| // Notify anyone registered for this ButtonType. |
| { |
| fbl::AutoLock lock(&channels_lock_); |
| uint32_t types = ButtonIdToButtonTypeBitMask(buttons_[button_index].id); |
| bool button_value = debounce_states_[button_index].value; |
| // Go through each ButtonType and send notifications. |
| for (uint8_t raw_type = 0; raw_type < static_cast<uint8_t>(ButtonType::MAX); raw_type++) { |
| if ((types & (1 << raw_type)) == 0) { |
| continue; |
| } |
| |
| ButtonType type = static_cast<ButtonType>(raw_type); |
| for (ButtonsNotifyInterface* interface : registered_notifiers_[type]) { |
| interface->binding()->OnNotify(type, button_value); |
| } |
| } |
| } |
| |
| debounce_states_[button_index].enqueued = false; |
| } |
| |
| int HidButtonsDevice::Thread() { |
| while (1) { |
| zx_port_packet_t packet; |
| zx_status_t status = port_.wait(zx::time::infinite(), &packet); |
| zxlogf(DEBUG, "%s msg received on port key %lu", __FUNCTION__, packet.key); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s port wait failed %d", __FUNCTION__, status); |
| return thrd_error; |
| } |
| |
| if (packet.key == kPortKeyShutDown) { |
| zxlogf(INFO, "%s shutting down", __FUNCTION__); |
| return thrd_success; |
| } |
| |
| if (packet.key >= kPortKeyInterruptStart && |
| packet.key < (kPortKeyInterruptStart + buttons_.size())) { |
| uint32_t type = static_cast<uint32_t>(packet.key - kPortKeyInterruptStart); |
| if (gpios_[type].config.type == BUTTONS_GPIO_TYPE_INTERRUPT) { |
| // We need to reconfigure the GPIO to catch the opposite polarity. |
| debounce_states_[type].value = ReconfigurePolarity(type, packet.key); |
| |
| // 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].enqueued = true; |
| } |
| |
| gpios_[type].irq.ack(); |
| } |
| |
| if (packet.key >= kPortKeyTimerStart && packet.key < (kPortKeyTimerStart + buttons_.size())) { |
| Notify(static_cast<uint32_t>(packet.key - kPortKeyTimerStart)); |
| } |
| } |
| return thrd_success; |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusStart(const hidbus_ifc_protocol_t* ifc) { |
| fbl::AutoLock lock(&client_lock_); |
| if (client_.is_valid()) { |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| |
| client_ = ddk::HidbusIfcProtocolClient(ifc); |
| return ZX_OK; |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusQuery(uint32_t options, hid_info_t* info) { |
| if (!info) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| info->dev_num = 0; |
| info->device_class = HID_DEVICE_CLASS_OTHER; |
| info->boot_device = false; |
| |
| return ZX_OK; |
| } |
| |
| void HidButtonsDevice::HidbusStop() { |
| fbl::AutoLock lock(&client_lock_); |
| client_.clear(); |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusGetDescriptor(hid_description_type_t desc_type, |
| void* out_data_buffer, size_t data_size, |
| size_t* out_data_actual) { |
| const uint8_t* desc; |
| size_t desc_size = get_buttons_report_desc(&desc); |
| if (data_size < desc_size) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| memcpy(out_data_buffer, desc, desc_size); |
| *out_data_actual = desc_size; |
| return ZX_OK; |
| } |
| |
| // Requires interrupts to be disabled for all rows/cols. |
| bool HidButtonsDevice::MatrixScan(uint32_t row, uint32_t col, zx_duration_t delay) { |
| gpio_config_in(&gpios_[col].gpio, GPIO_NO_PULL); // Float column to find row in use. |
| zx::nanosleep(zx::deadline_after(zx::duration(delay))); |
| |
| uint8_t val; |
| gpio_read(&gpios_[row].gpio, &val); |
| |
| gpio_config_out(&gpios_[col].gpio, gpios_[col].config.output_value); |
| zxlogf(DEBUG, "%s row %u col %u val %u", __FUNCTION__, row, col, val); |
| return static_cast<bool>(val); |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, |
| size_t len, size_t* out_len) { |
| if (!data || !out_len) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (rpt_id != BUTTONS_RPT_ID_INPUT) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| *out_len = sizeof(buttons_input_rpt_t); |
| if (*out_len > len) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| buttons_input_rpt_t input_rpt = {}; |
| input_rpt.rpt_id = BUTTONS_RPT_ID_INPUT; |
| |
| for (size_t i = 0; i < buttons_.size(); ++i) { |
| bool new_value = false; // A value true means a button is pressed. |
| if (buttons_[i].type == BUTTONS_TYPE_MATRIX) { |
| new_value = MatrixScan(buttons_[i].gpioA_idx, buttons_[i].gpioB_idx, buttons_[i].gpio_delay); |
| } else if (buttons_[i].type == BUTTONS_TYPE_DIRECT) { |
| uint8_t val; |
| gpio_read(&gpios_[buttons_[i].gpioA_idx].gpio, &val); |
| zxlogf(DEBUG, "%s GPIO direct read %u for button %lu", __FUNCTION__, val, i); |
| new_value = val; |
| } else { |
| zxlogf(ERROR, "%s unknown button type %u", __FUNCTION__, buttons_[i].type); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| if (gpios_[i].config.flags & BUTTONS_GPIO_FLAG_INVERTED) { |
| new_value = !new_value; |
| } |
| |
| zxlogf(DEBUG, "%s GPIO new value %u for button %lu", __FUNCTION__, new_value, i); |
| fill_button_in_report(buttons_[i].id, new_value, &input_rpt); |
| } |
| auto out = static_cast<buttons_input_rpt_t*>(data); |
| *out = input_rpt; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const void* data, |
| size_t len) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t HidButtonsDevice::HidbusGetProtocol(uint8_t* protocol) { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t HidButtonsDevice::HidbusSetProtocol(uint8_t protocol) { return ZX_OK; } |
| |
| uint8_t HidButtonsDevice::ReconfigurePolarity(uint32_t idx, uint64_t int_port) { |
| zxlogf(DEBUG, "%s gpio %u port %lu", __FUNCTION__, idx, int_port); |
| uint8_t current = 0, old; |
| gpio_read(&gpios_[idx].gpio, ¤t); |
| do { |
| gpio_set_polarity(&gpios_[idx].gpio, current ? GPIO_POLARITY_LOW : GPIO_POLARITY_HIGH); |
| old = current; |
| gpio_read(&gpios_[idx].gpio, ¤t); |
| zxlogf(TRACE, "%s old gpio %u new gpio %u", __FUNCTION__, old, current); |
| // If current switches after setup, we setup a new trigger for it (opposite edge). |
| } while (current != old); |
| return current; |
| } |
| |
| zx_status_t HidButtonsDevice::ConfigureInterrupt(uint32_t idx, uint64_t int_port) { |
| zxlogf(DEBUG, "%s gpio %u port %lu", __FUNCTION__, idx, int_port); |
| zx_status_t status; |
| uint8_t current = 0; |
| gpio_read(&gpios_[idx].gpio, ¤t); |
| gpio_release_interrupt(&gpios_[idx].gpio); |
| // We setup a trigger for the opposite of the current GPIO value. |
| status = gpio_get_interrupt(&gpios_[idx].gpio, |
| current ? ZX_INTERRUPT_MODE_EDGE_LOW : ZX_INTERRUPT_MODE_EDGE_HIGH, |
| gpios_[idx].irq.reset_and_get_address()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s gpio_get_interrupt failed %d", __FUNCTION__, status); |
| return status; |
| } |
| status = gpios_[idx].irq.bind(port_, int_port, 0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s zx_interrupt_bind failed %d", __FUNCTION__, status); |
| return status; |
| } |
| // To make sure polarity is correct in case it changed during configuration. |
| ReconfigurePolarity(idx, int_port); |
| return ZX_OK; |
| } |
| |
| zx_status_t HidButtonsDevice::Bind(fbl::Array<Gpio> gpios, |
| fbl::Array<buttons_button_config_t> buttons) { |
| { |
| fbl::AutoLock lock(&channels_lock_); |
| for (uint8_t raw_type = 0; raw_type < static_cast<uint8_t>(ButtonType::MAX); raw_type++) { |
| ButtonType type = static_cast<ButtonType>(raw_type); |
| registered_notifiers_[type] = std::set<ButtonsNotifyInterface*>(); |
| } |
| } |
| zx_status_t status; |
| |
| buttons_ = std::move(buttons); |
| gpios_ = std::move(gpios); |
| fbl::AllocChecker ac; |
| fbl::AutoLock lock(&channels_lock_); |
| |
| status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s port_create failed %d", __FUNCTION__, 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; |
| } |
| |
| // Check the metadata. |
| for (uint32_t i = 0; i < buttons_.size(); ++i) { |
| if (buttons_[i].gpioA_idx >= gpios_.size()) { |
| zxlogf(ERROR, "%s invalid gpioA_idx %u", __FUNCTION__, buttons_[i].gpioA_idx); |
| return ZX_ERR_INTERNAL; |
| } |
| if (buttons_[i].gpioB_idx >= gpios_.size()) { |
| zxlogf(ERROR, "%s invalid gpioB_idx %u", __FUNCTION__, buttons_[i].gpioB_idx); |
| return ZX_ERR_INTERNAL; |
| } |
| if (gpios_[buttons_[i].gpioA_idx].config.type != BUTTONS_GPIO_TYPE_INTERRUPT) { |
| zxlogf(ERROR, "%s invalid gpioA type %u", __FUNCTION__, |
| gpios_[buttons_[i].gpioA_idx].config.type); |
| return ZX_ERR_INTERNAL; |
| } |
| if (buttons_[i].type == BUTTONS_TYPE_MATRIX && |
| gpios_[buttons_[i].gpioB_idx].config.type != BUTTONS_GPIO_TYPE_MATRIX_OUTPUT) { |
| zxlogf(ERROR, "%s invalid matrix gpioB type %u", __FUNCTION__, |
| gpios_[buttons_[i].gpioB_idx].config.type); |
| return ZX_ERR_INTERNAL; |
| } |
| if (buttons_[i].id == BUTTONS_ID_FDR) { |
| zxlogf(INFO, "FDR (up and down buttons) setup to GPIO %u", buttons_[i].gpioA_idx); |
| } |
| |
| // Update the button_map_ array which maps ButtonTypes to the button. |
| uint32_t types = ButtonIdToButtonTypeBitMask(buttons_[i].id); |
| for (uint8_t raw_type = 0; raw_type < static_cast<uint8_t>(ButtonType::MAX); raw_type++) { |
| if ((types & (1 << raw_type)) == 0) { |
| continue; |
| } |
| |
| ButtonType type = static_cast<ButtonType>(raw_type); |
| button_map_[type] = i; |
| } |
| } |
| |
| // Setup. |
| for (uint32_t i = 0; i < gpios_.size(); ++i) { |
| status = gpio_set_alt_function(&gpios_[i].gpio, 0); // 0 means function GPIO. |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s gpio_set_alt_function failed %d", __FUNCTION__, status); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (gpios_[i].config.type == BUTTONS_GPIO_TYPE_MATRIX_OUTPUT) { |
| status = gpio_config_out(&gpios_[i].gpio, gpios_[i].config.output_value); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s gpio_config_out failed %d", __FUNCTION__, status); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } else if (gpios_[i].config.type == BUTTONS_GPIO_TYPE_INTERRUPT) { |
| status = gpio_config_in(&gpios_[i].gpio, gpios_[i].config.internal_pull); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s gpio_config_in failed %d", __FUNCTION__, status); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| status = ConfigureInterrupt(i, kPortKeyInterruptStart + i); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| } |
| |
| size_t out_len = 0; |
| status = HidbusGetReport(0, BUTTONS_RPT_ID_INPUT, &last_report_, sizeof(last_report_), &out_len); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s HidbusGetReport failed %d", __FUNCTION__, status); |
| } |
| |
| auto f = [](void* arg) -> int { return reinterpret_cast<HidButtonsDevice*>(arg)->Thread(); }; |
| int rc = thrd_create_with_name(&thread_, f, this, "hid-buttons-thread"); |
| if (rc != thrd_success) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| status = DdkAdd("hid-buttons", DEVICE_ADD_NON_BINDABLE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s DdkAdd failed %d", __FUNCTION__, status); |
| ShutDown(); |
| return status; |
| } |
| |
| std::unique_ptr<HidButtonsHidBusFunction> hidbus_function( |
| new (&ac) HidButtonsHidBusFunction(zxdev(), this)); |
| if (!ac.check()) { |
| DdkAsyncRemove(); |
| return ZX_ERR_NO_MEMORY; |
| } |
| status = hidbus_function->DdkAdd("hidbus_function"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s DdkAdd for Hidbus Function failed %d", __FUNCTION__, status); |
| DdkAsyncRemove(); |
| return status; |
| } |
| hidbus_function_ = hidbus_function.release(); |
| |
| std::unique_ptr<HidButtonsButtonsFunction> buttons_function( |
| new (&ac) HidButtonsButtonsFunction(zxdev(), this)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| status = buttons_function->DdkAdd("buttons_function"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s DdkAdd for Buttons Function failed %d", __FUNCTION__, status); |
| DdkAsyncRemove(); |
| return status; |
| } |
| buttons_function_ = buttons_function.release(); |
| |
| return ZX_OK; |
| } |
| |
| void HidButtonsDevice::ShutDown() { |
| zx_port_packet packet = {kPortKeyShutDown, ZX_PKT_TYPE_USER, ZX_OK, {}}; |
| zx_status_t status = port_.queue(&packet); |
| ZX_ASSERT(status == ZX_OK); |
| thrd_join(thread_, NULL); |
| for (uint32_t i = 0; i < gpios_.size(); ++i) { |
| gpios_[i].irq.destroy(); |
| } |
| fbl::AutoLock lock(&client_lock_); |
| client_.clear(); |
| |
| hidbus_function_ = nullptr; |
| buttons_function_ = nullptr; |
| } |
| |
| void HidButtonsDevice::DdkUnbind(ddk::UnbindTxn txn) { |
| ShutDown(); |
| txn.Reply(); |
| } |
| |
| void HidButtonsDevice::DdkRelease() { delete this; } |
| |
| static zx_status_t hid_buttons_bind(void* ctx, zx_device_t* parent) { |
| fbl::AllocChecker ac; |
| auto dev = fbl::make_unique_checked<buttons::HidButtonsDevice>(&ac, parent); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Get buttons metadata. |
| size_t actual = 0; |
| auto status = device_get_metadata_size(parent, DEVICE_METADATA_BUTTONS_BUTTONS, &actual); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s device_get_metadata_size failed %d", __FILE__, status); |
| return ZX_OK; |
| } |
| size_t n_buttons = actual / sizeof(buttons_button_config_t); |
| auto buttons = fbl::Array(new (&ac) buttons_button_config_t[n_buttons], n_buttons); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| actual = 0; |
| status = device_get_metadata(parent, DEVICE_METADATA_BUTTONS_BUTTONS, buttons.data(), |
| buttons.size() * sizeof(buttons_button_config_t), &actual); |
| if (status != ZX_OK || actual != buttons.size() * sizeof(buttons_button_config_t)) { |
| zxlogf(ERROR, "%s device_get_metadata failed %d", __FILE__, status); |
| return status; |
| } |
| |
| // Get gpios metadata. |
| actual = 0; |
| status = device_get_metadata_size(parent, DEVICE_METADATA_BUTTONS_GPIOS, &actual); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s device_get_metadata_size failed %d", __FILE__, status); |
| return ZX_OK; |
| } |
| size_t n_gpios = actual / sizeof(buttons_gpio_config_t); |
| auto configs = fbl::Array(new (&ac) buttons_gpio_config_t[n_gpios], n_gpios); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| actual = 0; |
| status = device_get_metadata(parent, DEVICE_METADATA_BUTTONS_GPIOS, configs.data(), |
| configs.size() * sizeof(buttons_gpio_config_t), &actual); |
| if (status != ZX_OK || actual != configs.size() * sizeof(buttons_gpio_config_t)) { |
| zxlogf(ERROR, "%s device_get_metadata failed %d", __FILE__, status); |
| return status; |
| } |
| |
| // Get the GPIOs. |
| ddk::CompositeProtocolClient composite(parent); |
| if (!composite.is_valid()) { |
| zxlogf(ERROR, "HidButtonsDevice: Could not get composite protocol"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| auto fragment_count = composite.GetFragmentCount(); |
| if (fragment_count != n_gpios) { |
| zxlogf(ERROR, "%s Could not get fragment count", __func__); |
| return ZX_ERR_INTERNAL; |
| } |
| zx_device_t* fragments[fragment_count]; |
| composite.GetFragments(fragments, fragment_count, &actual); |
| if (actual != fragment_count) { |
| zxlogf(ERROR, "%s Fragment count did not match", __func__); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Prepare gpios array. |
| auto gpios = fbl::Array(new (&ac) HidButtonsDevice::Gpio[n_gpios], n_gpios); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| for (uint32_t i = 0; i < n_gpios; ++i) { |
| status = device_get_protocol(fragments[i], ZX_PROTOCOL_GPIO, &gpios[i].gpio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s Could not get protocol", __func__); |
| return ZX_ERR_INTERNAL; |
| } |
| gpios[i].config = configs[i]; |
| } |
| |
| status = dev->Bind(std::move(gpios), std::move(buttons)); |
| if (status == ZX_OK) { |
| // devmgr is now in charge of the memory for dev. |
| __UNUSED auto ptr = dev.release(); |
| } |
| return status; |
| } |
| |
| zx_status_t HidButtonsDevice::ButtonsGetChannel(zx::channel chan, async_dispatcher_t* dispatcher) { |
| fbl::AutoLock lock(&channels_lock_); |
| |
| interfaces_.emplace_back(this); |
| auto status = interfaces_.back().Init(dispatcher, std::move(chan)); |
| if (status != ZX_OK) |
| interfaces_.pop_back(); |
| return status; |
| } |
| |
| bool HidButtonsDevice::GetState(ButtonType type) { |
| uint8_t val; |
| gpio_read(&gpios_[buttons_[button_map_[type]].gpioA_idx].gpio, &val); |
| return static_cast<bool>(val); |
| } |
| |
| zx_status_t HidButtonsDevice::RegisterNotify(uint8_t types, ButtonsNotifyInterface* notify) { |
| fbl::AutoLock lock(&channels_lock_); |
| // Go through each type in ButtonType and update our registration. |
| for (uint8_t raw_type = 0; raw_type < static_cast<uint8_t>(ButtonType::MAX); raw_type++) { |
| ButtonType type = static_cast<ButtonType>(raw_type); |
| auto& notify_set = registered_notifiers_[type]; |
| if ((types & (1 << raw_type)) == 0) { |
| // Our type does not exist in the bitmask and so we should de-register. |
| notify_set.erase(notify); |
| } else { |
| // Our type exists in the bitmask and so we should register. |
| notify_set.insert(notify); |
| } |
| } |
| return ZX_OK; |
| } |
| |
| void HidButtonsDevice::ClosingChannel(ButtonsNotifyInterface* notify) { |
| fbl::AutoLock lock(&channels_lock_); |
| // Remove this notifier from anything it's registered to listen to. |
| for (auto& [type, notify_set] : registered_notifiers_) { |
| notify_set.erase(notify); |
| } |
| |
| // release ownership |
| for (auto iter = interfaces_.begin(); iter != interfaces_.end(); ++iter) { |
| if (&(*iter) == notify) { |
| interfaces_.erase(iter); |
| return; |
| } |
| } |
| zxlogf(ERROR, "%s interfaces_ could not find channel", __func__); |
| } |
| |
| static constexpr zx_driver_ops_t hid_buttons_driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = hid_buttons_bind; |
| return ops; |
| }(); |
| |
| } // namespace buttons |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(hid_buttons, buttons::hid_buttons_driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_HID_BUTTONS), |
| ZIRCON_DRIVER_END(hid_buttons) |
| // clang-format on |