blob: efca04ae5ba3df4877a01dcb07320ebee78fdd00 [file] [log] [blame]
// Copyright 2021 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 "ti-tca6408a.h"
#include <lib/ddk/metadata.h>
#include <lib/driver/compat/cpp/metadata.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/component/cpp/node_add_args.h>
namespace {
// Arbitrary values for I2C retries.
constexpr uint8_t kI2cRetries = 10;
constexpr zx::duration kI2cRetryDelay = zx::usec(1);
} // namespace
namespace gpio {
zx::result<> TiTca6408aDevice::Start() {
// Get I2C.
ddk::I2cChannel i2c;
{
zx::result result = incoming()->Connect<fuchsia_hardware_i2c::Service::Device>("i2c");
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to open i2c service: %s", result.status_string());
return result.take_error();
}
i2c = std::move(result.value());
// Clear the polarity inversion register.
const uint8_t write_buf[2] = {static_cast<uint8_t>(TiTca6408a::Register::kPolarityInversion),
0};
i2c.WriteSyncRetries(write_buf, sizeof(write_buf), kI2cRetries, kI2cRetryDelay);
}
zx::result pin_index_offset =
compat::GetMetadata<uint32_t>(incoming(), DEVICE_METADATA_PRIVATE, "pdev");
if (pin_index_offset.is_error()) {
FDF_LOG(ERROR, "Failed to get pin_index_offset %s", pin_index_offset.status_string());
return pin_index_offset.take_error();
}
ZX_ASSERT(compat_server_
.Initialize(incoming(), outgoing(), node_name(), kDeviceName,
compat::ForwardMetadata::All())
.is_ok());
device_ = std::make_unique<TiTca6408a>(std::move(i2c), *pin_index_offset.value());
auto result = outgoing()->AddService<fuchsia_hardware_gpioimpl::Service>(
fuchsia_hardware_gpioimpl::Service::InstanceHandler({
.device = bindings_.CreateHandler(device_.get(), fdf::Dispatcher::GetCurrent()->get(),
fidl::kIgnoreBindingClosure),
}));
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to add Device service %s", result.status_string());
return result.take_error();
}
return CreateNode();
}
void TiTca6408aDevice::Stop() {
auto status = controller_->Remove();
if (!status.ok()) {
FDF_LOG(ERROR, "Could not remove child: %s", status.status_string());
}
}
zx::result<> TiTca6408aDevice::CreateNode() {
fidl::Arena arena;
auto offers = compat_server_.CreateOffers2(arena);
offers.push_back(
fdf::MakeOffer2<fuchsia_hardware_gpioimpl::Service>(arena, component::kDefaultInstance));
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, kDeviceName)
.offers2(arena, std::move(offers))
.Build();
zx::result controller_endpoints =
fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
ZX_ASSERT_MSG(controller_endpoints.is_ok(), "Failed to create endpoints: %s",
controller_endpoints.status_string());
fidl::WireResult result =
fidl::WireCall(node())->AddChild(args, std::move(controller_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));
return zx::ok();
}
void TiTca6408a::ConfigIn(ConfigInRequest& request, ConfigInCompleter::Sync& completer) {
if (!IsIndexInRange(request.index())) {
completer.Reply(fit::error(ZX_ERR_OUT_OF_RANGE));
return;
}
if (request.flags() != fuchsia_hardware_gpio::GpioFlags::kNoPull) {
completer.Reply(fit::error(ZX_ERR_NOT_SUPPORTED));
return;
}
auto result = SetBit(Register::kConfiguration, request.index());
if (result.is_error()) {
completer.Reply(fit::error(result.error_value()));
return;
}
completer.Reply(fit::ok());
}
void TiTca6408a::ConfigOut(ConfigOutRequest& request, ConfigOutCompleter::Sync& completer) {
if (zx_status_t status = Write(request.index(), request.initial_value()); status != ZX_OK) {
completer.Reply(zx::error(status));
return;
}
auto result = ClearBit(Register::kConfiguration, request.index());
if (result.is_error()) {
completer.Reply(zx::error(result.status_value()));
return;
}
completer.Reply(fit::ok());
}
void TiTca6408a::SetAltFunction(SetAltFunctionRequest& request,
SetAltFunctionCompleter::Sync& completer) {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void TiTca6408a::SetDriveStrength(SetDriveStrengthRequest& request,
SetDriveStrengthCompleter::Sync& completer) {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void TiTca6408a::GetDriveStrength(GetDriveStrengthRequest& request,
GetDriveStrengthCompleter::Sync& completer) {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void TiTca6408a::Read(ReadRequest& request, ReadCompleter::Sync& completer) {
if (!IsIndexInRange(request.index())) {
completer.Reply(zx::error(ZX_ERR_OUT_OF_RANGE));
return;
}
zx::result<uint8_t> value = ReadBit(Register::kInputPort, request.index());
if (value.is_error()) {
completer.Reply(zx::error(value.error_value()));
return;
}
completer.Reply(fit::ok(value.value()));
}
void TiTca6408a::Write(WriteRequest& request, WriteCompleter::Sync& completer) {
auto status = Write(request.index(), request.value());
if (status != ZX_OK) {
completer.Reply(fit::error(status));
return;
}
completer.Reply(fit::ok());
}
zx_status_t TiTca6408a::Write(uint32_t index, uint8_t value) {
if (!IsIndexInRange(index)) {
return ZX_ERR_OUT_OF_RANGE;
}
const zx::result<> status =
value ? SetBit(Register::kOutputPort, index) : ClearBit(Register::kOutputPort, index);
return status.status_value();
}
void TiTca6408a::GetInterrupt(GetInterruptRequest& request,
GetInterruptCompleter::Sync& completer) {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void TiTca6408a::ReleaseInterrupt(ReleaseInterruptRequest& request,
ReleaseInterruptCompleter::Sync& completer) {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void TiTca6408a::SetPolarity(SetPolarityRequest& request, SetPolarityCompleter::Sync& completer) {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void TiTca6408a::GetPins(GetPinsCompleter::Sync& completer) {}
void TiTca6408a::GetInitSteps(GetInitStepsCompleter::Sync& completer) {}
void TiTca6408a::GetControllerId(GetControllerIdCompleter::Sync& completer) { completer.Reply(0); }
zx::result<uint8_t> TiTca6408a::ReadBit(Register reg, uint32_t index) {
const auto bit = static_cast<uint8_t>(1 << (index - pin_index_offset_));
const auto address = static_cast<uint8_t>(reg);
uint8_t value = 0;
auto status = i2c_.WriteReadSyncRetries(&address, sizeof(address), &value, sizeof(value),
kI2cRetries, kI2cRetryDelay);
if (status.status != ZX_OK) {
FDF_LOG(ERROR, "Failed to read register %u: %s", address, zx_status_get_string(status.status));
return zx::error(status.status);
}
return zx::ok(static_cast<uint8_t>((value & bit) ? 1 : 0));
}
zx::result<> TiTca6408a::SetBit(Register reg, uint32_t index) {
const auto bit = static_cast<uint8_t>(1 << (index - pin_index_offset_));
const auto address = static_cast<uint8_t>(reg);
uint8_t value = 0;
auto status = i2c_.WriteReadSyncRetries(&address, sizeof(address), &value, sizeof(value),
kI2cRetries, kI2cRetryDelay);
if (status.status != ZX_OK) {
FDF_LOG(ERROR, "Failed to read register %u: %s", address, zx_status_get_string(status.status));
return zx::error(status.status);
}
const uint8_t write_buf[2] = {address, static_cast<uint8_t>(value | bit)};
status = i2c_.WriteSyncRetries(write_buf, sizeof(write_buf), kI2cRetries, kI2cRetryDelay);
if (status.status != ZX_OK) {
FDF_LOG(ERROR, "Failed to write register %u: %s", address, zx_status_get_string(status.status));
return zx::error(status.status);
}
return zx::ok();
}
zx::result<> TiTca6408a::ClearBit(Register reg, uint32_t index) {
const auto bit = static_cast<uint8_t>(1 << (index - pin_index_offset_));
const auto address = static_cast<uint8_t>(reg);
uint8_t value = 0;
auto status = i2c_.WriteReadSyncRetries(&address, sizeof(address), &value, sizeof(value),
kI2cRetries, kI2cRetryDelay);
if (status.status != ZX_OK) {
FDF_LOG(ERROR, "Failed to read register %u: %s", address, zx_status_get_string(status.status));
return zx::error(status.status);
}
const uint8_t write_buf[2] = {address, static_cast<uint8_t>(value & ~bit)};
status = i2c_.WriteSyncRetries(write_buf, sizeof(write_buf), kI2cRetries, kI2cRetryDelay);
if (status.status != ZX_OK) {
FDF_LOG(ERROR, "Failed to write register %u: %s", address, zx_status_get_string(status.status));
return zx::error(status.status);
}
return zx::ok();
}
} // namespace gpio
FUCHSIA_DRIVER_EXPORT(gpio::TiTca6408aDevice);