blob: 967ad657d2dd425338707119254ef626f4bf63ce [file] [log] [blame]
// Copyright 2022 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.h"
#include <fidl/fuchsia.driver.compat/cpp/wire.h>
#include <soc/aml-common/aml-registers.h>
#include "src/devices/usb/drivers/aml-usb-phy/usb-phy-regs.h"
#include "src/devices/usb/drivers/aml-usb-phy/usb-phy2-regs.h"
namespace aml_usb_phy {
namespace {
void dump_usb_regs(const fdf::MmioBuffer& mmio) {
DUMP_REG(USB_R0_V2, mmio)
DUMP_REG(USB_R1_V2, mmio)
DUMP_REG(USB_R2_V2, mmio)
DUMP_REG(USB_R3_V2, mmio)
DUMP_REG(USB_R4_V2, mmio)
DUMP_REG(USB_R5_V2, mmio)
DUMP_REG(USB_R6_V2, mmio)
}
} // namespace
void AmlUsbPhy::dump_regs() {
dump_usb_regs(usbctrl_mmio_);
for (const auto& u2 : usbphy2_) {
u2.dump_regs();
}
for (const auto& u3 : usbphy3_) {
u3.dump_regs();
}
}
zx_status_t AmlUsbPhy::InitPhy2() {
auto* usbctrl_mmio = &usbctrl_mmio_;
// first reset USB
auto portnum = usbphy2_.size();
uint32_t reset_level = 0;
while (portnum) {
portnum--;
reset_level = reset_level | (1 << (16 + portnum));
}
auto level_result =
reset_register_->WriteRegister32(RESET1_LEVEL_OFFSET, reset_level, reset_level);
if ((level_result.status() != ZX_OK) || level_result->is_error()) {
FDF_LOG(ERROR, "Reset Level Write failed\n");
return ZX_ERR_INTERNAL;
}
// amlogic_new_usbphy_reset_v2()
auto register_result1 = reset_register_->WriteRegister32(
RESET1_REGISTER_OFFSET, aml_registers::USB_RESET1_REGISTER_UNKNOWN_1_MASK,
aml_registers::USB_RESET1_REGISTER_UNKNOWN_1_MASK);
if ((register_result1.status() != ZX_OK) || register_result1->is_error()) {
FDF_LOG(ERROR, "Reset Register Write on 1 << 2 failed\n");
return ZX_ERR_INTERNAL;
}
zx::nanosleep(zx::deadline_after(zx::usec(500)));
// amlogic_new_usb2_init()
for (auto& phy : usbphy2_) {
auto u2p_ro_v2 = U2P_R0_V2::Get(phy.idx()).ReadFrom(usbctrl_mmio).set_por(1);
if (phy.is_otg_capable()) {
u2p_ro_v2.set_idpullup0(1)
.set_drvvbus0(1)
.set_host_device(phy.dr_mode() == UsbMode::Peripheral ? 0 : 1)
.WriteTo(usbctrl_mmio);
} else {
u2p_ro_v2.set_host_device(phy.dr_mode() == UsbMode::Peripheral).WriteTo(usbctrl_mmio);
}
u2p_ro_v2.set_por(0).WriteTo(usbctrl_mmio);
}
zx::nanosleep(zx::deadline_after(zx::usec(10)));
// amlogic_new_usbphy_reset_phycfg_v2()
auto register_result2 =
reset_register_->WriteRegister32(RESET1_LEVEL_OFFSET, reset_level, ~reset_level);
if ((register_result2.status() != ZX_OK) || register_result2->is_error()) {
FDF_LOG(ERROR, "Reset Register Write on 1 << 16 failed\n");
return ZX_ERR_INTERNAL;
}
zx::nanosleep(zx::deadline_after(zx::usec(100)));
auto register_result3 =
reset_register_->WriteRegister32(RESET1_LEVEL_OFFSET, aml_registers::USB_RESET1_LEVEL_MASK,
aml_registers::USB_RESET1_LEVEL_MASK);
if ((register_result3.status() != ZX_OK) || register_result3->is_error()) {
FDF_LOG(ERROR, "Reset Register Write on 1 << 16 failed\n");
return ZX_ERR_INTERNAL;
}
zx::nanosleep(zx::deadline_after(zx::usec(50)));
for (auto& phy : usbphy2_) {
auto mmio = &phy.mmio();
PHY2_R21::Get().ReadFrom(mmio).set_usb2_otg_aca_en(0).WriteTo(mmio);
auto u2p_r1 = U2P_R1_V2::Get(phy.idx());
int count = 0;
while (!u2p_r1.ReadFrom(usbctrl_mmio).phy_rdy()) {
// wait phy ready max 5ms, common is 100us
if (count > 1000) {
FDF_LOG(WARNING, "AmlUsbPhy::InitPhy U2P_R1_PHY_RDY wait failed");
break;
}
count++;
zx::nanosleep(zx::deadline_after(zx::usec(5)));
}
}
// One time PLL initialization
for (auto& phy : usbphy2_) {
phy.InitPll(type_, needs_hack_);
}
return ZX_OK;
}
zx_status_t AmlUsbPhy::InitOtg() {
auto* mmio = &usbctrl_mmio_;
USB_R1_V2::Get().ReadFrom(mmio).set_u3h_fladj_30mhz_reg(0x20).WriteTo(mmio);
USB_R5_V2::Get().ReadFrom(mmio).set_iddig_en0(1).set_iddig_en1(1).set_iddig_th(255).WriteTo(mmio);
return ZX_OK;
}
zx_status_t AmlUsbPhy::InitPhy3() {
for (auto& usbphy3 : usbphy3_) {
auto status = usbphy3.Init(usbctrl_mmio_);
if (status != ZX_OK) {
FDF_LOG(ERROR, "usbphy3.Init() error %s", zx_status_get_string(status));
return status;
}
}
return ZX_OK;
}
void AmlUsbPhy::ChangeMode(UsbPhyBase& phy, UsbMode new_mode) {
auto old_mode = phy.phy_mode();
if (new_mode == old_mode) {
FDF_LOG(ERROR, "Already in %d mode", static_cast<uint8_t>(new_mode));
return;
}
phy.SetMode(new_mode, usbctrl_mmio_);
if (new_mode == UsbMode::Host) {
++controller_->xhci_;
if (old_mode != UsbMode::Unknown) {
--controller_->dwc2_;
}
} else {
++controller_->dwc2_;
if (old_mode != UsbMode::Unknown) {
--controller_->xhci_;
}
}
}
void AmlUsbPhy::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt) {
if (status == ZX_ERR_CANCELED) {
return;
}
if (status != ZX_OK) {
FDF_LOG(ERROR, "irq_.wait failed: %d", status);
return;
}
{
auto r5 = USB_R5_V2::Get().ReadFrom(&usbctrl_mmio_);
// Acknowledge interrupt
r5.set_usb_iddig_irq(0).WriteTo(&usbctrl_mmio_);
// Read current host/device role.
for (auto& phy : usbphy2_) {
if (phy.dr_mode() != UsbMode::Otg) {
continue;
}
ChangeMode(phy, r5.iddig_curr() == 0 ? UsbMode::Host : UsbMode::Peripheral);
}
}
irq_.ack();
}
zx_status_t AmlUsbPhy::Init() {
bool has_otg = false;
auto status = InitPhy2();
if (status != ZX_OK) {
FDF_LOG(ERROR, "InitPhy2() error %s", zx_status_get_string(status));
return status;
}
status = InitOtg();
if (status != ZX_OK) {
FDF_LOG(ERROR, "InitOtg() error %s", zx_status_get_string(status));
return status;
}
status = InitPhy3();
if (status != ZX_OK) {
FDF_LOG(ERROR, "InitPhy3() error %s", zx_status_get_string(status));
return status;
}
for (auto& phy : usbphy2_) {
UsbMode mode;
if (phy.dr_mode() != UsbMode::Otg) {
mode = phy.dr_mode() == UsbMode::Host ? UsbMode::Host : UsbMode::Peripheral;
} else {
has_otg = true;
// Wait for PHY to stabilize before reading initial mode.
zx::nanosleep(zx::deadline_after(zx::sec(1)));
mode = USB_R5_V2::Get().ReadFrom(&usbctrl_mmio_).iddig_curr() == 0 ? UsbMode::Host
: UsbMode::Peripheral;
}
ChangeMode(phy, mode);
}
for (auto& phy : usbphy3_) {
if (phy.dr_mode() != UsbMode::Host) {
FDF_LOG(ERROR, "Not support USB3 in non-host mode yet");
}
ChangeMode(phy, UsbMode::Host);
}
if (has_otg) {
irq_handler_.set_object(irq_.get());
auto status = irq_handler_.Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher());
if (status != ZX_OK) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
return ZX_OK;
}
// PHY tuning based on connection state
void AmlUsbPhy::ConnectStatusChanged(ConnectStatusChangedRequest& request,
ConnectStatusChangedCompleter::Sync& completer) {
if (dwc2_connected_ == request.connected()) {
completer.Reply(fit::ok());
return;
}
for (auto& phy : usbphy2_) {
if (phy.phy_mode() != UsbMode::Peripheral) {
continue;
}
auto* mmio = &phy.mmio();
if (request.connected()) {
PHY2_R14::Get().FromValue(0).set_i_rpu_sw2_en(3).set_fs(1).set_ls(1).set_hs_out_en(1).WriteTo(
mmio);
PHY2_R13::Get()
.FromValue(0)
.set_Update_PMA_signals(1)
.set_minimum_count_for_sync_detection(7)
.WriteTo(mmio);
} else {
phy.InitPll(type_, needs_hack_);
}
}
dwc2_connected_ = request.connected();
completer.Reply(fit::ok());
}
} // namespace aml_usb_phy