Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions embassy-mcxa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ embedded-storage = "0.3.1"
# Custom executor support
embassy-executor = { version = "0.10.0", path = "../embassy-executor" }

# USB device driver trait layer
embassy-usb-driver = { version = "0.2.2", path = "../embassy-usb-driver" }

# `time` dependencies
embassy-time = { version = "0.5.1", path = "../embassy-time" }
embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", features = ["tick-hz-1_000_000"] }
Expand All @@ -59,15 +62,21 @@ grounded = { version = "0.2.1", features = ["cas", "critical-section"] }
heapless = "0.9"
maitake-sync = { version = "0.3.0", default-features = false, features = ["critical-section", "no-cache-pad"] }
nb = "1.1.0"
nxp-pac = { git = "https://github.com/embassy-rs/nxp-pac.git", rev = "a46946e7f5ba3b5d8136f2962400300d54a99d6c", features = ["rt"] }
# TEMPORARY: pinned to the branch behind upstream PR embassy-rs/nxp-pac#108, which
# adds the MCXA577 USBHS/USBPHY register blocks this driver needs. Those registers
# only exist on that branch until the PR merges. Switch both nxp-pac entries back to
# a released/main rev of `https://github.com/embassy-rs/nxp-pac.git` once #108 merges.
nxp-pac = { git = "https://github.com/bogdan-petru/nxp-pac.git", rev = "1a3fd38855dc1d9c81c3ecdf7be2188e363180f0", features = ["rt"] }
paste = "1.0.15"

rand-core-06 = { package = "rand_core", version = "0.6" }
rand-core-09 = { package = "rand_core", version = "0.9" }
rand-core-10 = { package = "rand_core", version = "0.10" }

[build-dependencies]
nxp-pac = { git = "https://github.com/embassy-rs/nxp-pac.git", rev = "a46946e7f5ba3b5d8136f2962400300d54a99d6c", default-features = false, features = ["metadata"] }
# TEMPORARY: see the note on the `[dependencies]` nxp-pac entry above. Switch back
# to `https://github.com/embassy-rs/nxp-pac.git` once embassy-rs/nxp-pac#108 merges.
nxp-pac = { git = "https://github.com/bogdan-petru/nxp-pac.git", rev = "1a3fd38855dc1d9c81c3ecdf7be2188e363180f0", default-features = false, features = ["metadata"] }
proc-macro2 = "1.0.106"
quote = "1.0.45"
regex = "1.12.3"
Expand Down
89 changes: 89 additions & 0 deletions embassy-mcxa/src/clocks/periph_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use crate::pac::mrcc::{
AdcClkselMux, ClkdivHalt, ClkdivReset, ClkdivUnstab, CtimerClkselMux, FclkClkselMux, Lpi2cClkselMux,
LpspiClkselMux, LpuartClkselMux, OstimerClkselMux,
};
#[cfg(feature = "mcxa5xx")]
use crate::pac::mrcc::{PhyClkselMux, UsbClkselMux};

#[must_use]
pub struct PreEnableParts {
Expand Down Expand Up @@ -207,6 +209,93 @@ impl SPConfHelper for Clk1MConfig {
}
}

//
// USBHS
//

/// Clock configuration for the MCXA5xx USBHS controller and PHY.
#[cfg(feature = "mcxa5xx")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct UsbhsConfig {
/// Power state required for the SOSC reference clock.
pub power: PoweredClock,
/// USB PHY reference clock divider.
pub phy_div: Div4,
}

#[cfg(feature = "mcxa5xx")]
impl Default for UsbhsConfig {
fn default() -> Self {
Self {
power: PoweredClock::NormalEnabledDeepSleepDisabled,
phy_div: Div4::no_div(),
}
}
}

#[cfg(all(feature = "mcxa5xx", feature = "defmt"))]
impl defmt::Format for UsbhsConfig {
fn format(&self, f: defmt::Formatter) {
defmt::write!(
f,
"UsbhsConfig {{ power: {:?}, phy_div: {=u32} }}",
self.power,
self.phy_div.into_divisor()
)
}
}

#[cfg(feature = "mcxa5xx")]
impl SPConfHelper for UsbhsConfig {
fn pre_enable_config(&self, clocks: &Clocks) -> Result<PreEnableParts, ClockError> {
#[cfg(feature = "sosc-as-gpio")]
{
let _ = clocks;
return Err(ClockError::BadConfig {
clock: "usbhs phy",
reason: "requires SOSC pins",
});
}

#[cfg(not(feature = "sosc-as-gpio"))]
{
let freq = clocks.ensure_clk_in_active(&self.power)?;
if freq != 24_000_000 {
return Err(ClockError::BadConfig {
clock: "usbhs phy",
reason: "requires a 24 MHz SOSC reference",
});
}

let mrcc0 = crate::pac::MRCC0;
mrcc0
.mrcc_usb1_clksel()
.write(|w| w.set_mux(UsbClkselMux::I2ClkUsbhs0PhyClkXtal));
mrcc0
.mrcc_usb1_phy_clksel()
.write(|w| w.set_mux(PhyClkselMux::I2ClkrootSosc));

let clkdiv = mrcc0.mrcc_usb1_phy_clkdiv();
clkdiv.modify(|w| {
w.set_div(self.phy_div.into_bits());
w.set_halt(ClkdivHalt::Off);
w.set_reset(ClkdivReset::Off);
});
clkdiv.modify(|w| {
w.set_halt(ClkdivHalt::On);
w.set_reset(ClkdivReset::On);
});
while clkdiv.read().unstab() == ClkdivUnstab::Off {}

Ok(PreEnableParts {
freq: freq / self.phy_div.into_divisor(),
wake_guard: WakeGuard::for_power(&self.power),
})
}
}
}

