| // Copyright 2019 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 "gpio-light.h" |
| |
| #include <assert.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| |
| #include <memory> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/metadata.h> |
| #include <ddk/platform-defs.h> |
| #include <ddktl/fidl.h> |
| #include <ddktl/protocol/composite.h> |
| #include <fbl/alloc_checker.h> |
| |
| namespace gpio_light { |
| |
| void GpioLight::GetNumLights(GetNumLightsCompleter::Sync& completer) { |
| completer.Reply(gpio_count_); |
| } |
| |
| void GpioLight::GetNumLightGroups(GetNumLightGroupsCompleter::Sync& completer) { |
| completer.Reply(0); |
| } |
| |
| void GpioLight::GetInfo(uint32_t index, GetInfoCompleter::Sync& completer) { |
| if (index >= gpio_count_) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::INVALID_INDEX); |
| return; |
| } |
| |
| char name[20]; |
| if (names_.size() > 0) { |
| snprintf(name, sizeof(name), "%s\n", names_.data() + index * kNameLength); |
| } else { |
| // Return "gpio-X" if no metadata was provided. |
| snprintf(name, sizeof(name), "gpio-%u\n", index); |
| } |
| |
| completer.ReplySuccess({ |
| .name = ::fidl::StringView(name, strlen(name)), |
| .capability = ::llcpp::fuchsia::hardware::light::Capability::SIMPLE, |
| }); |
| } |
| |
| void GpioLight::GetCurrentSimpleValue(uint32_t index, |
| GetCurrentSimpleValueCompleter::Sync& completer) { |
| if (index >= gpio_count_) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::INVALID_INDEX); |
| return; |
| } |
| |
| uint8_t value; |
| if (gpios_[index].Read(&value) != ZX_OK) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::FAILED); |
| } else { |
| completer.ReplySuccess(value); |
| } |
| } |
| |
| void GpioLight::SetSimpleValue(uint32_t index, bool value, |
| SetSimpleValueCompleter::Sync& completer) { |
| if (index >= gpio_count_) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::INVALID_INDEX); |
| return; |
| } |
| |
| if (gpios_[index].Write(value) != ZX_OK) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::FAILED); |
| } else { |
| completer.ReplySuccess(); |
| } |
| } |
| |
| void GpioLight::GetCurrentBrightnessValue(uint32_t index, |
| GetCurrentBrightnessValueCompleter::Sync& completer) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::NOT_SUPPORTED); |
| } |
| |
| void GpioLight::SetBrightnessValue(uint32_t index, double value, |
| SetBrightnessValueCompleter::Sync& completer) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::NOT_SUPPORTED); |
| } |
| |
| void GpioLight::GetCurrentRgbValue(uint32_t index, GetCurrentRgbValueCompleter::Sync& completer) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::NOT_SUPPORTED); |
| } |
| |
| void GpioLight::SetRgbValue(uint32_t index, llcpp::fuchsia::hardware::light::Rgb value, |
| SetRgbValueCompleter::Sync& completer) { |
| completer.ReplyError(::llcpp::fuchsia::hardware::light::LightError::NOT_SUPPORTED); |
| } |
| |
| zx_status_t GpioLight::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) { |
| DdkTransaction transaction(txn); |
| llcpp::fuchsia::hardware::light::Light::Dispatch(this, msg, &transaction); |
| return transaction.Status(); |
| } |
| |
| void GpioLight::DdkRelease() { delete this; } |
| |
| zx_status_t GpioLight::Create(void* ctx, zx_device_t* parent) { |
| fbl::AllocChecker ac; |
| auto dev = std::unique_ptr<GpioLight>(new (&ac) GpioLight(parent)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| auto status = dev->Init(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // devmgr is now in charge of the device. |
| __UNUSED auto* dummy = dev.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t GpioLight::Init() { |
| ddk::CompositeProtocolClient composite(parent_); |
| if (!composite.is_valid()) { |
| zxlogf(ERROR, "GpioLight: Could not get composite protocol"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| auto fragment_count = composite.GetFragmentCount(); |
| if (fragment_count <= 0) { |
| return ZX_ERR_INTERNAL; |
| } |
| // fragment 0 is platform device, only used for passing metadata. |
| gpio_count_ = fragment_count - 1; |
| |
| size_t metadata_size; |
| size_t expected = gpio_count_ * kNameLength; |
| auto status = device_get_metadata_size(parent(), DEVICE_METADATA_NAME, &metadata_size); |
| if (status == ZX_OK) { |
| if (expected != metadata_size) { |
| zxlogf(ERROR, "%s: expected metadata size %zu, got %zu", __func__, expected, metadata_size); |
| status = ZX_ERR_INTERNAL; |
| } |
| } |
| if (status == ZX_OK) { |
| fbl::AllocChecker ac; |
| names_.reset(new (&ac) char[metadata_size], metadata_size); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| status = device_get_metadata(parent(), DEVICE_METADATA_NAME, names_.data(), metadata_size, |
| &expected); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| composite_device_fragment_t fragments[fragment_count]; |
| size_t actual; |
| composite.GetFragmentsNew(fragments, fragment_count, &actual); |
| if (actual != fragment_count) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| fbl::AllocChecker ac; |
| auto* gpios = new (&ac) ddk::GpioProtocolClient[gpio_count_]; |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| gpios_.reset(gpios, gpio_count_); |
| |
| for (uint32_t i = 0; i < gpio_count_; i++) { |
| auto* gpio = &gpios_[i]; |
| auto status = device_get_protocol(fragments[i + 1].device, ZX_PROTOCOL_GPIO, gpio); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = gpio->ConfigOut(0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "gpio-light: ConfigOut failed for gpio %u", i); |
| return status; |
| } |
| } |
| |
| return DdkAdd("gpio-light", DEVICE_ADD_NON_BINDABLE); |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = GpioLight::Create; |
| return ops; |
| }(); |
| |
| } // namespace gpio_light |
| |
| ZIRCON_DRIVER_BEGIN(gpio_light, gpio_light::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_GPIO_LIGHT), ZIRCON_DRIVER_END(gpio_light) |