blob: e5b6f40d58f2e0e28d84278cf43c9a2cc3d67727 [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 "src/devices/usb/drivers/aml-usb-phy/aml-usb-phy-device.h"
#include <fidl/fuchsia.hardware.registers/cpp/wire.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <lib/driver/platform-device/cpp/pdev.h>
#include <mutex>
#include <bind/fuchsia/amlogic/platform/cpp/bind.h>
#include <bind/fuchsia/cpp/bind.h>
#include <bind/fuchsia/platform/cpp/bind.h>
#include "src/devices/usb/drivers/aml-usb-phy/aml-usb-phy.h"
#include "src/devices/usb/drivers/aml-usb-phy/power-regs.h"
#include "src/devices/usb/drivers/aml-usb-phy/usb-phy-regs.h"
namespace aml_usb_phy {
namespace {
[[maybe_unused]] void dump_power_regs(const fdf::MmioBuffer& mmio) {
DUMP_REG(A0_RTI_GEN_PWR_SLEEP0, mmio)
DUMP_REG(A0_RTI_GEN_PWR_ISO0, mmio)
}
[[maybe_unused]] void dump_hhi_mem_pd_regs(const fdf::MmioBuffer& mmio){
DUMP_REG(HHI_MEM_PD_REG0, mmio)}
zx_status_t
PowerOn(fidl::ClientEnd<fuchsia_hardware_registers::Device>& reset_register,
fdf::MmioBuffer& power_mmio, fdf::MmioBuffer& sleep_mmio, bool dump_regs = false) {
A0_RTI_GEN_PWR_SLEEP0::Get().ReadFrom(&sleep_mmio).set_usb_comb_power_off(0).WriteTo(&sleep_mmio);
HHI_MEM_PD_REG0::Get().ReadFrom(&power_mmio).set_usb_comb_pd(0).WriteTo(&power_mmio);
zx::nanosleep(zx::deadline_after(zx::usec(100)));
fidl::Arena<> arena;
fidl::WireUnownedResult register_result1 =
fidl::WireCall(reset_register).buffer(arena)->WriteRegister32(RESET1_LEVEL_OFFSET, 0x4, 0);
if (!register_result1.ok() || register_result1->is_error()) {
fdf::error("Reset Register Write on 1 << 2 failed: {}",
register_result1.FormatDescription().c_str());
return ZX_ERR_INTERNAL;
}
zx::nanosleep(zx::deadline_after(zx::usec(100)));
A0_RTI_GEN_PWR_ISO0::Get()
.ReadFrom(&sleep_mmio)
.set_usb_comb_isolation_enable(0)
.WriteTo(&sleep_mmio);
fidl::WireUnownedResult register_result2 =
fidl::WireCall(reset_register).buffer(arena)->WriteRegister32(RESET1_LEVEL_OFFSET, 0x4, 0x4);
if (!register_result2.ok() || register_result2->is_error()) {
fdf::error("Reset Register Write on 1 << 2 failedd: {}",
register_result2.FormatDescription().c_str());
return ZX_ERR_INTERNAL;
}
zx::nanosleep(zx::deadline_after(zx::usec(100)));
A0_RTI_GEN_PWR_SLEEP0::Get().ReadFrom(&sleep_mmio).set_pci_comb_power_off(0).WriteTo(&sleep_mmio);
fidl::WireUnownedResult register_result3 =
fidl::WireCall(reset_register)
.buffer(arena)
->WriteRegister32(RESET1_LEVEL_OFFSET, 0xF << 26, 0);
if (!register_result3.ok() || register_result3->is_error()) {
fdf::error("Reset Register Write on 1 << 2 failed: {}",
register_result3.FormatDescription().c_str());
return ZX_ERR_INTERNAL;
}
A0_RTI_GEN_PWR_ISO0::Get()
.ReadFrom(&sleep_mmio)
.set_pci_comb_isolation_enable(0)
.WriteTo(&sleep_mmio);
A0_RTI_GEN_PWR_SLEEP0::Get().ReadFrom(&sleep_mmio).set_ge2d_power_off(0).WriteTo(&sleep_mmio);
HHI_MEM_PD_REG0::Get().ReadFrom(&power_mmio).set_ge2d_pd(0).WriteTo(&power_mmio);
A0_RTI_GEN_PWR_ISO0::Get()
.ReadFrom(&sleep_mmio)
.set_ge2d_isolation_enable(0)
.WriteTo(&sleep_mmio);
A0_RTI_GEN_PWR_ISO0::Get()
.ReadFrom(&sleep_mmio)
.set_ge2d_isolation_enable(1)
.WriteTo(&sleep_mmio);
HHI_MEM_PD_REG0::Get().ReadFrom(&power_mmio).set_ge2d_pd(0xFF).WriteTo(&power_mmio);
A0_RTI_GEN_PWR_SLEEP0::Get().ReadFrom(&sleep_mmio).set_ge2d_power_off(1).WriteTo(&sleep_mmio);
if (dump_regs) {
dump_power_regs(sleep_mmio);
dump_hhi_mem_pd_regs(power_mmio);
}
return ZX_OK;
}
} // namespace
zx::result<> AmlUsbPhyDevice::Start() {
// Get Reset Register.
fidl::ClientEnd<fuchsia_hardware_registers::Device> reset_register;
{
zx::result result =
incoming()->Connect<fuchsia_hardware_registers::Service::Device>("register-reset");
if (result.is_error()) {
fdf::error("Failed to open i2c service: {}", result);
return result.take_error();
}
reset_register = std::move(result.value());
}
zx::result pdev_client_end =
incoming()->Connect<fuchsia_hardware_platform_device::Service::Device>("pdev");
if (pdev_client_end.is_error()) {
fdf::error("Failed to connect to platform device: {}", pdev_client_end);
return pdev_client_end.take_error();
}
fdf::PDev pdev{std::move(pdev_client_end.value())};
zx::result usb_phy_metadata = pdev.GetFidlMetadata<fuchsia_hardware_usb_phy::Metadata>();
if (usb_phy_metadata.is_error()) {
fdf::error("Failed to get metadata: {}", usb_phy_metadata);
return usb_phy_metadata.take_error();
}
// Get mmio.
std::vector<UsbPhy2> usbphy2;
std::vector<UsbPhy3> usbphy3;
bool needs_hack = false;
zx::result dev_info = pdev.GetDeviceInfo();
if (dev_info.is_ok() &&
(dev_info->pid == bind_fuchsia_amlogic_platform::BIND_PLATFORM_DEV_PID_S905D2 ||
dev_info->pid == bind_fuchsia_amlogic_platform::BIND_PLATFORM_DEV_PID_S905D3)) {
fdf::error("Using hack");
needs_hack = true;
}
zx::result usbctrl_mmio = MapMmio(pdev, 0);
if (usbctrl_mmio.is_error()) {
fdf::error("Failed to map mmio: {}", usbctrl_mmio);
return usbctrl_mmio.take_error();
}
uint32_t idx = 1;
const auto& usb_phy_modes = usb_phy_metadata.value().usb_phy_modes();
if (!usb_phy_modes.has_value()) {
fdf::error("Metadata missing usb_phy_modes field");
return zx::error(ZX_ERR_INTERNAL);
}
for (size_t i = 0; i < usb_phy_modes.value().size(); ++i) {
zx::result mmio = MapMmio(pdev, idx);
if (mmio.is_error()) {
return mmio.take_error();
}
const auto& phy_mode = usb_phy_modes.value()[i];
const auto& protocol = phy_mode.protocol();
if (!protocol.has_value()) {
fdf::error("Phy-mode {} missing protocol field", i);
return zx::error(ZX_ERR_INTERNAL);
}
const auto& is_otg_capable = phy_mode.is_otg_capable();
if (!is_otg_capable.has_value()) {
fdf::error("Phy-mode {} missing is_otg_capable field", i);
return zx::error(ZX_ERR_INTERNAL);
}
const auto& dr_mode = phy_mode.dr_mode();
if (!is_otg_capable.has_value()) {
fdf::error("Phy-mode {} missing dr_mode field", i);
return zx::error(ZX_ERR_INTERNAL);
}
switch (protocol.value()) {
case fuchsia_hardware_usb_phy::ProtocolVersion::kUsb20: {
usbphy2.emplace_back(usbphy2.size(), std::move(*mmio), is_otg_capable.value(),
dr_mode.value());
} break;
case fuchsia_hardware_usb_phy::ProtocolVersion::kUsb30: {
usbphy3.emplace_back(std::move(*mmio), is_otg_capable.value(), dr_mode.value());
} break;
default:
fdf::error("Unsupported protocol type {}", static_cast<uint32_t>(protocol.value()));
break;
}
idx++;
}
zx::result interrupt = pdev.GetInterrupt(0);
if (interrupt.is_error()) {
fdf::error("Failed to get interrupt: {}", interrupt);
return interrupt.take_error();
}
// Optional MMIOs
{
auto power_mmio = MapMmio(pdev, idx++);
auto sleep_mmio = MapMmio(pdev, idx++);
if (power_mmio.is_ok() && sleep_mmio.is_ok()) {
fdf::info("Found power and sleep MMIO.");
auto status = PowerOn(reset_register, *power_mmio, *sleep_mmio);
if (status != ZX_OK) {
fdf::error("Failed to power on: {}", zx_status_get_string(status));
return zx::error(status);
}
}
}
// Create and initialize device
const auto& phy_type = usb_phy_metadata.value().phy_type();
if (!phy_type.has_value()) {
fdf::error("Metadata missing phy_type field");
return zx::error(ZX_ERR_INTERNAL);
}
device_ = std::make_unique<AmlUsbPhy>(this, phy_type.value(), std::move(reset_register),
std::move(*usbctrl_mmio), std::move(interrupt.value()),
std::move(usbphy2), std::move(usbphy3), needs_hack);
{
auto result = CreateNode();
if (result.is_error()) {
fdf::error("Failed to create node: {}", result);
return zx::error(result.status_value());
}
}
// Initialize device. Must come after CreateNode() because Init() will create xHCI and DWC2
// nodes on top of node_.
auto status = device_->Init();
if (status != ZX_OK) {
fdf::error("Init() error {}", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok();
}
zx::result<> AmlUsbPhyDevice::CreateNode() {
// Add node for aml-usb-phy.
fidl::Arena arena;
auto args =
fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena).name(arena, kDeviceName).Build();
auto controller_endpoints = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
zx::result node_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::Node>();
if (node_endpoints.is_error()) {
fdf::error("Failed to create node endpoints: {}", node_endpoints);
return node_endpoints.take_error();
}
{
fidl::WireResult result = fidl::WireCall(node())->AddChild(
args, std::move(controller_endpoints.server), std::move(node_endpoints->server));
if (!result.ok()) {
fdf::error("Failed to add child {}", result.FormatDescription().c_str());
return zx::error(result.status());
}
}
controller_.Bind(std::move(controller_endpoints.client));
node_.Bind(std::move(node_endpoints->client));
return zx::ok();
}
zx::result<> AmlUsbPhyDevice::ChildNode::Publish() {
std::lock_guard<std::mutex> _(lock_);
count_++;
if (count_ != 1) {
return zx::ok();
}
// Serve fuchsia_hardware_usb_phy.
{
auto result = parent_->outgoing()->AddService<fuchsia_hardware_usb_phy::Service>(
fuchsia_hardware_usb_phy::Service::InstanceHandler({
.device = parent_->bindings_.CreateHandler(
parent_->device_.get(), fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure),
}),
name_);
if (result.is_error()) {
fdf::error("Failed to add Device service: {} for device {}", result, name_);
return result.take_error();
}
}
fuchsia_driver_framework::Offer offers[]{
fdf::MakeOffer2<fuchsia_hardware_usb_phy::Service>(name_)};
std::vector properties = {
fdf::MakeProperty(bind_fuchsia::PLATFORM_DEV_VID,
bind_fuchsia_platform::BIND_PLATFORM_DEV_VID_GENERIC),
fdf::MakeProperty(bind_fuchsia::PLATFORM_DEV_PID,
bind_fuchsia_platform::BIND_PLATFORM_DEV_PID_GENERIC),
fdf::MakeProperty(bind_fuchsia::PLATFORM_DEV_DID, property_did_),
};
zx::result child = fdf::AddChild(parent_->node_.client_end(), *fdf::Logger::GlobalInstance(),
name_, properties, offers);
if (child.is_error()) {
fdf::error("Failed to add child: {} for device {}", child, name_);
return child.take_error();
}
child_controller_.Bind(std::move(child.value()));
return zx::ok();
}
zx::result<> AmlUsbPhyDevice::ChildNode::UnPublish() {
std::lock_guard<std::mutex> _(lock_);
if (count_ == 0) {
// Nothing to remove.
return zx::ok();
}
count_--;
if (count_ != 0) {
// Has more instances.
return zx::ok();
}
zx::result<> ret = zx::ok();
// Reset.
if (child_controller_) {
auto result = child_controller_->Remove();
if (!result.ok()) {
fdf::error("Failed to remove {}. {}", name_.data(), result.FormatDescription().c_str());
ret = zx::error(result.status());
}
child_controller_.TakeClientEnd().reset();
}
{
zx::result result =
parent_->outgoing()->RemoveService<fuchsia_hardware_usb_phy::Service>(name_);
if (result.is_error()) {
fdf::error("Failed to remove device service for {}:{}", name_.data(), result);
ret = result;
}
}
return ret;
}
void AmlUsbPhyDevice::Stop() {
auto status = controller_->Remove();
if (!status.ok()) {
fdf::error("Could not remove child: {}", status.status_string());
}
}
zx::result<fdf::MmioBuffer> AmlUsbPhyDevice::MapMmio(fdf::PDev& pdev, uint32_t idx) {
return pdev.MapMmio(idx);
}
void AmlUsbPhyDevice::UnbindOnFailure() { node_.TakeClientEnd().TakeChannel().reset(); }
} // namespace aml_usb_phy
FUCHSIA_DRIVER_EXPORT(aml_usb_phy::AmlUsbPhyDevice);