/// Placeholder configuration for the DAC peripheral.
///
/// The DAC HAL driver is not yet implemented, but the PAC metadata
Expand Down
3 changes: 3 additions & 0 deletions embassy-mcxa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub mod sgi;
pub mod spi;
#[cfg(mcxa_trng)]
pub mod trng;
/// USB 2.0 full-speed device driver (MCXA5xx USBHS controller).
#[cfg(feature = "mcxa5xx")]
pub mod usb;
#[cfg(mcxa_wwdt)]
pub mod wwdt;

Expand Down
118 changes: 118 additions & 0 deletions embassy-mcxa/src/usb/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! USBPHY bring-up for the MCXA5xx USBHS controller.
//!
//! Clock source selection and MRCC gate/reset handling live in the clocks
//! subsystem. This module only performs the USBPHY register sequence from the
//! NXP MCUXpresso SDK `USB_EhciPhyInit` path.

/// PHY trim parameters for the high-speed transmit drivers.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct PhyConfig {
/// `D_CAL` trim value. Must fit in the 4-bit hardware field.
pub d_cal: u8,
/// `TXCAL45DP` trim value. Must fit in the 4-bit hardware field.
pub txcal45dp: u8,
/// `TXCAL45DM` trim value. Must fit in the 4-bit hardware field.
pub txcal45dm: u8,
/// PLL divider select for the reference clock (2 = 24 MHz reference).
/// Must fit in the 3-bit hardware field.
pub pll_div_sel: u8,
}

impl Default for PhyConfig {
fn default() -> Self {
Self {
d_cal: 0,
txcal45dp: 0,
txcal45dm: 0,
pll_div_sel: 2,
}
}
}

