use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
use crate::usb::error::{UsbError, UsbResult};
use crate::usb::host::dwc2::mmio;
use crate::usb::host::dwc2::regs::{
Dwc2Regs, DCFG, DCTL, DEPMSK, DIEPCTL, DOEPCTL, GAHBCFG, GHWCFG2, GHWCFG3, GHWCFG4, GINTSTS,
GRSTCTL, GUSBCFG,
};
use crate::usb::platform;
#[inline]
fn base() -> usize {
platform::dwc2_base_virt()
}
#[inline]
fn regs() -> &'static Dwc2Regs {
mmio::dwc2_regs().expect("DWC2 base not set (call platform::set_dwc2_base_virt)")
}
#[inline]
fn spin_delay(n: u32) {
for _ in 0..n {
core::hint::spin_loop();
}
}
#[derive(Clone, Copy, Debug)]
pub enum DeviceSpeedHint {
HighSpeed,
FullSpeed,
}
static mut SPEED_HINT: DeviceSpeedHint = DeviceSpeedHint::HighSpeed;
pub fn dwc2_device_set_speed_hint(hint: DeviceSpeedHint) {
unsafe {
SPEED_HINT = hint;
}
}
#[inline]
fn current_speed_hint() -> DeviceSpeedHint {
unsafe { SPEED_HINT }
}
const DWC2_CORE_REV_4_20A: u32 = 0x4f54_420a;
const DWC2_CORE_REV_MASK: u32 = 0xffff;
fn wait_ahb_idle() -> UsbResult<()> {
let r = regs();
for _ in 0..3_000_000u32 {
if r.grstctl.is_set(GRSTCTL::AHBIDLE) {
return Ok(());
}
spin_delay(32);
}
Err(UsbError::Timeout)
}
fn core_soft_reset() -> UsbResult<()> {
wait_ahb_idle()?;
let r = regs();
let snpsid = r.gsnpsid.get();
let core_rev = snpsid & DWC2_CORE_REV_MASK;
let new_rst_seq = core_rev >= (DWC2_CORE_REV_4_20A & DWC2_CORE_REV_MASK);
r.grstctl.modify(GRSTCTL::CSFTRST::SET);
if !new_rst_seq {
for _ in 0..3_000_000u32 {
if !r.grstctl.is_set(GRSTCTL::CSFTRST) {
spin_delay(4096);
return Ok(());
}
spin_delay(32);
}
return Err(UsbError::Timeout);
}
for _ in 0..3_000_000u32 {
if r.grstctl.is_set(GRSTCTL::CSFTRST_DONE) {
r.grstctl
.modify(GRSTCTL::CSFTRST::CLEAR + GRSTCTL::CSFTRST_DONE::SET);
spin_delay(4096);
return Ok(());
}
spin_delay(32);
}
Err(UsbError::Timeout)
}
fn force_device_mode() -> UsbResult<()> {
let r = regs();
r.gusbcfg
.modify(GUSBCFG::FORCEHOSTMODE::CLEAR + GUSBCFG::FORCEDEVMODE::SET);
spin_delay(100_000);
for _ in 0..500_000u32 {
if !r.gintsts.is_set(GINTSTS::CURMODE_HOST) {
return Ok(());
}
spin_delay(32);
}
Err(UsbError::Hardware("CURMODE stays in Host after FORCEDEVMODE"))
}
fn init_gusbcfg_cv182x() {
let r = regs();
let utmi_w = r.ghwcfg4.read(GHWCFG4::UTMI_PHY_DATA_WIDTH);
let want_16bit = utmi_w == 1;
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV GHWCFG4.UTMI_PHY_DATA_WIDTH={utmi_w} => PHYIF16={}",
if want_16bit { 1 } else { 0 }
));
let mut field = GUSBCFG::FORCEDEVMODE::SET
+ GUSBCFG::FORCEHOSTMODE::CLEAR
+ GUSBCFG::ULPI_UTMI_SEL::CLEAR
+ GUSBCFG::TOUTCAL.val(0x7);
if want_16bit {
field += GUSBCFG::PHYIF16::SET;
} else {
field += GUSBCFG::PHYIF16::CLEAR;
}
r.gusbcfg.modify(field);
}
fn init_gahb_dma() {
let r = regs();
let arch = r.ghwcfg2.read(GHWCFG2::ARCH);
r.gahbcfg
.modify(GAHBCFG::HBSTLEN::Incr16 + GAHBCFG::GLBL_INTR_EN::SET);
if arch == 2 {
r.gahbcfg.modify(GAHBCFG::DMA_EN::SET);
}
}
fn init_device_fifos() {
let r = regs();
let total = r.ghwcfg3.read(GHWCFG3::DFIFO_DEPTH);
let rx: u32 = 256;
let nptx: u32 = 64;
let mut ep1tx: u32 = 256;
let ep2tx: u32 = 32;
let used = rx + nptx + ep1tx + ep2tx;
if used > total {
let leftover = total.saturating_sub(rx + nptx + ep2tx);
ep1tx = leftover;
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV FIFO total={} too small, shrink ep1tx={}",
total, ep1tx
));
}
r.grxfsiz.set(rx & 0xffff);
r.gnptxfsiz.set(((nptx & 0xffff) << 16) | (rx & 0xffff));
let dieptxf1 = ((ep1tx & 0xffff) << 16) | ((rx + nptx) & 0xffff);
let dieptxf2 = ((ep2tx & 0xffff) << 16) | ((rx + nptx + ep1tx) & 0xffff);
unsafe {
core::ptr::write_volatile((base() + 0x104) as *mut u32, dieptxf1);
core::ptr::write_volatile((base() + 0x108) as *mut u32, dieptxf2);
}
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV FIFO total={} GRXFSIZ={:#x} GNPTXFSIZ={:#x} DIEPTXF1={:#x} DIEPTXF2={:#x}",
total,
r.grxfsiz.get(),
r.gnptxfsiz.get(),
dieptxf1,
dieptxf2,
));
let _ = used;
}
fn flush_all_fifos() -> UsbResult<()> {
wait_ahb_idle()?;
let r = regs();
r.grstctl
.write(GRSTCTL::TXFFLSH::SET + GRSTCTL::TXFNUM.val(0x10));
for _ in 0..3_000_000u32 {
if !r.grstctl.is_set(GRSTCTL::TXFFLSH) {
break;
}
spin_delay(8);
}
r.grstctl.write(GRSTCTL::RXFFLSH::SET);
for _ in 0..3_000_000u32 {
if !r.grstctl.is_set(GRSTCTL::RXFFLSH) {
break;
}
spin_delay(8);
}
spin_delay(2_000);
Ok(())
}
fn ep0_pre_config() {
let r = regs();
let mps_field = 0u32; let in_ep0 = &r.diep[0];
let out_ep0 = &r.doep[0];
in_ep0.diepctl.modify(
DIEPCTL::MPS.val(mps_field) + DIEPCTL::EPTYPE::Control + DIEPCTL::USBACTEP::SET,
);
out_ep0.doepctl.modify(
DOEPCTL::MPS.val(mps_field) + DOEPCTL::EPTYPE::Control + DOEPCTL::USBACTEP::SET,
);
r.diepmsk
.write(DEPMSK::XFERCOMPL::SET + DEPMSK::TIMEOUT_OR_SETUP::SET + DEPMSK::EPDISBLD::SET);
r.doepmsk
.write(DEPMSK::XFERCOMPL::SET + DEPMSK::TIMEOUT_OR_SETUP::SET + DEPMSK::EPDISBLD::SET);
r.daintmsk
.write(crate::usb::host::dwc2::regs::DAINTMSK::IEPMSK.val(1)
+ crate::usb::host::dwc2::regs::DAINTMSK::OEPMSK.val(1));
}
pub fn dwc2_device_init() -> UsbResult<()> {
if base() == 0 {
return Err(UsbError::Hardware(
"DWC2 base not set (call platform::set_dwc2_base_virt)",
));
}
let r = regs();
r.gintmsk.set(0);
r.gintsts.set(0xFFFF_FFFF);
core_soft_reset()?;
#[cfg(feature = "cv182x-host")]
init_gusbcfg_cv182x();
#[cfg(not(feature = "cv182x-host"))]
{
let r = regs();
r.gusbcfg
.modify(GUSBCFG::FORCEHOSTMODE::CLEAR + GUSBCFG::FORCEDEVMODE::SET);
}
force_device_mode()?;
core_soft_reset()?;
#[cfg(feature = "cv182x-host")]
init_gusbcfg_cv182x();
#[cfg(not(feature = "cv182x-host"))]
regs()
.gusbcfg
.modify(GUSBCFG::FORCEHOSTMODE::CLEAR + GUSBCFG::FORCEDEVMODE::SET);
force_device_mode()?;
regs().pcgctl.set(0);
init_gahb_dma();
let speed = current_speed_hint();
let dcfg_speed = match speed {
DeviceSpeedHint::HighSpeed => DCFG::DEVSPD::HighSpeed,
DeviceSpeedHint::FullSpeed => DCFG::DEVSPD::FullSpeedHs,
};
regs().dcfg.modify(
dcfg_speed
+ DCFG::DEVADDR.val(0)
+ DCFG::NZSTSOUTHSHK::CLEAR
+ DCFG::DESCDMA::CLEAR
+ DCFG::PERFRINT::Frm80,
);
regs().dctl.modify(DCTL::SFTDISCON::SET);
init_device_fifos();
flush_all_fifos()?;
ep0_pre_config();
regs().gintmsk.set(0);
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV init done speed={:?} GUSBCFG={:#010x} DCFG={:#010x} DCTL={:#010x}",
speed,
regs().gusbcfg.get(),
regs().dcfg.get(),
regs().dctl.get(),
));
Ok(())
}
pub fn dwc2_device_softconnect() {
if base() == 0 {
return;
}
regs().dctl.modify(DCTL::SFTDISCON::CLEAR);
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV softconnect: DCTL={:#010x}",
regs().dctl.get()
));
}
pub fn dwc2_device_softdisconnect() {
if base() == 0 {
return;
}
regs().dctl.modify(DCTL::SFTDISCON::SET);
}
pub fn dwc2_device_dump_status(tag: &str) {
if base() == 0 {
return;
}
let r = regs();
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV {tag} GINTSTS={:#010x} GUSBCFG={:#010x} DCFG={:#010x} DCTL={:#010x} DSTS={:#010x}",
r.gintsts.get(),
r.gusbcfg.get(),
r.dcfg.get(),
r.dctl.get(),
r.dsts.get()
));
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV {tag} DAINT={:#010x} DAINTMSK={:#010x} DIEPCTL0={:#010x} DOEPCTL0={:#010x}",
r.daint.get(),
r.daintmsk.get(),
r.diep[0].diepctl.get(),
r.doep[0].doepctl.get(),
));
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV {tag} DOEPINT0={:#010x} DOEPTSIZ0={:#010x} DOEPDMA0={:#010x} DIEPINT0={:#010x} DIEPTSIZ0={:#010x}",
r.doep[0].doepint.get(),
r.doep[0].doeptsiz.get(),
r.doep[0].doepdma.get(),
r.diep[0].diepint.get(),
r.diep[0].dieptsiz.get(),
));
}