diff --git a/.cargo/config.toml b/.cargo/config.toml index a95af8f3..f24eb0ad 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,3 +6,4 @@ rustflags = [ "-C", "link-arg=-error-limit=0", "-C", "link-arg=-nmagic", ] +runner = ["probe-rs", "run", "--chip=mimxrt1170"] diff --git a/Cargo.toml b/Cargo.toml index 1c809092..a99a1c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,10 @@ version = "0.1.3" # imxrt-rs dependencies ####################### +[dependencies.imxrt-enet] +workspace = true +optional = true + [dependencies.imxrt-dma] workspace = true @@ -88,10 +92,10 @@ features = ["imxrt-ral/imxrt1062", "imxrt1060"] [features] default = ["imxrt-usbd"] imxrt1010 = ["imxrt-iomuxc/imxrt1010"] -imxrt1020 = ["imxrt-iomuxc/imxrt1020"] -imxrt1060 = ["imxrt-iomuxc/imxrt1060"] -imxrt1064 = ["imxrt-iomuxc/imxrt1060"] -imxrt1170 = ["imxrt-iomuxc/imxrt1170"] +imxrt1020 = ["imxrt-iomuxc/imxrt1020", "imxrt-enet"] +imxrt1060 = ["imxrt-iomuxc/imxrt1060", "imxrt-enet"] +imxrt1064 = ["imxrt-iomuxc/imxrt1060", "imxrt-enet"] +imxrt1170 = ["imxrt-iomuxc/imxrt1170", "imxrt-enet"] ################ # Extra features @@ -102,10 +106,11 @@ imxrt1170 = ["imxrt-iomuxc/imxrt1170"] eh02-unproven = [] [workspace] -members = ["board", "logging"] +members = ["board", "logging", "tools"] [workspace.dependencies] imxrt-dma = "0.1" +imxrt-enet = { git = "https://github.com/mciantyre/imxrt-enet" } imxrt-iomuxc = "0.2.1" imxrt-hal = { version = "0.5", path = "." } imxrt-log = { path = "logging", default-features = false, features = [ diff --git a/board/Cargo.toml b/board/Cargo.toml index 53d2e3b8..ce87bfd9 100644 --- a/board/Cargo.toml +++ b/board/Cargo.toml @@ -5,6 +5,9 @@ edition = { workspace = true } # Only for testing, documentation. publish = false +[dependencies.bitflags] +version = "1.2" + [dependencies.defmt] version = "0.3" @@ -54,6 +57,12 @@ version = "0.2" version = "0.3" optional = true +[dependencies.smoltcp] +version = "0.10.0" +optional = true +default-features = false +features = ["socket-tcp", "socket-udp", "socket-dhcpv4", "defmt"] + # knurling-rs style defmt-rtt with panic-probe [target.thumbv7em-none-eabihf.dependencies.defmt-rtt] version = "0.4" @@ -123,6 +132,7 @@ imxrt1170evk-cm7 = [ "defmt-rtt", "panic-probe", "imxrt-log", + "enet", ] logging-defmt = ["imxrt-log/defmt"] @@ -133,5 +143,6 @@ logging-defmt = ["imxrt-log/defmt"] # # See board-specific documentation for its effects. spi = [] +enet = ["dep:smoltcp"] lcd1602 = ["dep:lcd_1602_i2c"] diff --git a/board/build.rs b/board/build.rs index 4c59d767..a83062e5 100644 --- a/board/build.rs +++ b/board/build.rs @@ -83,7 +83,7 @@ fn main() -> Result<(), Box> { 16 * 1024 * 1024, ) .rodata(imxrt_rt::Memory::Dtcm) - .stack_size(32 * 1024) + .stack_size(48 * 1024) .build()?; println!("cargo:rustc-cfg=board=\"imxrt1170evk-cm7\""); println!("cargo:rustc-cfg=chip=\"imxrt1170\""); diff --git a/board/src/imxrt1170evk-cm7.rs b/board/src/imxrt1170evk-cm7.rs index 3e5a734b..372a7787 100644 --- a/board/src/imxrt1170evk-cm7.rs +++ b/board/src/imxrt1170evk-cm7.rs @@ -23,8 +23,12 @@ fn defmt_panic() -> ! { /// You'll find log messages using the serial console, through the DAP. pub(crate) const DEFAULT_LOGGING_BACKEND: crate::logging::Backend = crate::logging::Backend::Lpuart; +pub const ENET_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01]; + use hal::ccm::clock_gate; const CLOCK_GATES: &[clock_gate::Locator] = &[ + clock_gate::iomuxc(), + clock_gate::iomuxc_lpsr(), clock_gate::gpio(), clock_gate::dma(), clock_gate::pit::<1>(), @@ -38,13 +42,58 @@ const CLOCK_GATES: &[clock_gate::Locator] = &[ clock_gate::snvs(), ]; +const ENET_CLOCK_GATES: &[clock_gate::Locator] = &[ + clock_gate::enet(), + clock_gate::enet_1g(), + clock_gate::enet_qos(), +]; + pub(crate) unsafe fn configure() { let mut ccm = ral::ccm::CCM::instance(); + let gpc = unsafe { ral::gpc_cpu_mode_ctrl_::GPC_CPU_MODE_CTRL_0::instance() }; + let gpc = &*gpc; + let pll = unsafe { ral::anadig_pll::ANADIG_PLL::instance() }; + let mut pmu = unsafe { ral::anadig_pmu::ANADIG_PMU::instance() }; + + hal::pmu::enable_pll_reference_voltage(&mut pmu, true); + hal::pmu::set_phy_ldo_setpoints(&mut pmu, hal::pmu::Setpoint::all()); + hal::pmu::set_phy_ldo_control(&mut pmu, hal::pmu::ControlMode::Gpc); + hal::pmu::set_pll_reference_control(&mut pmu, hal::pmu::ControlMode::Gpc); + + for clock_source in { + use hal::ccm::ClockSource::*; + [Pll1, Pll1Clk, Pll1Div2, Pll1Div5] + } { + let oscpll = &ccm.OSCPLL[clock_source as usize]; + ral::write_reg!(ral::ccm::oscpll, oscpll, OSCPLL_DIRECT, 0); + hal::ccm::set_setpoints(&mut ccm, clock_source, hal::ccm::Setpoint::SP1); + hal::ccm::set_gpc_control_mode(&mut ccm, clock_source, hal::ccm::ControlMode::Gpc).unwrap(); + } + + ral::modify_reg!(ral::anadig_pll, pll, SYS_PLL1_CTRL, + SYS_PLL1_CONTROL_MODE: 1, + SYS_PLL1_DIV2_CONTROL_MODE: 1, + SYS_PLL1_DIV5_CONTROL_MODE: 1, + ); + prepare_clock_tree(&mut ccm); CLOCK_GATES .iter() .for_each(|locator| locator.set(&mut ccm, clock_gate::ON)); + + ENET_CLOCK_GATES + .iter() + .for_each(|l| l.set(&mut ccm, clock_gate::OFF)); + clock_tree::enet_root_on(&mut ccm, false); + + clock_tree::configure_enet(RUN_MODE, &mut ccm); + hal::gpc::request_setpoint_transition(gpc, 1).unwrap(); + + clock_tree::enet_root_on(&mut ccm, true); + ENET_CLOCK_GATES + .iter() + .for_each(|l| l.set(&mut ccm, clock_gate::ON)); } fn prepare_clock_tree(ccm: &mut ral::ccm::CCM) { @@ -65,6 +114,8 @@ pub const LPSPI_CLK_FREQUENCY: u32 = clock_tree::lpspi_frequency:: pub const LPI2C_CLK_FREQUENCY: u32 = clock_tree::lpi2c_frequency::(RUN_MODE); pub const PWM_PRESCALER: hal::flexpwm::Prescaler = hal::flexpwm::Prescaler::Prescaler8; pub const PWM_FREQUENCY: u32 = clock_tree::bus_frequency(RUN_MODE) / PWM_PRESCALER.divider(); +pub const ENET_FREQUENCY: u32 = clock_tree::ENET1_FREQUENCY; +pub const ENET_BUS_FREQUENCY: u32 = clock_tree::bus_frequency(RUN_MODE); pub type Led = hal::gpio::Output; @@ -162,6 +213,7 @@ pub struct Specifics { pub spi: Spi, pub pwm: Pwm, pub i2c: I2c, + pub enet: imxrt_ral::enet::ENET, } impl Specifics { @@ -178,14 +230,42 @@ impl Specifics { let mut gpio13 = hal::gpio::Port::new(gpio13); let button = hal::gpio::Input::without_pin(&mut gpio13, 0); - let iomuxc = unsafe { ral::iomuxc::IOMUXC::instance() }; + let iomuxc_gpr = unsafe { ral::iomuxc_gpr::IOMUXC_GPR::instance() }; + // ENET_REF_CLK driven by ENET1_CLK_ROOT. + ral::modify_reg!(ral::iomuxc_gpr, iomuxc_gpr, GPR4, ENET_REF_CLK_DIR: 1); + // Handle ERR050396. Depending on the runtime config, we might place buffers into TCM. + ral::modify_reg!(ral::iomuxc_gpr, iomuxc_gpr, GPR28, CACHE_ENET1G: 0, CACHE_ENET: 0); + + let mut iomuxc = unsafe { ral::iomuxc::IOMUXC::instance() }; + let mut iomuxc_lpsr = unsafe { ral::iomuxc_lpsr::IOMUXC_LPSR::instance() }; + configure_eth_pins(&mut iomuxc, &mut iomuxc_lpsr); let mut iomuxc = super::convert_iomuxc(iomuxc); configure_pins(&mut iomuxc); let gpio9 = unsafe { ral::gpio::GPIO9::instance() }; + let gpio12 = unsafe { ral::gpio::GPIO12::instance() }; + + const ENET_INT: u32 = 1 << 11; + const ENET_RST: u32 = 1 << 12; + ral::modify_reg!(ral::gpio, gpio9, GDIR, |gdir| gdir | ENET_INT); + ral::modify_reg!(ral::gpio, gpio12, GDIR, |gdir| gdir | ENET_RST); + + ral::write_reg!(ral::gpio, gpio9, DR_CLEAR, ENET_INT); + ral::write_reg!(ral::gpio, gpio12, DR_CLEAR, ENET_RST); + common.block_ms(500); + + ral::write_reg!(ral::gpio, gpio9, DR_SET, ENET_INT); + common.block_ms(500); + ral::write_reg!(ral::gpio, gpio12, DR_SET, ENET_RST); + common.block_ms(500); + let mut gpio9 = hal::gpio::Port::new(gpio9); let led = gpio9.output(iomuxc.gpio_ad.p04); + // let ccm_enet_25mh_ref = gpio9.output(iomuxc.gpio_ad.p14); + // ccm_enet_25mh_ref.set(); + hal::iomuxc::alternate(&mut iomuxc.gpio_ad.p14, 9); + let console = unsafe { ral::lpuart::Instance::<{ CONSOLE_INSTANCE }>::instance() }; let mut console = hal::lpuart::Lpuart::new( console, @@ -261,6 +341,7 @@ impl Specifics { spi, pwm, i2c, + enet: unsafe { imxrt_ral::enet::ENET::instance() }, } } } @@ -286,6 +367,178 @@ fn configure_pins(iomuxc: &mut super::Pads) { ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_29, DSE: DSE_1_HIGH_DRIVER); } +fn configure_eth_pins( + iomuxc: &mut ral::iomuxc::IOMUXC, + iomuxc_lpsr: &mut ral::iomuxc_lpsr::IOMUXC_LPSR, +) { + // + // Muxing + // + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_AD_32, MUX_MODE: ALT3_ENET_MDC, SION: 0); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_AD_33, MUX_MODE: ALT3_ENET_MDIO, SION: 0); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_02, MUX_MODE: ALT1_ENET_TX_DATA0, SION: 0); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_03, MUX_MODE: ALT1_ENET_TX_DATA1, SION: 0); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_04, MUX_MODE: ALT1_ENET_TX_EN, SION: 0); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_05, MUX_MODE: ALT2_ENET_REF_CLK, SION: 1); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_06, MUX_MODE: ALT1_ENET_RX_DATA0, SION: 1); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_07, MUX_MODE: ALT1_ENET_RX_DATA1, SION: 1); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_08, MUX_MODE: ALT1_ENET_RX_EN, SION: 0); + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_DISP_B2_09, MUX_MODE: ALT1_ENET_RX_ER, SION: 0); + + ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_AD_12, MUX_MODE: ALT10_GPIO9_IO11, SION: 0); + ral::write_reg!(ral::iomuxc_lpsr, iomuxc_lpsr, SW_MUX_CTL_PAD_GPIO_LPSR_12, MUX_MODE: ALT10_GPIO12_IO12); + + // + // Pad configurations + // + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_02, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_0_PULL_DISABLE__HIGHZ, ODE: ODE_0_DISABLED); + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_03, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_0_PULL_DISABLE__HIGHZ, ODE: ODE_0_DISABLED); + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_04, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_0_PULL_DISABLE__HIGHZ, ODE: ODE_0_DISABLED); + + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_05, SRE: SRE_1_FAST_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_0_PULL_DISABLE__HIGHZ, ODE: ODE_0_DISABLED); + + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_06, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_1_PULL_ENABLE, PUS: PUS_0_WEAK_PULL_DOWN, ODE: ODE_0_DISABLED); + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_07, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_1_PULL_ENABLE, PUS: PUS_0_WEAK_PULL_DOWN, ODE: ODE_0_DISABLED); + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_08, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_1_PULL_ENABLE, PUS: PUS_0_WEAK_PULL_DOWN, ODE: ODE_0_DISABLED); + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_DISP_B2_09, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_1_PULL_ENABLE, PUS: PUS_0_WEAK_PULL_DOWN, ODE: ODE_0_DISABLED); + + ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_12, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_1_PULL, PUS: PUS_0_WEAK_PULL_DOWN, ODE: ODE_0_OPEN_DRAIN_DISABLED); + ral::write_reg!(ral::iomuxc_lpsr, iomuxc_lpsr, SW_PAD_CTL_PAD_GPIO_LPSR_12, SRE: SRE_0_SLOW_SLEW_RATE, DSE: DSE_1_HIGH_DRIVER, PUE: PUE_1_PULL, PUS: PUS_1_WEAK_PULL_UP, ODE_LPSR: ODE_LPSR_0_DISABLED); + + // + // Input daisies + // + ral::write_reg!(ral::iomuxc, iomuxc, ENET_MAC0_MDIO_SELECT_INPUT, DAISY: SELECT_GPIO_AD_33_ALT3); + ral::write_reg!(ral::iomuxc, iomuxc, ENET_IPG_CLK_RMII_SELECT_INPUT, DAISY: SELECT_GPIO_DISP_B2_05_ALT2); + ral::write_reg!(ral::iomuxc, iomuxc, ENET_MAC0_RXDATA_SELECT_INPUT_0, DAISY: SELECT_GPIO_DISP_B2_06_ALT1); + ral::write_reg!(ral::iomuxc, iomuxc, ENET_MAC0_RXDATA_SELECT_INPUT_1, DAISY: SELECT_GPIO_DISP_B2_07_ALT1); + ral::write_reg!(ral::iomuxc, iomuxc, ENET_MAC0_RXEN_SELECT_INPUT, DAISY: SELECT_GPIO_DISP_B2_08_ALT1); + ral::write_reg!(ral::iomuxc, iomuxc, ENET_MAC0_RXERR_SELECT_INPUT, DAISY: SELECT_GPIO_DISP_B2_09_ALT1); +} + +fn init_enet_phy_ksz8081rnb< + M: hal::enet::MiimRead + + hal::enet::MiimWrite, +>( + mdio: &mut M, +) -> Result<(), &'static str> { + // + // TODO(mciantyre) Upstream this, somewhere, and add missing parts. + // + const PHY_ID1_REG: u8 = 0x02; + const PHY_BASICCONTROL_REG: u8 = 0x00; + const PHY_BASICSTATUS_REG: u8 = 0x01; + const PHY_AUTONEG_ADVERTISE_REG: u8 = 0x04; + + const KSZ8081_PHY_ADDR: u8 = 0x02; // Arbitrary. + const KSZ8081_PHY_CONTROL2_REG: u8 = 0x1F; + const KSZ8081_PHY_CTL2_REFCLK_SELECT_MASK: u16 = 0x0080; + + bitflags::bitflags! { + struct BasicControl : u16 { + const RESET = 0x8000; + const AUTONEG = 0x1000; + const RESTART_AUTONEG = 0x0200; + } + } + + bitflags::bitflags! { + struct BasicStatus : u16 { + const LINKSTATUS = 0x0004; + const AUTONEG_COMP = 0x0020; + } + } + + bitflags::bitflags! { + struct AutoNegotiation : u16 { + const BASET4_100_ABILITY = 0x0200; + const BASETX_100_FULL_DUPLEX = 0x0100; + const BASETX_100_HALF_DUPLEX = 0x0080; + const BASETX_10_FULL_DUPLEX = 0x0040; + const BASETX_10_HALF_DUPLEX = 0x0020; + const IEEE802_3_SELECTOR = 0x0001; + } + } + + let mut retries = 100000usize; + while retries > 0 { + const KSZ8081_ID: u16 = 0x22; + let address = mdio.read(KSZ8081_PHY_ADDR, PHY_ID1_REG).unwrap(); + if address == KSZ8081_ID { + break; + } + retries -= 1; + } + if retries == 0 { + return Err("Could not detect KSZ8081"); + } + + mdio.write( + KSZ8081_PHY_ADDR, + PHY_BASICCONTROL_REG, + BasicControl::RESET.bits(), + ) + .unwrap(); + + // Use RMII50M (KSZ8081-specific configuration). + let ctrl2 = mdio + .read(KSZ8081_PHY_ADDR, KSZ8081_PHY_CONTROL2_REG) + .unwrap(); + mdio.write( + KSZ8081_PHY_ADDR, + KSZ8081_PHY_CONTROL2_REG, + ctrl2 | KSZ8081_PHY_CTL2_REFCLK_SELECT_MASK, + ) + .unwrap(); + + // Enable auto-negotiation. + mdio.write( + KSZ8081_PHY_ADDR, + PHY_AUTONEG_ADVERTISE_REG, + (AutoNegotiation::BASETX_100_FULL_DUPLEX | AutoNegotiation::IEEE802_3_SELECTOR).bits(), + ) + .unwrap(); + mdio.write( + KSZ8081_PHY_ADDR, + PHY_BASICCONTROL_REG, + (BasicControl::AUTONEG | BasicControl::RESTART_AUTONEG).bits(), + ) + .unwrap(); + + // Wait for auto-negotiation, and for the link to be available. + // + // This blocks while there's no link. + while !BasicStatus::from_bits_truncate( + mdio.read(KSZ8081_PHY_ADDR, PHY_BASICSTATUS_REG).unwrap(), + ) + .contains(BasicStatus::AUTONEG_COMP | BasicStatus::LINKSTATUS) + {} + + Ok(()) +} + +fn init_enet_phy_rtl8211f< + M: hal::enet::MiimRead + + hal::enet::MiimWrite, +>( + _: &mut M, +) -> Result<(), &'static str> { + // Intentionally left blank. + Ok(()) +} + +/// Initialize the KSZ8081 10/100-Mbit PHY. +pub fn init_enet_phy< + M: hal::enet::MiimRead + + hal::enet::MiimWrite, +>( + mdio: &mut M, +) -> Result<(), &'static str> { + init_enet_phy_ksz8081rnb(mdio)?; + init_enet_phy_rtl8211f(mdio)?; + Ok(()) +} + pub mod interrupt { use crate::board_interrupts as syms; use crate::ral::Interrupt; diff --git a/board/src/imxrt11xx/clock_tree.rs b/board/src/imxrt11xx/clock_tree.rs index 3158bf10..1ca3a3bf 100644 --- a/board/src/imxrt11xx/clock_tree.rs +++ b/board/src/imxrt11xx/clock_tree.rs @@ -29,6 +29,8 @@ enum ClockSource { /// SYS_PLL1, with dedicated 1GHz frequency /// for ETH. SysPll1, + /// SYS_PLL1 with a fixed divide-by-2. + SysPll1Div2, /// SYS_PLL2, main PLL (no PFDs). SysPll2, /// SYS_PLL3, main PLL (no PDFs). @@ -50,6 +52,7 @@ impl ClockSource { match (self, run_mode) { (ClockSource::ArmCm7, RunMode::Overdrive) => 1_000_000_000, // Brr... (ClockSource::SysPll1, _) => 1_000_000_000, + (ClockSource::SysPll1Div2, _) => 1_000_000_000 / 2, (ClockSource::SysPll2, _) => 528_000_000, (ClockSource::SysPll3, _) => 480_000_000, (ClockSource::XtalOsc24MHz, _) => XTAL_OSCILLATOR_HZ, @@ -107,6 +110,12 @@ fn configure_clock_root(offset: usize, selection: &Selection, ccm: &mut CCM) { ) {} } +#[inline(always)] +fn clock_root_on(offset: usize, ccm: &mut CCM, on: bool) { + let clock_root = &ccm.CLOCK_ROOT[offset]; + ral::modify_reg!(ral::ccm::clockroot, clock_root, CLOCK_ROOT_CONTROL, OFF: !on as u32); +} + /// Set the bus clock (IPG) configuration for the Cortex M7. /// /// When this call returns, the bus clock frequency matches the value returned @@ -263,3 +272,43 @@ where // LPSPI6 -> CLOCK_ROOT48 configure_clock_root(N as usize + 42, &lpspi_selection::(run_mode), ccm); } + +pub const ENET1_FREQUENCY: u32 = 50_000_000; + +/// Turn on / off all ENET root clocks. +pub fn enet_root_on(ccm: &mut CCM, on: bool) { + for offset in 51..=57 { + clock_root_on(offset, ccm, on); + } +} + +/// Configure clocks for all-things ETH. +/// +/// When this call returns, the ENET clock frequencies matches the value returned +/// by [`enet_frequency`]. +pub fn configure_enet(run_mode: RunMode, ccm: &mut CCM) { + const ENET1_ROOT_CLOCK_SOURCE: ClockSource = ClockSource::SysPll1Div2; + let enet1_divider: u32 = ENET1_ROOT_CLOCK_SOURCE.frequency(run_mode) / ENET1_FREQUENCY; + configure_clock_root( + 51, + &Selection { + mux: 4, + source: ENET1_ROOT_CLOCK_SOURCE, + divider: enet1_divider, + }, + ccm, + ); + + // All other ENET clocks are at 24MHz. + for offset in 52..=57 { + configure_clock_root( + offset, + &Selection { + mux: 0b001, + source: ClockSource::XtalOsc24MHz, + divider: 1, + }, + ccm, + ); + } +} diff --git a/board/src/lib.rs b/board/src/lib.rs index c5819562..5fd34c77 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -48,6 +48,8 @@ mod board_impl; #[cfg(feature = "lcd1602")] pub use lcd_1602_i2c as lcd1602; +#[cfg(feature = "enet")] +pub use smoltcp; // rustdoc doesn't like when this is named 'board' // since it happens to match the package name. @@ -124,6 +126,18 @@ impl Common { usbphy1: unsafe { UsbPhy1::instance() }, } } + + /// Block execution for some milliseconds. + /// + /// This is a convenience for board-specific set up. Use this instead + /// of manually configuring a timer. + fn block_ms(&mut self, ms: u32) { + self.pit.0.set_load_timer_value(PIT_FREQUENCY / 1_000 * ms); + self.pit.0.enable(); + while !self.pit.0.is_elapsed() {} + self.pit.0.clear_elapsed(); + self.pit.0.disable(); + } } /// Board entrypoint. diff --git a/examples/rtic_enet_dhcp.rs b/examples/rtic_enet_dhcp.rs new file mode 100644 index 00000000..e7d04fbb --- /dev/null +++ b/examples/rtic_enet_dhcp.rs @@ -0,0 +1,260 @@ +//! Demonstrates DHCP address assignment, then either TCP or UDP socket communication. +//! +//! The device acquires an IP address using DHCP. This can take a few seconds, so +//! be patient. Plug it into your DHCP-capable router, and wait for it to assign +//! an IP address. Check the device defmt for information on the assigned IP, or +//! check your router. The LED turns on once DHCP assignment completes. +//! +//! The transport behaviors depend on the `SocketDemo` configuration you select. +//! Change it depending on your desired demo: +//! +//! - `SocketDemo::TcpLoopback` listens on port 5000 for incoming connections. +//! Once you connect, the socket loops back any data sent to it. +//! - `SocketDemo::UdpBroadcast` occasionally broadcasts UDP packets. The packets +//! contain a counter that increments for every message sent from your board. + +#![no_std] +#![no_main] + +#[rtic::app(device = board, peripherals = false, dispatchers = [BOARD_SWTASK0])] +mod app { + + /// Variations of this demo. + /// + /// See the top-level docs for more information. + #[allow(dead_code)] + enum SocketDemo { + TcpLoopback, + UdpBroadcast, + } + + /// Change the demo here. + const SOCKET_DEMO: SocketDemo = SocketDemo::TcpLoopback; + + use board::smoltcp; + use hal::enet; + use imxrt_hal as hal; + + use smoltcp::iface::Config; + use smoltcp::iface::Interface; + use smoltcp::iface::SocketSet; + use smoltcp::socket::dhcpv4; + use smoltcp::socket::{tcp, udp}; + use smoltcp::time::Instant; + use smoltcp::wire::EthernetAddress; + use smoltcp::wire::IpCidr; + use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint, Ipv4Address}; + + #[local] + struct Local { + /// For realizing blocking delays in the idle loop. + delay: hal::timer::BlockingGpt<1, { board::GPT1_FREQUENCY }>, + /// The ethernet instance. + /// + /// Taken by the idle task. + enet: Option, + led: board::Led, + } + + #[shared] + struct Shared {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (board::Common { gpt1, .. }, board::Specifics { led, enet, .. }) = board::new(); + + let delay = hal::timer::Blocking::<_, { board::GPT1_FREQUENCY }>::from_gpt(gpt1); + + ( + Shared {}, + Local { + delay, + enet: Some(enet), + led, + }, + ) + } + + #[idle(local = [ + delay, enet, led, + txdt: enet::TransmitBuffers<2, 1520> = enet::TransmitBuffers::new(), + rxdt: enet::ReceiveBuffers<2, 1520> = enet::ReceiveBuffers::new(), + socket_buffer: [u8; 1024] = [0; 1024], + msg: [u8; 512] = [0; 512], + udp_tx_meta: [udp::PacketMetadata; 1] = [udp::PacketMetadata::EMPTY], + ])] + fn idle(cx: idle::Context) -> ! { + // Ethernet setup happens here, after init, in order to use interrupt-driven logging. + // + // TODO(mciantyre) that's not necessary if we use defmt_rtt. + let idle::LocalResources { + enet, + delay, + led, + txdt, + rxdt, + socket_buffer, + msg, + udp_tx_meta, + .. + } = cx.local; + let enet = enet.take().unwrap(); + + delay.block_ms(2000); + let mut dev = enet::Enet::new( + enet, + txdt.take(), + rxdt.take(), + board::ENET_BUS_FREQUENCY, + &board::ENET_MAC, + ); + delay.block_ms(200); + + defmt::info!("Initializing the PHY, and waiting for link..."); + // Call blocks until the entire link is up! TODO(mciantyre) not block while the link isn't available. + while let Err(what) = board::init_enet_phy(&mut dev) { + defmt::error!("Failed to initialize PHY: {}", what); + delay.block_ms(2000); + } + + dev.set_duplex(hal::enet::Duplex::Full); + dev.enable_mib(true); + dev.enable_rmii_mode(true); + dev.enable_10t_mode(false); + dev.enable_mac(true); + + let mut time: i64 = 0; + + let mut iface = Interface::new( + Config::new(EthernetAddress(board::ENET_MAC).into()), + &mut dev, + Instant::from_millis(time), + ); + + let dhcp_socket = dhcpv4::Socket::new(); + + let mut sockets: [_; 2] = Default::default(); + let mut sockets = SocketSet::new(sockets.as_mut_slice()); + let dhcp_handle = sockets.add(dhcp_socket); + + defmt::info!("Waiting for DHCP assignment..."); + loop { + time += 100; + delay.block_ms(100); + + iface.poll(Instant::from_millis(time), &mut dev, &mut sockets); + let dhcp_socket: &mut dhcpv4::Socket = sockets.get_mut(dhcp_handle); + if let Some(dhcpv4::Event::Configured(config)) = dhcp_socket.poll() { + led.set(); + defmt::info!("Received IP assignment! Address is {:?}", config.address); + iface.update_ip_addrs(|ips| { + ips.push(IpCidr::Ipv4(config.address)).unwrap(); + }); + break; + } + } + + let msg = msg.as_mut_slice(); + match SOCKET_DEMO { + SocketDemo::TcpLoopback => { + let buffer_split = socket_buffer.len() / 2; + let buffer_splits = socket_buffer.split_at_mut(buffer_split); + + defmt::info!("Starting TCP loopback mode using port 5000"); + let mut tcp_socket = tcp::Socket::new( + tcp::SocketBuffer::new(buffer_splits.0), + tcp::SocketBuffer::new(buffer_splits.1), + ); + tcp_socket.set_nagle_enabled(false); + if let Err(err) = tcp_socket.listen(IpListenEndpoint { + addr: None, + port: 5000, + }) { + defmt::error!("Failed to listen on TCP socket: {}", err); + panic!(); + } + let socket_handle = sockets.add(tcp_socket); + loop { + time += 10; + delay.block_ms(10); + if iface.poll(Instant::from_millis(time), &mut dev, &mut sockets) { + let tcp_socket: &mut tcp::Socket = sockets.get_mut(socket_handle); + let available = match tcp_socket.recv_slice(msg) { + Err(tcp::RecvError::InvalidState) => { + defmt::error!("TCP Receive error: invalid state"); + continue; + } + Err(tcp::RecvError::Finished) => { + defmt::warn!("Receive error finished; re-listening on port 5000"); + tcp_socket.abort(); + if let Err(err) = tcp_socket.listen(IpListenEndpoint { + addr: None, + port: 5000, + }) { + defmt::error!("Failed to listen on TCP socket: {}", err); + } + continue; + } + Ok(0) => continue, + Ok(n) => n, + }; + + defmt::info!("Received {} bytes from a client", available); + if let Some(remote) = tcp_socket.remote_endpoint() { + defmt::info!("Remote client: {:?}", remote); + } else { + defmt::warn!("Not sure of the remote's IP address!"); + } + + if let Err(err) = tcp_socket.send_slice(&msg[..available]) { + defmt::error!("TCP send error {:?}", err); + continue; + } + } + } + } + SocketDemo::UdpBroadcast => { + defmt::info!("Starting UDP broadcast mode"); + let not_receiving = udp::PacketBuffer::new(&mut [][..], &mut [][..]); + let mut udp_socket = udp::Socket::new( + not_receiving, + udp::PacketBuffer::new( + udp_tx_meta.as_mut_slice(), + socket_buffer.as_mut_slice(), + ), + ); + if let Err(err) = udp_socket.bind(IpListenEndpoint { + addr: None, + port: 5000, + }) { + defmt::error!("Failed to bind UDP socket: {}", err); + panic!(); + }; + let socket_handle = sockets.add(udp_socket); + let mut counter: u8 = 0; + loop { + time += 10; + delay.block_ms(10); + iface.poll(Instant::from_millis(time), &mut dev, &mut sockets); + if time % 1000 == 0 { + let udp_socket: &mut udp::Socket = sockets.get_mut(socket_handle); + counter = counter.wrapping_add(1); + msg.fill(counter); + match udp_socket.send_slice( + msg, + IpEndpoint { + addr: IpAddress::Ipv4(Ipv4Address::BROADCAST), + port: 5000, + }, + ) { + Ok(()) => defmt::info!("Sent buffer full of counter {}", counter), + Err(err) => { + defmt::warn!("Failed to send counter {}: {:?}", counter, err) + } + } + } + } + } + } + } +} diff --git a/src/chip/imxrt11xx.rs b/src/chip/imxrt11xx.rs index 26371342..130d5711 100644 --- a/src/chip/imxrt11xx.rs +++ b/src/chip/imxrt11xx.rs @@ -5,6 +5,8 @@ pub mod ccm; #[path = "dma.rs"] pub mod dma; +pub mod gpc; +pub mod pmu; pub mod usbphy; cfg_if::cfg_if! { @@ -15,7 +17,7 @@ cfg_if::cfg_if! { } pub(crate) mod reexports { - pub use super::usbphy; + pub use super::{gpc, pmu, usbphy}; } pub(crate) mod iomuxc { diff --git a/src/chip/imxrt11xx/ccm.rs b/src/chip/imxrt11xx/ccm.rs index 75b262e1..e2974216 100644 --- a/src/chip/imxrt11xx/ccm.rs +++ b/src/chip/imxrt11xx/ccm.rs @@ -10,3 +10,68 @@ pub mod clock_gate; pub mod output_source; pub use crate::common::ccm::XTAL_OSCILLATOR_HZ; + +pub use crate::gpc::{ControlMode, Setpoint}; +use crate::ral::{self, ccm::CCM}; + +/// A clock source. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(usize)] +#[non_exhaustive] +pub enum ClockSource { + /// The voltage controlled oscillator for PLL1. + /// + /// This output isn't exposed by the clock tree. + /// However, the [`Pll1Clk`] is exposed. + Pll1 = 21, + /// The PLL1 output into the clock tree. + Pll1Clk = 22, + /// PLL1 with a fixed divide-by-2. + Pll1Div2 = 23, + /// PLL1 with a fixed divide-by-5. + Pll1Div5 = 24, +} + +/// Indicates that the clock does not support setpoint control. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetpointNotImplementedError(()); + +/// Signals if the clock source supports setpoint control. +#[inline] +pub fn has_setpoint(ccm: &CCM, clock_source: ClockSource) -> bool { + let oscpll = &ccm.OSCPLL[clock_source as usize]; + ral::read_reg!( + ral::ccm::oscpll, + oscpll, + OSCPLL_CONFIG, + SETPOINT_PRESENT == 1 + ) +} + +/// Set the GPC setpoint control mode for the clock source. +/// +/// Returns an error if [`has_setpoint`] does not report support +/// for setpoints. +#[inline] +pub fn set_gpc_control_mode( + ccm: &mut CCM, + clock_source: ClockSource, + control_mode: ControlMode, +) -> Result<(), SetpointNotImplementedError> { + if control_mode == ControlMode::Gpc && !has_setpoint(ccm, clock_source) { + return Err(SetpointNotImplementedError(())); + } + + let oscpll = &ccm.OSCPLL[clock_source as usize]; + ral::modify_reg!(ral::ccm::oscpll, oscpll, OSCPLL_AUTHEN, SETPOINT_MODE: control_mode.is_gpc() as u32); + Ok(()) +} + +/// Configure the setpoints for the clock. +/// +/// Note: this does not affect the standby setpoints. +#[inline] +pub fn set_setpoints(ccm: &mut CCM, clock_source: ClockSource, setpoint: Setpoint) { + let oscpll = &ccm.OSCPLL[clock_source as usize]; + ral::modify_reg!(ral::ccm::oscpll, oscpll, OSCPLL_SETPOINT, SETPOINT: setpoint.bits() as u32); +} diff --git a/src/chip/imxrt11xx/ccm/clock_gate.rs b/src/chip/imxrt11xx/ccm/clock_gate.rs index 31009f2c..a33ec4f6 100644 --- a/src/chip/imxrt11xx/ccm/clock_gate.rs +++ b/src/chip/imxrt11xx/ccm/clock_gate.rs @@ -3,6 +3,8 @@ //! This module exposes a similar API as the `clock_gate` API //! for the 10xx MCUs. Consult that module's documentation for //! more information. +//! +//! The API works for clock gates in unassigned mode. use crate::ral::{self, ccm::CCM}; @@ -199,3 +201,33 @@ where // FlexIO2 -> LPCG54 Locator::new(N as usize + 52) } + +/// Returns the ENET. +#[inline] +pub const fn enet() -> Locator { + Locator::new(112) +} + +/// Returns the ENET1G clock gate locator. +#[inline] +pub const fn enet_1g() -> Locator { + Locator::new(113) +} + +/// Returns the ENET QOS clock gate locator. +#[inline] +pub const fn enet_qos() -> Locator { + Locator::new(114) +} + +/// Returns the IOMUXC clock gate locator. +#[inline] +pub const fn iomuxc() -> Locator { + Locator::new(49) +} + +/// Returns the IOMUXC_LPSR clock gate locator. +#[inline] +pub const fn iomuxc_lpsr() -> Locator { + Locator::new(50) +} diff --git a/src/chip/imxrt11xx/gpc.rs b/src/chip/imxrt11xx/gpc.rs new file mode 100644 index 00000000..341a9a58 --- /dev/null +++ b/src/chip/imxrt11xx/gpc.rs @@ -0,0 +1,89 @@ +//! General Power Controller. + +use crate::ral::{self, gpc_cpu_mode_ctrl_::RegisterBlock as GpcCtrl}; + +/// Describes the power / clock control mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum ControlMode { + /// Software, not the GPC, controls the element. + Software, + /// The GPC controls the element. + Gpc, +} + +impl ControlMode { + pub(crate) const fn is_gpc(self) -> bool { + matches!(self, Self::Gpc) + } +} + +bitflags::bitflags! { + /// A bitmask for specifying setpoints. + /// + /// No matter the API, a high bit indicates that the setpoint + /// is "enabled,", "compatible," or "on" for the given configuration. + /// A lower bit indicates that the setpoint is "disabled," "not + /// compatible," or "off" for the configuration. (Some registers have + /// inverted interpretation; nevertheless, the APIs hide this for + /// consistency.) + pub struct Setpoint: u16 { + /// Setpoint 0. + const SP0 = 1 << 0; + /// Setpoint 1. + const SP1 = 1 << 1; + /// Setpoint 2. + const SP2 = 1 << 2; + /// Setpoint 3. + const SP3 = 1 << 3; + /// Setpoint 4. + const SP4 = 1 << 4; + /// Setpoint 5. + const SP5 = 1 << 5; + /// Setpoint 6. + const SP6 = 1 << 6; + /// Setpoint 7. + const SP7 = 1 << 7; + /// Setpoint 8. + const SP8 = 1 << 8; + /// Setpoint 9. + const SP9 = 1 << 9; + /// Setpoint 10. + const SP10 = 1 << 10; + /// Setpoint 11. + const SP11 = 1 << 11; + /// Setpoint 12. + const SP12 = 1 << 12; + /// Setpoint 13. + const SP13 = 1 << 13; + /// Setpoint 14. + const SP14 = 1 << 14; + /// Setpoint 15. + const SP15 = 1 << 15; + } +} + +/// The setpoint is out of range. +/// +/// Setpoints are bound between 0 and 15, inclusive. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetpointOutOfRangeError(pub u32); + +/// Request a run mode setpoint transition. +/// +/// Blocks until the transition completes. +pub fn request_setpoint_transition( + gpc: &GpcCtrl, + setpoint: u32, +) -> Result<(), SetpointOutOfRangeError> { + if setpoint < 16 { + ral::modify_reg!(ral::gpc_cpu_mode_ctrl_, gpc, CM_SP_CTRL, + CPU_SP_RUN: setpoint, + CPU_SP_RUN_EN: 1 + ); + while ral::read_reg!(ral::gpc_cpu_mode_ctrl_, gpc, CM_SP_CTRL, CPU_SP_RUN_EN == 1) {} + Ok(()) + } else { + Err(SetpointOutOfRangeError(setpoint)) + } +} diff --git a/src/chip/imxrt11xx/pmu.rs b/src/chip/imxrt11xx/pmu.rs new file mode 100644 index 00000000..3a200abc --- /dev/null +++ b/src/chip/imxrt11xx/pmu.rs @@ -0,0 +1,45 @@ +//! Power Management Unit. +//! +//! The PMu also provides some control for the PHY LDO. +//! Note that the terms "PHY LDO" and `LDO_PLL` represent +//! the same thing, and this API prefers the PHY LDO term. + +pub use crate::gpc::{ControlMode, Setpoint}; +use crate::ral::{self, anadig_pmu::ANADIG_PMU as PMU}; + +/// Set the control mode for PHY LDO. +#[inline] +pub fn set_phy_ldo_control(pmu: &mut PMU, control_mode: ControlMode) { + ral::modify_reg!(ral::anadig_pmu, pmu, PMU_LDO_PLL, LDO_PLL_CONTROL_MODE: control_mode.is_gpc() as u32); +} + +/// Specify with setpoints should turn on the PHY_LDO. +/// +/// Each high bit indicates that the PHY LDO is on for that setpoint. +/// See [`Setpoint`] documentation for more information. +#[inline] +pub fn set_phy_ldo_setpoints(pmu: &PMU, setpoint: Setpoint) { + // Low bit == on. + // High bit == off. + // + // Flip the bits, since each software setpoint bit + // suggests "on." + ral::write_reg!( + ral::anadig_pmu, + pmu, + LDO_PLL_ENABLE_SP, + (!setpoint).bits() as u32 + ); +} + +/// Enable or disable the PLL bandgap reference voltage. +#[inline] +pub fn enable_pll_reference_voltage(pmu: &mut PMU, enable: bool) { + ral::modify_reg!(ral::anadig_pmu, pmu, PMU_REF_CTRL, EN_PLL_VOL_REF_BUFFER: enable as u32); +} + +/// Set the control mode for the PLL bandgap reference voltage. +#[inline] +pub fn set_pll_reference_control(pmu: &mut PMU, control_mode: ControlMode) { + ral::modify_reg!(ral::anadig_pmu, pmu, PMU_REF_CTRL, REF_CONTROL_MODE: control_mode.is_gpc() as u32); +} diff --git a/src/lib.rs b/src/lib.rs index e9caa0ea..a06ade2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,6 +203,15 @@ pub mod dma { pub use crate::common::dma::*; } +/// Ethernet MAC. +/// +/// When enabled, this module re-exports types from the `imxrt-enet` package. +/// It's compatible with `smoltcp`. +#[cfg(feature = "imxrt-enet")] +pub mod enet { + pub use imxrt_enet::*; +} + /// USB device. /// /// This module re-exports types from the `imxrt-usbd` package. The driver is compatible diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 00000000..7c7de721 --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tools" +version = "0.1.0" +repository.workspace = true +keywords.workspace = true +categories.workspace = true +license.workspace = true +edition.workspace = true +publish = false + +[dependencies] diff --git a/tools/src/bin/tcp_loopback.rs b/tools/src/bin/tcp_loopback.rs new file mode 100644 index 00000000..33ffb8bb --- /dev/null +++ b/tools/src/bin/tcp_loopback.rs @@ -0,0 +1,35 @@ +use std::io::prelude::*; +use std::net::TcpStream; + +fn main() -> Result<(), Box> { + let ip = std::env::args() + .skip(1) + .next() + .ok_or("Provide an IPv4 address")?; + + let ip: std::net::Ipv4Addr = ip.parse()?; + + let mut client = TcpStream::connect(std::net::SocketAddrV4::new(ip, 5000))?; + client.set_nodelay(true)?; + + const MSG: &[u8] = b"Hello, world!"; + client.write(MSG)?; + let mut resp = [0; MSG.len()]; + client.read(&mut resp)?; + + if resp != MSG { + return Err(format!("Expected '{MSG:?}' but received '{resp:?}'").into()); + } + + let mut resp = vec![0; 256]; + for idx in 0..10 { + let msg: Vec = Vec::from_iter((0..=255u8).cycle().skip(idx).take(resp.len())); + client.write(&msg)?; + client.read(&mut resp)?; + if msg != resp { + return Err(format!("Mismatched buffer on {idx} attempt").into()); + } + } + + Ok(()) +}