blob: c15274fe3dae59f576f99e901d7743ff1ed73938 [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 "src/devices/power/drivers/fusb302/fusb302.h"
#include <fidl/fuchsia.hardware.gpio/cpp/wire.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/profile.h>
#include <lib/zx/result.h>
#include <lib/zx/timer.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <string>
#include <fbl/alloc_checker.h>
#include <fbl/string_buffer.h>
#include "src/devices/power/drivers/fusb302/fusb302-controls.h"
#include "src/devices/power/drivers/fusb302/pd-sink-state-machine.h"
#include "src/devices/power/drivers/fusb302/registers.h"
#include "src/devices/power/drivers/fusb302/state-machine-base.h"
#include "src/devices/power/drivers/fusb302/typec-port-state-machine.h"
namespace fusb302 {
void Fusb302::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt) {
ProcessStateChanges(signals_.ServiceInterrupts());
irq_.ack();
}
void Fusb302::HandleTimeout(async_dispatcher_t*, async::WaitBase*, zx_status_t status,
const zx_packet_signal_t*) {
HardwareStateChanges changes;
FDF_LOG(TRACE, "State machine timer fired off");
changes.timer_signaled = true;
ProcessStateChanges(changes);
}
void Fusb302::ProcessStateChanges(HardwareStateChanges changes) {
if (changes.received_reset) {
pd_state_machine_.DidReceiveSoftReset();
pd_state_machine_.Run(SinkPolicyEngineInput::kInitialized);
}
while (protocol_.HasUnreadMessage()) {
pd_state_machine_.Run(SinkPolicyEngineInput::kMessageReceived);
if (protocol_.HasUnreadMessage()) {
pd_state_machine_.ProcessUnexpectedMessage();
pd_state_machine_.Run(SinkPolicyEngineInput::kInitialized);
}
}
if (changes.port_state_changed) {
port_state_machine_.Run(TypeCPortInput::kPortStateChanged);
if (port_state_machine_.current_state() == TypeCPortState::kSinkAttached) {
pd_state_machine_.Run(SinkPolicyEngineInput::kInitialized);
} else {
pd_state_machine_.Reset();
}
}
if (changes.timer_signaled &&
port_state_machine_.current_state() == TypeCPortState::kSinkAttached) {
pd_state_machine_.Run(SinkPolicyEngineInput::kTimerFired);
}
}
zx_status_t Fusb302::ResetHardwareAndStartPowerRoleDetection() {
auto status = ResetReg::Get().FromValue(0).set_sw_res(true).WriteTo(i2c_);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to write Reset register: %s", zx_status_get_string(status));
return status;
}
zx::result<> result = signals_.InitInterruptUnit();
if (!result.is_ok()) {
return result.error_value();
}
result = controls_.ResetIntoPowerRoleDiscovery();
if (!result.is_ok()) {
return result.error_value();
}
return ZX_OK;
}
zx_status_t Fusb302::Init() {
zx::result<> result = identity_.ReadIdentity();
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to initialize inspect: %s", result.status_string());
return result.error_value();
}
zx_status_t status = ResetHardwareAndStartPowerRoleDetection();
if (status != ZX_OK) {
FDF_LOG(ERROR, "ResetHardwareAndStartPowerRoleDetection() failed: %s",
zx_status_get_string(status));
return status;
}
irq_handler_.set_object(irq_.get());
irq_handler_.Begin(dispatcher_.async_dispatcher());
return ZX_OK;
}
zx::result<> Fusb302::WaitAsyncForTimer(zx::timer& timer) {
timeout_handler_.set_object(timer.get());
timeout_handler_.set_trigger(ZX_TIMER_SIGNALED);
auto status = timeout_handler_.Begin(dispatcher_.async_dispatcher(),
fit::bind_member(this, &Fusb302::HandleTimeout));
if (status != ZX_OK) {
FDF_LOG(WARNING, "Failed to wait on timer: %s", zx_status_get_string(status));
}
return zx::make_result(status);
}
zx::result<> Fusb302Device::Start() {
// Map hardware resources.
fidl::ClientEnd<fuchsia_hardware_i2c::Device> i2c;
zx::interrupt irq;
{
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());
}
{
zx::result result = incoming()->Connect<fuchsia_hardware_gpio::Service::Device>("gpio");
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to open gpio service: %s", result.status_string());
return result.take_error();
}
auto gpio = fidl::WireSyncClient(std::move(result.value()));
if (auto result = gpio->ConfigIn(fuchsia_hardware_gpio::GpioFlags::kPullUp);
!result.ok() || result->is_error()) {
FDF_LOG(ERROR, "GPIO ConfigIn() failed: %s",
result.ok() ? zx_status_get_string(result->error_value())
: result.FormatDescription().c_str());
}
if (auto result = gpio->GetInterrupt(ZX_INTERRUPT_MODE_LEVEL_LOW);
!result.ok() || result->is_error()) {
FDF_LOG(ERROR, "GPIO GetInterrupt() failed: %s",
result.ok() ? zx_status_get_string(result->error_value())
: result.FormatDescription().c_str());
} else {
irq = std::move(result->value()->irq);
}
}
auto fusb302_dispatcher = fdf::SynchronizedDispatcher::Create(
{}, "fusb302", [&](fdf_dispatcher_t*) {}, "fuchsia.devices.power.drivers.fusb302.interrupt");
ZX_ASSERT_MSG(!fusb302_dispatcher.is_error(), "Creating dispatcher error: %s",
zx_status_get_string(fusb302_dispatcher.status_value()));
device_ = std::make_unique<fusb302::Fusb302>(std::move(*fusb302_dispatcher), std::move(i2c),
std::move(irq));
auto status = device_->Init();
if (status != ZX_OK) {
FDF_LOG(ERROR, "Init() failed: %s", zx_status_get_string(status));
return zx::error(status);
}
auto result = outgoing()->component().AddUnmanagedProtocol<fuchsia_hardware_powersource::Source>(
source_bindings_.CreateHandler(device_.get(), dispatcher(), fidl::kIgnoreBindingClosure),
kDeviceName);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to add Device 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 Fusb302Device::Stop() { device_.reset(); }
zx::result<> Fusb302Device::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("power");
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, kDeviceName)
.devfs_args(devfs.Build())
.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());
zx::result node_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::Node>();
ZX_ASSERT_MSG(node_endpoints.is_ok(), "Failed to create 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 fusb302
FUCHSIA_DRIVER_EXPORT(fusb302::Fusb302Device);