From f4a083bb4812b243716b602ad4da49c7b3c1ae1e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 3 Nov 2025 11:46:30 +0100 Subject: [PATCH] continue sdio --- zynq/zynq7000-hal/src/eth/ll.rs | 73 +++++++++++--------- zynq/zynq7000-hal/src/eth/mod.rs | 4 +- zynq/zynq7000-hal/src/sdio.rs | 112 ++++++++++++++++++++++++++----- 3 files changed, 140 insertions(+), 49 deletions(-) diff --git a/zynq/zynq7000-hal/src/eth/ll.rs b/zynq/zynq7000-hal/src/eth/ll.rs index 2cc5054..25af4a5 100644 --- a/zynq/zynq7000-hal/src/eth/ll.rs +++ b/zynq/zynq7000-hal/src/eth/ll.rs @@ -8,11 +8,6 @@ use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::He use super::{EthernetId, PsEthernet as _}; -pub struct EthernetLowLevel { - id: EthernetId, - pub regs: zynq7000::eth::MmioRegisters<'static>, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Speed { Mbps10, @@ -177,8 +172,17 @@ impl ClockDivSet { /// Ethernet low-level interface. /// /// Basic building block for higher-level abstraction. +pub struct EthernetLowLevel { + id: EthernetId, + /// Register block. Direct public access is allowed to allow low-level operations. + pub regs: zynq7000::eth::MmioRegisters<'static>, +} + impl EthernetLowLevel { /// Creates a new instance of the Ethernet low-level interface. + /// + /// Returns [None] if the given registers block base address does not correspond to a valid + /// Ethernet peripheral. #[inline] pub fn new(regs: zynq7000::eth::MmioRegisters<'static>) -> Option { regs.id()?; @@ -207,33 +211,7 @@ impl EthernetLowLevel { } pub fn reset(&mut self, cycles: usize) { - let assert_reset = match self.id { - EthernetId::Eth0 => EthernetReset::builder() - .with_gem1_ref_rst(false) - .with_gem0_ref_rst(true) - .with_gem1_rx_rst(false) - .with_gem0_rx_rst(true) - .with_gem1_cpu1x_rst(false) - .with_gem0_cpu1x_rst(true) - .build(), - EthernetId::Eth1 => EthernetReset::builder() - .with_gem1_ref_rst(true) - .with_gem0_ref_rst(false) - .with_gem1_rx_rst(true) - .with_gem0_rx_rst(false) - .with_gem1_cpu1x_rst(true) - .with_gem0_cpu1x_rst(false) - .build(), - }; - unsafe { - Slcr::with(|regs| { - regs.reset_ctrl().write_eth(assert_reset); - for _ in 0..cycles { - aarch32_cpu::asm::nop(); - } - regs.reset_ctrl().write_eth(EthernetReset::DEFAULT); - }); - } + reset(self.id, cycles); } #[inline] @@ -386,3 +364,34 @@ impl EthernetLowLevel { self.id } } + +/// Resets the Ethernet peripheral with the given ID. +pub fn reset(id: EthernetId, cycles: usize) { + let assert_reset = match id { + EthernetId::Eth0 => EthernetReset::builder() + .with_gem1_ref_rst(false) + .with_gem0_ref_rst(true) + .with_gem1_rx_rst(false) + .with_gem0_rx_rst(true) + .with_gem1_cpu1x_rst(false) + .with_gem0_cpu1x_rst(true) + .build(), + EthernetId::Eth1 => EthernetReset::builder() + .with_gem1_ref_rst(true) + .with_gem0_ref_rst(false) + .with_gem1_rx_rst(true) + .with_gem0_rx_rst(false) + .with_gem1_cpu1x_rst(true) + .with_gem0_cpu1x_rst(false) + .build(), + }; + unsafe { + Slcr::with(|regs| { + regs.reset_ctrl().write_eth(assert_reset); + for _ in 0..cycles { + aarch32_cpu::asm::nop(); + } + regs.reset_ctrl().write_eth(EthernetReset::DEFAULT); + }); + } +} diff --git a/zynq/zynq7000-hal/src/eth/mod.rs b/zynq/zynq7000-hal/src/eth/mod.rs index 2197144..f4abc64 100644 --- a/zynq/zynq7000-hal/src/eth/mod.rs +++ b/zynq/zynq7000-hal/src/eth/mod.rs @@ -470,7 +470,7 @@ impl Ethernet { }); }); } - ll.configure_clock(config.clk_config_1000_mbps, true); + ll.configure_peripheral_clock(config.clk_config_1000_mbps, true); let mut mdio = mdio::Mdio::new(&ll, true); mdio.configure_clock_div(config.mdc_clk_div); ll.regs.modify_net_ctrl(|mut val| { @@ -491,7 +491,7 @@ impl Ethernet { pub fn new(mut ll: EthernetLowLevel, config: EthernetConfig) -> Self { Self::common_init(&mut ll, config.mac_address); - ll.configure_clock(config.clk_config_1000_mbps, true); + ll.configure_peripheral_clock(config.clk_config_1000_mbps, true); let mut mdio = mdio::Mdio::new(&ll, true); mdio.configure_clock_div(config.mdc_clk_div); Ethernet { diff --git a/zynq/zynq7000-hal/src/sdio.rs b/zynq/zynq7000-hal/src/sdio.rs index e4422fd..e781aba 100644 --- a/zynq/zynq7000-hal/src/sdio.rs +++ b/zynq/zynq7000-hal/src/sdio.rs @@ -240,9 +240,91 @@ impl SdioClockConfig { }) } } +/// SDIO low-level interface. +/// +/// Basic building block for higher-level abstraction. +pub struct SdioLowLevel { + id: SdioId, + /// Register block. Direct public access is allowed to allow low-level operations. + pub regs: zynq7000::sdio::MmioRegisters<'static>, +} + +impl SdioLowLevel { + /// Create a new SDIO low-level interface from the given register block. + /// + /// Returns [None] if the given registers block base address does not correspond to a valid + /// Ethernet peripheral. + pub fn new(regs: zynq7000::sdio::MmioRegisters<'static>) -> Option { + let id = regs.id()?; + Some(Self { id, regs }) + } + + /// Common SDIO clock configuration routine which should be called once before using the SDIO. + /// + /// This does NOT disable the clock, which should be done before changing the clock + /// configuration. It also does NOT enable the clock. + /// + /// It will configure the SDIO peripheral clock as well as initializing the SD clock frequency + /// divisor based on the initial phase divider specified in the [SdioDivisors] field of the + /// configuration. + pub fn configure_clock(&mut self, clock_config: &SdioClockConfig) { + unsafe { + Slcr::with(|slcr| { + slcr.clk_ctrl().modify_sdio_clk_ctrl(|mut val| { + val.set_srcsel(clock_config.src_sel); + val.set_divisor(clock_config.ref_clock_divisor); + if self.id == SdioId::Sdio1 { + val.set_clk_1_act(true); + } else { + val.set_clk_0_act(true); + } + val + }); + }); + } + self.configure_sd_clock_div_init_phase(&clock_config.sdio_clock_divisors); + } + + /// Configure the SD clock divisor for the initialization phase (400 kHz target clock). + pub fn configure_sd_clock_div_init_phase(&mut self, divs: &SdioDivisors) { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_sdclk_frequency_select(divs.divisor_init_phase); + val + }); + } + + /// Configure the SD clock divisor for the normal phase (regular SDIO speed clock). + pub fn configure_sd_clock_div_normal_phase(&mut self, divs: &SdioDivisors) { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_sdclk_frequency_select(divs.divisor_normal); + val + }); + } + + #[inline] + pub fn enable_clock(&mut self) { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_sd_clock_enable(true); + val + }); + } + + #[inline] + pub fn disable_clock(&mut self) { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_sd_clock_enable(false); + val + }); + } + + /// Reset the SDIO peripheral using the SLCR reset register for SDIO. + pub fn reset(&mut self, cycles: u32) { + reset(self.id, cycles); + } +} pub struct Sdio { - regs: zynq7000::sdio::MmioRegisters<'static>, + ll: SdioLowLevel, } impl Sdio { @@ -265,7 +347,6 @@ impl Sdio { return None; } Some(Self::new( - id, regs, clock_config, clock_pin, @@ -293,7 +374,6 @@ impl Sdio { return None; } Some(Self::new( - id, regs, clock_config, clock_pin, @@ -303,37 +383,39 @@ impl Sdio { } fn new( - id: SdioId, regs: zynq7000::sdio::MmioRegisters<'static>, clock_config: SdioClockConfig, clock_pin: impl MioPin, command_pin: impl MioPin, data_pins: (impl MioPin, impl MioPin, impl MioPin, impl MioPin), ) -> Self { + let mut ll = SdioLowLevel::new(regs).unwrap(); + Self::initialize(&mut ll, &clock_config); IoPeriphPin::new(clock_pin, MUX_CONF, None); IoPeriphPin::new(command_pin, MUX_CONF, None); IoPeriphPin::new(data_pins.0, MUX_CONF, None); IoPeriphPin::new(data_pins.1, MUX_CONF, None); IoPeriphPin::new(data_pins.2, MUX_CONF, None); IoPeriphPin::new(data_pins.3, MUX_CONF, None); - Self { regs } + Self { ll } } - fn initialize( - id: SdioId, - regs: &mut zynq7000::sdio::MmioRegisters<'static>, - clock_config: &SdioClockConfig, - ) { - reset(id, 10); - // TODO: Clock Config - // TODO: There is probably some other configuartion necessary.. the docs really are not + fn initialize(ll: &mut SdioLowLevel, clock_config: &SdioClockConfig) { + ll.reset(10); + // TODO: SW reset for all? + // TODO: Internal clock? + ll.disable_clock(); + ll.configure_clock(clock_config); + ll.enable_clock(); + + // TODO: There is probably some other configuration necessary.. the docs really are not // complete here.. unsafe {} } #[inline] pub fn regs(&mut self) -> &mut zynq7000::sdio::MmioRegisters<'static> { - &mut self.regs + &mut self.ll.regs } } @@ -362,7 +444,7 @@ pub fn reset(id: SdioId, cycles: u32) { regs.reset_ctrl().write_sdio(assert_reset); // Keep it in reset for a few cycle.. not sure if this is necessary. for _ in 0..cycles { - cortex_ar::asm::nop(); + aarch32_cpu::asm::nop(); } regs.reset_ctrl().write_sdio(DualRefAndClockReset::DEFAULT); });