impl PhyConfig {
/// FRDM-MCXA577 board calibration values, using the 24 MHz crystal reference.
pub const fn frdm_mcxa577() -> Self {
Self {
d_cal: 0x04,
txcal45dp: 0x07,
txcal45dm: 0x07,
pll_div_sel: 2,
}
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PhyInitError {
InvalidConfig,
PllLock,
}

/// Bring up the USBPHY PLL and transmitter trims.
///
/// # Safety
///
/// Must be called after the USBHS/USBPHY MRCC gates and resets are configured,
/// with exclusive access to USBPHY. The SOSC / PHY reference clock must already
/// be running.
pub(crate) unsafe fn init_phy(cfg: &PhyConfig) -> Result<(), PhyInitError> {
if cfg.d_cal > 0x0f || cfg.txcal45dp > 0x0f || cfg.txcal45dm > 0x0f || cfg.pll_div_sel > 0x07 {
return Err(PhyInitError::InvalidConfig);
}

let phy = crate::pac::USB1_HS_PHY;

phy.CTRL_CLR().write(|w| w.set_SFTRST(true));
phy.ANACTRL_SET().write(|w| w.set_LVI_EN(true));
phy.PLL_SIC_SET().write(|w| w.set_PLL_REG_ENABLE(true));

// SDK waits at least 15 us for the PLL regulator to stabilize. This is
// deliberately conservative in cycles because the CPU clock varies by app.
cortex_m::asm::delay(15_000);

phy.PLL_SIC_SET().write(|w| w.set_PLL_POWER(true));
phy.PLL_SIC_CLR().write(|w| {
w.set_PLL_DIV_SEL(0b111);
w.set_PLL_BYPASS(true);
});
phy.PLL_SIC_SET().write(|w| {
w.set_PLL_DIV_SEL(cfg.pll_div_sel);
w.set_PLL_EN_USB_CLKS(true);
});

phy.CTRL_CLR().write(|w| w.set_CLKGATE(true));
phy.PWD().write(|w| w.0 = 0);

let mut locked = false;
for _ in 0..4_000_000 {
if phy.PLL_SIC().read().PLL_LOCK() == crate::pac::usbphy::PLL_LOCK::PLL_LOCKED {
locked = true;
break;
}
cortex_m::asm::nop();
}
if !locked {
return Err(PhyInitError::PllLock);
}

phy.TRIM_OVERRIDE_EN_SET().write(|w| {
w.set_DIV_SEL_OVERRIDE(true);
w.set_TX_D_CAL_OVERRIDE(true);
w.set_TX_CAL45DP_OVERRIDE(true);
w.set_TX_CAL45DM_OVERRIDE(true);
});
phy.CTRL_SET().write(|w| {
w.set_ENUTMILEVEL2(true);
w.set_ENUTMILEVEL3(true);
});
phy.PWD().write(|w| w.0 = 0);

phy.TX().modify(|w| {
w.set_D_CAL(crate::pac::usbphy::D_CAL::from_bits(cfg.d_cal));
w.set_TXCAL45DN(cfg.txcal45dm);
w.set_TXCAL45DP(cfg.txcal45dp);
});

Ok(())
}
101 changes: 101 additions & 0 deletions embassy-mcxa/src/usb/ehci.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! EHCI device-mode DMA structures for the MCXA5xx USBHS controller.
//!
//! The MMIO register blocks come from `nxp-pac`; the queue heads and transfer
//! descriptors are RAM data structures consumed directly by the controller.

/// Device queue head (dQH). One per endpoint and direction (so `2 * N` total),
/// aligned to 64 bytes and laid out as a contiguous array in RAM.
///
/// Layout per the Chipidea/EHCI device specification.
#[repr(C, align(64))]
#[derive(Clone, Copy)]
pub(crate) struct QueueHead {
/// Capabilities: max packet length, ZLT, IOS, Mult.
pub(crate) capabilities: u32,
/// Current dTD pointer (hardware-owned).
pub(crate) current_dtd: u32,
/// dTD overlay area (8 words): next pointer, token, 5 buffer pointers.
pub(crate) next_dtd: u32,
pub(crate) token: u32,
pub(crate) buffer: [u32; 5],
/// Reserved word to pad the overlay to spec.
pub(crate) _reserved: u32,
/// Setup packet buffer (8 bytes) for control endpoints.
pub(crate) setup: [u32; 2],
/// Padding to fill out to 64 bytes.
pub(crate) _pad: [u32; 4],
}

impl QueueHead {
pub(crate) const fn new() -> Self {
Self {
capabilities: 0,
current_dtd: 0,
next_dtd: 1, // terminate bit set
token: 0,
buffer: [0; 5],
_reserved: 0,
setup: [0; 2],
_pad: [0; 4],
}
}
}

/// Capability field: ZLT disable.
///
/// The driver sets this because `embassy-usb` drives zero-length packets and
/// control status stages explicitly; the controller should not synthesize them.
pub(crate) const QH_CAP_ZLT: u32 = 1 << 29;
/// Capability field: interrupt-on-setup (control OUT QH).
pub(crate) const QH_CAP_IOS: u32 = 1 << 15;
/// Shift for the max-packet-length field in the QH capabilities word.
pub(crate) const QH_CAP_MAXLEN_SHIFT: u32 = 16;

/// Device transfer descriptor (dTD), 32-byte aligned.
#[repr(C, align(32))]
#[derive(Clone, Copy)]
pub(crate) struct TransferDescriptor {
/// Next dTD pointer (bit0 = terminate).
pub(crate) next: u32,
/// Token: total bytes, IOC, status.
pub(crate) token: u32,
/// Buffer page pointers.
pub(crate) buffer: [u32; 5],
/// Padding to 32 bytes.
pub(crate) _pad: u32,
}

impl TransferDescriptor {
pub(crate) const fn new() -> Self {
Self {
next: 1, // terminate
token: 0,
buffer: [0; 5],
_pad: 0,
}
}
}

/// dTD next-pointer terminate bit.
pub(crate) const DTD_NEXT_TERMINATE: u32 = 1 << 0;
/// dTD token: total bytes shift (bits 30:16).
pub(crate) const DTD_TOKEN_TOTAL_SHIFT: u32 = 16;
/// dTD token: remaining byte-count mask after shifting by [`DTD_TOKEN_TOTAL_SHIFT`].
pub(crate) const DTD_TOKEN_TOTAL_MASK: u32 = 0x7FFF;
/// dTD token: interrupt on complete.
pub(crate) const DTD_TOKEN_IOC: u32 = 1 << 15;
/// dTD token: active status bit.
pub(crate) const DTD_TOKEN_ACTIVE: u32 = 1 << 7;
/// dTD token: halted status bit.
pub(crate) const DTD_TOKEN_HALTED: u32 = 1 << 6;
/// dTD token: data-buffer error status bit.
pub(crate) const DTD_TOKEN_DATA_BUFFER_ERROR: u32 = 1 << 5;
/// dTD token: transaction error status bit.
pub(crate) const DTD_TOKEN_TRANSACTION_ERROR: u32 = 1 << 3;
/// dTD token: transfer status bits that indicate a completed transfer failed.
pub(crate) const DTD_TOKEN_ERROR_MASK: u32 =
DTD_TOKEN_HALTED | DTD_TOKEN_DATA_BUFFER_ERROR | DTD_TOKEN_TRANSACTION_ERROR;
/// dTD buffer pointer page size. Each dTD carries five page pointers.
pub(crate) const DTD_BUFFER_PAGE_SIZE: u32 = 0x1000;
/// dTD buffer pointer page mask.
pub(crate) const DTD_BUFFER_PAGE_MASK: u32 = DTD_BUFFER_PAGE_SIZE - 1;
Loading