use core::hint::spin_loop;
use crate::{regs, Speed};
const DR_REG_HP_SYS_CLKRST_BASE: usize = 0x500E_6000;
const HP_SYS_CLKRST_SOC_CLK_CTRL1_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x18;
const HP_SYS_CLKRST_REF_CLK_CTRL0_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x24;
const HP_SYS_CLKRST_REF_CLK_CTRL1_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x28;
const HP_SYS_CLKRST_PERI_CLK_CTRL00_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x30;
const HP_SYS_CLKRST_PERI_CLK_CTRL01_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x34;
const DR_REG_LP_CLKRST_BASE: usize = 0x5011_1000;
const LP_CLKRST_HP_CLK_CTRL_REG: usize = DR_REG_LP_CLKRST_BASE + 0x40;
const LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG: usize = DR_REG_LP_CLKRST_BASE + 0x4C;
const HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN: u32 = 1 << 13;
const HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT: u32 = 0;
const HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK: u32 =
0xff << HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT;
const HP_SYS_CLKRST_REG_REF_50M_CLK_EN: u32 = 1 << 27;
const HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN: u32 = 1 << 24;
const HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_SHIFT: u32 = 25;
const HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK: u32 =
0x3 << HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_SHIFT;
const HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN: u32 = 1 << 27;
const HP_SYS_CLKRST_REG_EMAC_RX_CLK_SRC_SEL: u32 = 1 << 28;
const HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN: u32 = 1 << 29;
const HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT: u32 = 0;
const HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK: u32 =
0xff << HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT;
const HP_SYS_CLKRST_REG_EMAC_TX_CLK_SRC_SEL: u32 = 1 << 8;
const HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN: u32 = 1 << 9;
const HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT: u32 = 10;
const HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK: u32 =
0xff << HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT;
const LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN: u32 = 1 << 13;
const LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN: u32 = 1 << 14;
const LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN: u32 = 1 << 15;
const LP_CLKRST_HP_MPLL_500M_CLK_EN: u32 = 1 << 28;
const LP_CLKRST_RST_EN_EMAC: u32 = 1 << 30;
const LP_CLKRST_FORCE_NORST_EMAC: u32 = 1 << 31;
const IO_MUX_GPIO23_REG: usize = 0x500E_1060;
const IO_MUX_GPIO32_REG: usize = 0x500E_1084;
const IO_MUX_GPIO39_REG: usize = 0x500E_10A0;
const IO_MUX_GPIO44_REG: usize = 0x500E_10B4;
const IO_MUX_GPIO50_REG: usize = 0x500E_10CC;
const FUN_IE: u32 = 1 << 9;
const FUN_PU: u32 = 1 << 8;
const FUN_PD: u32 = 1 << 7;
const MCU_SEL_SHIFT: u32 = 12;
const MCU_SEL_MASK: u32 = 0x7 << MCU_SEL_SHIFT;
const REF_50M_CLK_PAD_FUNC: u32 = 3;
const EMAC_RMII_CLK_PAD_FUNC: u32 = 3;
const CLOCK_CONFIG_DELAY_SPINS: usize = 100_000;
const RMII_CLK_SRC_EXTERNAL: u32 = 0;
const REF_50M_CLK_DIV_DEFAULT: u32 = 9;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RefClockPin {
Gpio32,
Gpio44,
Gpio50,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MpllClockOutPin {
Gpio23,
Gpio39,
}
pub fn configure_clock_ext_in(pin: RefClockPin) {
select_rmii_phy_interface();
enable_emac_clock_tree();
configure_ref_clock_pin(pin);
select_external_rmii_clock();
configure_speed_divider(Speed::Mbps100);
}
fn select_rmii_phy_interface() {
const HP_SYSTEM_SYS_GMAC_CTRL0_REG: usize = 0x500E_514C;
const PHY_INTF_SEL_SHIFT: u32 = 2;
const PHY_INTF_SEL_MASK: u32 = 0x7 << PHY_INTF_SEL_SHIFT;
const PHY_INTF_SEL_RMII: u32 = 4;
const GMAC_RST_CLK_TX_N: u32 = 1 << 6;
const GMAC_RST_CLK_RX_N: u32 = 1 << 7;
let value = (regs::read(HP_SYSTEM_SYS_GMAC_CTRL0_REG) & !PHY_INTF_SEL_MASK)
| (PHY_INTF_SEL_RMII << PHY_INTF_SEL_SHIFT)
| GMAC_RST_CLK_TX_N
| GMAC_RST_CLK_RX_N;
regs::write(HP_SYSTEM_SYS_GMAC_CTRL0_REG, value);
}
pub fn disable_emac_clock_tree() {
clear_bits(
HP_SYS_CLKRST_PERI_CLK_CTRL00_REG,
HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN
| HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN
| HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN,
);
clear_bits(
HP_SYS_CLKRST_PERI_CLK_CTRL01_REG,
HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN,
);
clear_bits(
LP_CLKRST_HP_CLK_CTRL_REG,
LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN
| LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN
| LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN,
);
clear_bits(
HP_SYS_CLKRST_SOC_CLK_CTRL1_REG,
HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
);
let mut reset = regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG);
reset |= LP_CLKRST_RST_EN_EMAC;
reset &= !LP_CLKRST_FORCE_NORST_EMAC;
regs::write(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG, reset);
}
pub fn configure_clock_mpll_out(output_pin: MpllClockOutPin, input_pin: RefClockPin) {
select_rmii_phy_interface();
enable_emac_clock_tree();
enable_ref_50m_clock();
configure_ref_50m_output_pin(output_pin);
configure_ref_clock_pin(input_pin);
select_external_rmii_clock();
configure_speed_divider(Speed::Mbps100);
}
pub fn configure_speed_divider(speed: Speed) {
let divider = divider_for_speed(speed);
let peri_clk_ctrl01 = regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG);
let peri_clk_ctrl01 = replace_field(
peri_clk_ctrl01,
HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK,
HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT,
divider,
);
let peri_clk_ctrl01 = replace_field(
peri_clk_ctrl01,
HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK,
HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT,
divider,
);
regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG, peri_clk_ctrl01);
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn emac_clock_tree_enabled() -> bool {
regs::read(HP_SYS_CLKRST_SOC_CLK_CTRL1_REG) & HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN != 0
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn emac_reset_asserted() -> bool {
regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG) & LP_CLKRST_RST_EN_EMAC != 0
}
fn enable_emac_clock_tree() {
set_bits(
HP_SYS_CLKRST_SOC_CLK_CTRL1_REG,
HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
);
set_bits(
HP_SYS_CLKRST_PERI_CLK_CTRL00_REG,
HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN | HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN,
);
set_bits(
HP_SYS_CLKRST_PERI_CLK_CTRL01_REG,
HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN,
);
let hp_clk = (regs::read(LP_CLKRST_HP_CLK_CTRL_REG)
& !(LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN | LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN))
| LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN;
regs::write(LP_CLKRST_HP_CLK_CTRL_REG, hp_clk);
let mut reset = regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG);
reset &= !(LP_CLKRST_RST_EN_EMAC | LP_CLKRST_FORCE_NORST_EMAC);
regs::write(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG, reset);
for _ in 0..CLOCK_CONFIG_DELAY_SPINS {
spin_loop();
}
}
fn configure_ref_clock_pin(pin: RefClockPin) {
let iomux = input_pin_iomux_reg(pin);
let value = (regs::read(iomux) & !(MCU_SEL_MASK | FUN_PU | FUN_PD))
| FUN_IE
| (EMAC_RMII_CLK_PAD_FUNC << MCU_SEL_SHIFT);
regs::write(iomux, value);
}
fn configure_ref_50m_output_pin(pin: MpllClockOutPin) {
let iomux = output_pin_iomux_reg(pin);
let value = (regs::read(iomux) & !(MCU_SEL_MASK | FUN_IE | FUN_PU | FUN_PD))
| (REF_50M_CLK_PAD_FUNC << MCU_SEL_SHIFT);
regs::write(iomux, value);
}
fn enable_ref_50m_clock() {
set_bits(LP_CLKRST_HP_CLK_CTRL_REG, LP_CLKRST_HP_MPLL_500M_CLK_EN);
let ref_clk_ctrl0 = replace_field(
regs::read(HP_SYS_CLKRST_REF_CLK_CTRL0_REG),
HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK,
HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT,
REF_50M_CLK_DIV_DEFAULT,
);
regs::write(HP_SYS_CLKRST_REF_CLK_CTRL0_REG, ref_clk_ctrl0);
set_bits(
HP_SYS_CLKRST_REF_CLK_CTRL1_REG,
HP_SYS_CLKRST_REG_REF_50M_CLK_EN,
);
}
fn select_external_rmii_clock() {
let peri_clk_ctrl00 = regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG);
let peri_clk_ctrl00 = replace_field(
peri_clk_ctrl00,
HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK,
HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_SHIFT,
RMII_CLK_SRC_EXTERNAL,
) & !HP_SYS_CLKRST_REG_EMAC_RX_CLK_SRC_SEL;
regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG, peri_clk_ctrl00);
let peri_clk_ctrl01 =
regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG) & !HP_SYS_CLKRST_REG_EMAC_TX_CLK_SRC_SEL;
regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG, peri_clk_ctrl01);
}
fn divider_for_speed(speed: Speed) -> u32 {
match speed {
Speed::Mbps100 => 1,
Speed::Mbps10 => 10,
}
}
fn input_pin_iomux_reg(pin: RefClockPin) -> usize {
match pin {
RefClockPin::Gpio32 => IO_MUX_GPIO32_REG,
RefClockPin::Gpio44 => IO_MUX_GPIO44_REG,
RefClockPin::Gpio50 => IO_MUX_GPIO50_REG,
}
}
fn output_pin_iomux_reg(pin: MpllClockOutPin) -> usize {
match pin {
MpllClockOutPin::Gpio23 => IO_MUX_GPIO23_REG,
MpllClockOutPin::Gpio39 => IO_MUX_GPIO39_REG,
}
}
fn replace_field(value: u32, mask: u32, shift: u32, field: u32) -> u32 {
(value & !mask) | ((field << shift) & mask)
}
fn set_bits(reg: usize, bits: u32) {
regs::write(reg, regs::read(reg) | bits);
}
fn clear_bits(reg: usize, bits: u32) {
regs::write(reg, regs::read(reg) & !bits);
}
#[cfg(test)]
mod tests {
use super::{
configure_clock_ext_in, configure_clock_mpll_out, configure_speed_divider,
disable_emac_clock_tree, divider_for_speed, input_pin_iomux_reg, output_pin_iomux_reg,
replace_field, MpllClockOutPin, RefClockPin, EMAC_RMII_CLK_PAD_FUNC, FUN_IE, FUN_PD,
FUN_PU, HP_SYS_CLKRST_PERI_CLK_CTRL00_REG, HP_SYS_CLKRST_PERI_CLK_CTRL01_REG,
HP_SYS_CLKRST_REF_CLK_CTRL0_REG, HP_SYS_CLKRST_REF_CLK_CTRL1_REG,
HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN, HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK,
HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK, HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT,
HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN, HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK, HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT,
HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN, HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN,
HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK, HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT,
HP_SYS_CLKRST_REG_REF_50M_CLK_EN, HP_SYS_CLKRST_SOC_CLK_CTRL1_REG, IO_MUX_GPIO23_REG,
IO_MUX_GPIO32_REG, IO_MUX_GPIO39_REG, IO_MUX_GPIO44_REG, IO_MUX_GPIO50_REG,
LP_CLKRST_FORCE_NORST_EMAC, LP_CLKRST_HP_CLK_CTRL_REG, LP_CLKRST_HP_MPLL_500M_CLK_EN,
LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN, LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN,
LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN, LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG,
LP_CLKRST_RST_EN_EMAC, MCU_SEL_MASK, MCU_SEL_SHIFT, REF_50M_CLK_DIV_DEFAULT,
REF_50M_CLK_PAD_FUNC,
};
use crate::{regs, Speed};
#[test]
fn ref_clock_pin_maps_to_expected_iomux_register() {
assert_eq!(input_pin_iomux_reg(RefClockPin::Gpio32), IO_MUX_GPIO32_REG);
assert_eq!(input_pin_iomux_reg(RefClockPin::Gpio44), IO_MUX_GPIO44_REG);
assert_eq!(input_pin_iomux_reg(RefClockPin::Gpio50), IO_MUX_GPIO50_REG);
}
#[test]
fn mpll_output_pin_maps_to_expected_iomux_register() {
assert_eq!(
output_pin_iomux_reg(MpllClockOutPin::Gpio23),
IO_MUX_GPIO23_REG
);
assert_eq!(
output_pin_iomux_reg(MpllClockOutPin::Gpio39),
IO_MUX_GPIO39_REG
);
}
#[test]
fn speed_divider_matches_checklist() {
assert_eq!(divider_for_speed(Speed::Mbps100), 1);
assert_eq!(divider_for_speed(Speed::Mbps10), 10);
}
#[test]
fn configure_speed_divider_updates_rx_and_tx_fields_only() {
regs::reset_test_registers();
regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG, u32::MAX);
configure_speed_divider(Speed::Mbps10);
let value = regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG);
let divider_mask =
HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK | HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK;
assert_eq!(
(value & HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK)
>> HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT,
10
);
assert_eq!(
(value & HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK)
>> HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT,
10
);
assert_eq!(value & !divider_mask, u32::MAX & !divider_mask);
}
#[test]
fn replace_field_overwrites_only_target_bits() {
let value = replace_field(
0xffff_ffff,
MCU_SEL_MASK,
MCU_SEL_SHIFT,
EMAC_RMII_CLK_PAD_FUNC,
);
let expected_mcu_sel = EMAC_RMII_CLK_PAD_FUNC << MCU_SEL_SHIFT;
assert_eq!(value & MCU_SEL_MASK, expected_mcu_sel);
assert_eq!(value & FUN_IE, FUN_IE);
assert_eq!(value & FUN_PU, FUN_PU);
assert_eq!(value & FUN_PD, FUN_PD);
}
#[test]
fn disable_clock_tree_clears_gates_and_asserts_reset() {
regs::reset_test_registers();
configure_clock_ext_in(RefClockPin::Gpio32);
disable_emac_clock_tree();
assert_eq!(
regs::read(HP_SYS_CLKRST_SOC_CLK_CTRL1_REG) & HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
0
);
assert_eq!(
regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG)
& (HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN
| HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN
| HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN),
0
);
assert_eq!(
regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG) & HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN,
0
);
assert_eq!(
regs::read(LP_CLKRST_HP_CLK_CTRL_REG)
& (LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN
| LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN
| LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN),
0
);
assert_ne!(
regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG) & LP_CLKRST_RST_EN_EMAC,
0
);
assert_eq!(
regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG) & LP_CLKRST_FORCE_NORST_EMAC,
0
);
}
#[test]
fn mpll_clock_out_enables_ref_50m_and_loopback_input() {
regs::reset_test_registers();
configure_clock_mpll_out(MpllClockOutPin::Gpio23, RefClockPin::Gpio32);
assert_ne!(
regs::read(LP_CLKRST_HP_CLK_CTRL_REG) & LP_CLKRST_HP_MPLL_500M_CLK_EN,
0
);
assert_ne!(
regs::read(HP_SYS_CLKRST_REF_CLK_CTRL1_REG) & HP_SYS_CLKRST_REG_REF_50M_CLK_EN,
0
);
assert_eq!(
(regs::read(HP_SYS_CLKRST_REF_CLK_CTRL0_REG)
& HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK)
>> HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT,
REF_50M_CLK_DIV_DEFAULT
);
assert_eq!(
regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG)
& HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK,
0
);
assert_eq!(
regs::read(IO_MUX_GPIO23_REG) & MCU_SEL_MASK,
REF_50M_CLK_PAD_FUNC << MCU_SEL_SHIFT
);
assert_eq!(regs::read(IO_MUX_GPIO23_REG) & FUN_IE, 0);
assert_eq!(
regs::read(IO_MUX_GPIO32_REG) & MCU_SEL_MASK,
EMAC_RMII_CLK_PAD_FUNC << MCU_SEL_SHIFT
);
assert_ne!(regs::read(IO_MUX_GPIO32_REG) & FUN_IE, 0);
}
}