blob: f324d4791c9f93cc7bf067defe01394c114b88d2 [file] [log] [blame]
// Copyright 2023 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/a1-usb-phy/a1-usb-phy.h"
#include <assert.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/fit/defer.h>
#include <lib/zx/time.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/errors.h>
#include <cstdio>
#include <sstream>
#include <string>
#include <ddktl/fidl.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <soc/aml-common/aml-power-domain.h>
#include "src/devices/usb/drivers/a1-usb-phy/usb-phy-regs.h"
namespace {
constexpr uint8_t kPwrOn = 1;
// MMIO indices.
enum {
MMIO_USB_CONTROL = 0,
MMIO_USB_PHY = 1,
MMIO_RESET = 2,
MMIO_CLK = 3,
};
} // namespace
namespace a1_usb_phy {
// Based on set_usb_pll() in phy-aml-new-usb2-v2.c
void A1UsbPhy::InitPll(fdf::MmioBuffer* mmio) {
PLL_REGISTER_44::Get().FromValue(pll_settings_[1]).WriteTo(mmio);
PLL_REGISTER_40::Get()
.FromValue(0x0)
.set_value(pll_settings_[0])
.set_enable(0x1)
.set_reset(0x1)
.WriteTo(mmio);
zx::nanosleep(zx::deadline_after(zx::usec(10)));
PLL_REGISTER_48::Get().FromValue(pll_settings_[2]).WriteTo(mmio);
zx::nanosleep(zx::deadline_after(zx::usec(200)));
PLL_REGISTER_40::Get()
.FromValue(0x0)
.set_value(pll_settings_[0])
.set_enable(0x1)
.set_reset(0x0)
.WriteTo(mmio);
zx::nanosleep(zx::deadline_after(zx::usec(10)));
// PLL
PLL_REGISTER_48::Get().FromValue(pll_settings_[2]).WriteTo(mmio);
PLL_REGISTER_C::Get()
.FromValue(0x0)
.set_squelch_ref(0x4)
.set_hsdic_ref(0x2)
.set_TBD_4(0x1)
.WriteTo(mmio);
// Tuning
PLL_REGISTER_50::Get().FromValue(pll_settings_[3]).WriteTo(mmio);
PLL_REGISTER_54::Get()
.FromValue(0x0)
.set_usb2_cal_ack_en(0x1)
.set_usb2_tx_strg_pd(0x1)
.set_usb2_otg_aca_trim_1_0(0x2)
.WriteTo(mmio);
uint32_t temp = usbphy_mmio_->Read32(0x8);
uint32_t temp1 = usbphy_mmio_->Read32(0x10);
usbphy_mmio_->Write32((temp & 0xfff) | temp1, 0x10);
PLL_REGISTER_34::Get()
.FromValue(0x0)
.set_Update_PMA_signals(0x1)
.set_minimum_count_for_sync_detection(0x7)
.WriteTo(mmio);
PLL_REGISTER_38::Get()
.FromValue(0x0)
.set_bypass_ctrl_8_i_rpd_en(0x1)
.set_bypass_ctrl_9_i_rpu_sw2_en(0x1)
.WriteTo(mmio);
}
zx_status_t A1UsbPhy::InitPhy() {
auto* usbctrl_mmio = &*usbctrl_mmio_;
auto* reset_mmio = &*reset_mmio_;
auto* usbphy_mmio = &*usbphy_mmio_;
RESET_REGISTER::Get(RESET1_LEVEL_OFFSET).ReadFrom(reset_mmio).set_usbphy(0x1).WriteTo(reset_mmio);
// amlogic_new_usbphy_reset_v2()
RESET_REGISTER::Get(RESET1_REGISTER_OFFSET).FromValue(0x0).set_usbctrl(0x1).WriteTo(reset_mmio);
// FIXME(voydanoff) this delay is very long, but it is what the Amlogic Linux kernel is doing.
zx::nanosleep(zx::deadline_after(zx::usec(10)));
U2P_R0_V2::Get(0).ReadFrom(usbctrl_mmio).set_por(0x1).WriteTo(usbctrl_mmio);
U2P_R0_V2::Get(0).ReadFrom(usbctrl_mmio).set_host_device(0x1).WriteTo(usbctrl_mmio);
zx::nanosleep(zx::deadline_after(zx::usec(10)));
RESET_REGISTER::Get(RESET1_LEVEL_OFFSET).ReadFrom(reset_mmio).set_usbphy(0x0).WriteTo(reset_mmio);
zx::nanosleep(zx::deadline_after(zx::usec(200)));
RESET_REGISTER::Get(RESET1_LEVEL_OFFSET).ReadFrom(reset_mmio).set_usbphy(0x1).WriteTo(reset_mmio);
zx::nanosleep(zx::deadline_after(zx::usec(50)));
PLL_REGISTER_54::Get().ReadFrom(usbphy_mmio).set_usb2_otg_aca_en(0x0).WriteTo(usbphy_mmio);
auto u2p_r1 = U2P_R1_V2::Get(0);
int count = 0;
while (!u2p_r1.ReadFrom(usbctrl_mmio).phy_rdy()) {
// wait phy ready max 5ms, common is 100us
if (count > 1000) {
zxlogf(ERROR, "A1UsbPhy::InitPhy U2P_R1_PHY_RDY wait failed");
break;
}
count++;
zx::nanosleep(zx::deadline_after(zx::usec(5)));
}
if (count > 1000) {
return ZX_ERR_OUT_OF_RANGE;
} else {
return ZX_OK;
}
}
zx_status_t A1UsbPhy::InitOtg() {
auto* mmio = &*usbctrl_mmio_;
USB_R1_V2::Get()
.ReadFrom(mmio)
.set_u3h_fladj_30mhz_reg(0x20)
.set_u3h_host_u2_port_disable(0x2)
.WriteTo(mmio);
USB_R5_V2::Get().ReadFrom(mmio).set_iddig_en0(0x1).set_iddig_en1(0x1).set_iddig_th(0xff).WriteTo(
mmio);
return ZX_OK;
}
void A1UsbPhy::SetMode(UsbMode mode, SetModeCompletion completion) {
ZX_DEBUG_ASSERT(mode == UsbMode::HOST || mode == UsbMode::PERIPHERAL);
// Only the irq thread calls |SetMode|, and it should have waited for the
// previous call to |SetMode| to complete.
auto cleanup = fit::defer([&]() {
if (completion)
completion();
});
if (mode == phy_mode_)
return;
auto* usbctrl_mmio = &*usbctrl_mmio_;
auto r0 = USB_R0_V2::Get().ReadFrom(usbctrl_mmio);
if (mode == UsbMode::HOST) {
r0.set_u2d_act(0x0);
} else {
r0.set_u2d_act(0x1);
r0.set_u2d_ss_scaledown_mode(0x0);
}
r0.WriteTo(usbctrl_mmio);
USB_R4_V2::Get()
.ReadFrom(usbctrl_mmio)
.set_p21_sleepm0(mode == UsbMode::PERIPHERAL)
.WriteTo(usbctrl_mmio);
U2P_R0_V2::Get(0)
.ReadFrom(usbctrl_mmio)
.set_host_device(mode == UsbMode::HOST)
.set_por(0x0)
.WriteTo(usbctrl_mmio);
zx::nanosleep(zx::deadline_after(zx::usec(500)));
auto old_mode = phy_mode_;
phy_mode_ = mode;
if (old_mode == UsbMode::UNKNOWN) {
// One time PLL initialization
InitPll(&*usbphy_mmio_);
} else {
auto* phy_mmio = &*usbphy_mmio_;
PLL_REGISTER_38::Get()
.FromValue(mode == UsbMode::HOST ? pll_settings_[6] : 0)
.WriteTo(phy_mmio);
PLL_REGISTER_34::Get().FromValue(pll_settings_[5]).WriteTo(phy_mmio);
}
if (mode == UsbMode::HOST) {
auto status = AddXhciDevice();
if (status != ZX_OK) {
zxlogf(ERROR, "AddXhciDevice() error %s", zx_status_get_string(status));
}
}
}
zx_status_t A1UsbPhy::Create(void* ctx, zx_device_t* parent) {
auto dev = std::make_unique<A1UsbPhy>(parent);
auto status = dev->Init();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
[[maybe_unused]] auto* _ = dev.release();
return ZX_OK;
}
zx_status_t A1UsbPhy::UsbPowerOn() {
static const zx_smc_parameters_t kSetPdCall = aml_pd_smc::CreatePdSmcCall(A1_PDID_USB, kPwrOn);
if (!smc_short_circuit_) {
ZX_ASSERT(smc_monitor_.is_valid());
zx_smc_result_t result;
auto status = zx_smc_call(smc_monitor_.get(), &kSetPdCall, &result);
if (status != ZX_OK) {
zxlogf(ERROR, "Call zx_smc_call failed: %s", zx_status_get_string(status));
return status;
}
}
return ZX_OK;
}
void A1UsbPhy::InitUsbClk() {
auto* clk_mmio = &*clk_mmio_;
// clock source select: 0:cts_oscin_clk; 1:cts_sys_clk; 2:fclk_div3; 3:fclk_div5.
USB_BUSCLK_CTRL::Get()
.ReadFrom(clk_mmio)
.set_clock_div(0x0)
.set_clock_gate_en(0x1)
.set_clock_source_select(0x1)
.WriteTo(clk_mmio);
}
zx_status_t A1UsbPhy::AddXhciDevice() {
if (xhci_device_) {
zxlogf(ERROR, "Add xhci device repeatedly!");
return ZX_ERR_BAD_STATE;
}
static zx_protocol_device_t ops = {
.version = DEVICE_OPS_VERSION,
// Defer get_protocol() to parent.
.get_protocol =
[](void* ctx, uint32_t id, void* proto) {
return device_get_protocol(reinterpret_cast<A1UsbPhy*>(ctx)->zxdev(), id, proto);
},
.release = [](void*) {},
};
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GENERIC},
{BIND_PLATFORM_DEV_PID, 0, PDEV_PID_GENERIC},
{BIND_PLATFORM_DEV_DID, 0, PDEV_DID_USB_XHCI_COMPOSITE},
};
// clang-format off
device_add_args_t args = (ddk::DeviceAddArgs("xhci")
.set_context(this)
.set_props(props)
.set_proto_id(ZX_PROTOCOL_USB_PHY)
.set_ops(&ops)).get();
// clang-format on
auto status = device_add(zxdev(), &args, &xhci_device_);
if (status != ZX_OK) {
zxlogf(ERROR, "device_add() error %s", zx_status_get_string(status));
}
return status;
}
void A1UsbPhy::RemoveXhciDevice() {
if (xhci_device_) {
device_async_remove(xhci_device_);
xhci_device_ = nullptr;
}
}
zx_status_t A1UsbPhy::Init() {
ddk::PDevFidl pdev(parent());
if (!pdev.is_valid()) {
zxlogf(ERROR, "A1UsbPhy::Init: could not get platform device protocol");
return ZX_ERR_NOT_SUPPORTED;
}
size_t actual;
zx_status_t status =
DdkGetMetadata(DEVICE_METADATA_PRIVATE, pll_settings_, sizeof(pll_settings_), &actual);
if (status != ZX_OK || actual != sizeof(pll_settings_)) {
zxlogf(ERROR, "A1UsbPhy::Init could not get metadata for PLL settings");
return ZX_ERR_INTERNAL;
}
status = DdkGetMetadata(DEVICE_METADATA_USB_MODE, &dr_mode_, sizeof(dr_mode_), &actual);
if (status == ZX_OK && actual != sizeof(dr_mode_)) {
zxlogf(ERROR, "A1UsbPhy::Init could not get metadata for USB Mode");
return ZX_ERR_INTERNAL;
} else if (status != ZX_OK) {
dr_mode_ = USB_MODE_OTG;
}
// TODO(123426) Remove. See issue details.
TestMetadata test_meta;
status = DdkGetMetadata(DEVICE_METADATA_TEST, &test_meta, sizeof(test_meta), &actual);
if (status == ZX_OK && actual == sizeof(test_meta)) {
// Only supplied by tests, not otherwise present.
smc_short_circuit_ = test_meta.smc_short_circuit;
}
status = pdev.MapMmio(MMIO_USB_CONTROL, &usbctrl_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "pdev.MapMmio usbctrl failed %s", zx_status_get_string(status));
return status;
}
status = pdev.MapMmio(MMIO_USB_PHY, &usbphy_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "pdev.MapMmio usbphy failed %s", zx_status_get_string(status));
return status;
}
status = pdev.MapMmio(MMIO_RESET, &reset_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "pdev.MapMmio reset failed %s", zx_status_get_string(status));
return status;
}
status = pdev.MapMmio(MMIO_CLK, &clk_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "pdev.MapMmio clk failed %s", zx_status_get_string(status));
return status;
}
if (!smc_short_circuit_) {
// TODO(123426) Can't (yet) use fake smc resources. See issue details.
status = pdev.GetSmc(0, &smc_monitor_);
if (status != ZX_OK) {
zxlogf(ERROR, "unable to sip monitor handle %s", zx_status_get_string(status));
return status;
}
}
// USB module power supply.
status = UsbPowerOn();
if (status != ZX_OK) {
zxlogf(ERROR, "USB Power Domain Control failed %s", zx_status_get_string(status));
return status;
}
// USB bus clock configuration.
InitUsbClk();
status = InitPhy();
if (status != ZX_OK) {
zxlogf(ERROR, "Usb phy initialization failed %s", zx_status_get_string(status));
return status;
}
status = InitOtg();
if (status != ZX_OK) {
return status;
}
return DdkAdd("a1-usb-phy", DEVICE_ADD_NON_BINDABLE);
}
void A1UsbPhy::DdkInit(ddk::InitTxn txn) {
if (dr_mode_ != USB_MODE_OTG) {
sync_completion_t set_mode_sync;
auto completion = [&](void) { sync_completion_signal(&set_mode_sync); };
fbl::AutoLock lock(&lock_);
if (dr_mode_ == USB_MODE_PERIPHERAL) {
zxlogf(INFO, "Entering USB Peripheral Mode");
SetMode(UsbMode::PERIPHERAL, std::move(completion));
} else {
zxlogf(INFO, "Entering USB Host Mode");
SetMode(UsbMode::HOST, std::move(completion));
}
sync_completion_wait(&set_mode_sync, ZX_TIME_INFINITE);
return txn.Reply(ZX_OK);
}
// If dr_mode_ is USB_MODE_OTG, the controller does not support OTG negotiation, and an error is
// reported.
zxlogf(ERROR, "controller does not support OTG mode");
return txn.Reply(ZX_ERR_NOT_SUPPORTED);
}
// PHY tuning based on connection state
void A1UsbPhy::UsbPhyConnectStatusChanged(bool connected) {
fbl::AutoLock lock(&lock_);
if (dwc2_connected_ == connected)
return;
auto* mmio = &*usbphy_mmio_;
if (connected) {
PLL_REGISTER_38::Get().FromValue(pll_settings_[7]).WriteTo(mmio);
PLL_REGISTER_34::Get().FromValue(pll_settings_[5]).WriteTo(mmio);
} else {
InitPll(mmio);
}
dwc2_connected_ = connected;
}
void A1UsbPhy::DdkUnbind(ddk::UnbindTxn txn) {
fbl::AutoLock lock(&lock_);
RemoveXhciDevice();
txn.Reply();
}
void A1UsbPhy::DdkRelease() { delete this; }
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = A1UsbPhy::Create;
return ops;
}();
} // namespace a1_usb_phy
ZIRCON_DRIVER(a1_usb_phy, a1_usb_phy::driver_ops, "zircon", "0.1");