use crate::config::{ClkGpio, XtalFreq};
const I2C_APLL: u8 = 0x6D;
const I2C_APLL_HOSTID: u8 = 3;
const ANA_CONF_REG: usize = 0x3FF4_8030;
const ANA_CONF_PLLA_FORCE_PU: u32 = 1 << 24;
const ANA_CONF_PLLA_FORCE_PD: u32 = 1 << 23;
const IO_MUX_BASE: usize = 0x3FF4_9000;
const GPIO_BASE: usize = 0x3FF4_4000;
const GPIO_FUNC_OUT_SEL_BASE: usize = GPIO_BASE + 0x530;
const GPIO_ENABLE_W1TS: usize = GPIO_BASE + 0x024;
const MCU_SEL_MASK: u32 = 0x7 << 12;
const FUN_DRV_MASK: u32 = 0x3 << 10;
const FUN_IE: u32 = 1 << 9;
const APLL_POWER_UP_SPIN: u32 = 10_000;
unsafe extern "C" {
fn rom_i2c_writeReg(block: u8, block_hostid: u8, reg_add: u8, indata: u8);
fn rom_i2c_readReg(block: u8, block_hostid: u8, reg_add: u8) -> u8;
}
#[inline(always)]
fn regi2c_read(reg: u8) -> u8 {
unsafe { rom_i2c_readReg(I2C_APLL, I2C_APLL_HOSTID, reg) }
}
#[inline(always)]
fn regi2c_write(reg: u8, data: u8) {
unsafe { rom_i2c_writeReg(I2C_APLL, I2C_APLL_HOSTID, reg, data) }
}
fn apll_write_mask(reg: u8, msb: u8, lsb: u8, val: u8) {
let old = regi2c_read(reg);
let mask = ((1u16 << (msb - lsb + 1)) - 1) as u8;
let new = (old & !(mask << lsb)) | ((val & mask) << lsb);
regi2c_write(reg, new);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ApllCoefficients {
pub sdm0: u8,
pub sdm1: u8,
pub sdm2: u8,
pub o_div: u8,
}
impl ApllCoefficients {
pub const fn for_xtal(xtal: XtalFreq) -> Self {
match xtal {
XtalFreq::Mhz26 => Self {
sdm0: 118,
sdm1: 98,
sdm2: 11,
o_div: 2,
},
XtalFreq::Mhz32 => Self {
sdm0: 0,
sdm1: 128,
sdm2: 8,
o_div: 2,
},
XtalFreq::Mhz40 => Self {
sdm0: 0,
sdm1: 0,
sdm2: 6,
o_div: 2,
},
}
}
}
pub fn configure_apll_50mhz(xtal: XtalFreq) {
let c = ApllCoefficients::for_xtal(xtal);
unsafe {
let ana = core::ptr::read_volatile(ANA_CONF_REG as *const u32);
core::ptr::write_volatile(
ANA_CONF_REG as *mut u32,
(ana & !ANA_CONF_PLLA_FORCE_PD) | ANA_CONF_PLLA_FORCE_PU,
);
}
for _ in 0..APLL_POWER_UP_SPIN {
core::hint::spin_loop();
}
apll_write_mask(7, 5, 0, c.sdm2);
apll_write_mask(9, 7, 0, c.sdm0);
apll_write_mask(8, 7, 0, c.sdm1);
regi2c_write(5, 0x09);
regi2c_write(5, 0x49);
apll_write_mask(4, 4, 0, c.o_div);
regi2c_write(0, 0x0F);
regi2c_write(0, 0x3F);
regi2c_write(0, 0x1F);
}
pub fn configure_emac_clk_out(gpio: ClkGpio) {
let io_mux_addr = io_mux_addr_for_clk_gpio(gpio);
let gpio_num = gpio.gpio_num() as usize;
unsafe {
let val = core::ptr::read_volatile(io_mux_addr as *const u32);
core::ptr::write_volatile(
io_mux_addr as *mut u32,
(val & !MCU_SEL_MASK & !FUN_DRV_MASK) | (5 << 12) | (3 << 10),
);
core::ptr::write_volatile((GPIO_FUNC_OUT_SEL_BASE + gpio_num * 4) as *mut u32, 256);
core::ptr::write_volatile(GPIO_ENABLE_W1TS as *mut u32, 1u32 << gpio_num);
}
}
pub fn configure_emac_clk_in(gpio: ClkGpio) {
let io_mux_addr = io_mux_addr_for_clk_gpio(gpio);
let gpio_num = gpio.gpio_num() as usize;
unsafe {
let val = core::ptr::read_volatile(io_mux_addr as *const u32);
core::ptr::write_volatile(
io_mux_addr as *mut u32,
(val & !MCU_SEL_MASK) | (5 << 12) | FUN_IE,
);
core::ptr::write_volatile((GPIO_FUNC_OUT_SEL_BASE + gpio_num * 4) as *mut u32, 256);
}
}
const fn io_mux_addr_for_clk_gpio(gpio: ClkGpio) -> usize {
let offset = match gpio {
ClkGpio::Gpio0 => 0x44,
ClkGpio::Gpio16 => 0x4C,
ClkGpio::Gpio17 => 0x50,
};
IO_MUX_BASE + offset
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clk_gpio_io_mux_addresses() {
assert_eq!(
io_mux_addr_for_clk_gpio(ClkGpio::Gpio0),
0x3FF4_9044,
"GPIO0 IO_MUX address mismatch"
);
assert_eq!(
io_mux_addr_for_clk_gpio(ClkGpio::Gpio16),
0x3FF4_904C,
"GPIO16 IO_MUX address mismatch"
);
assert_eq!(
io_mux_addr_for_clk_gpio(ClkGpio::Gpio17),
0x3FF4_9050,
"GPIO17 IO_MUX address mismatch"
);
}
#[test]
fn clk_gpio_numbers_match_enum() {
assert_eq!(ClkGpio::Gpio0.gpio_num(), 0);
assert_eq!(ClkGpio::Gpio16.gpio_num(), 16);
assert_eq!(ClkGpio::Gpio17.gpio_num(), 17);
}
#[test]
fn ana_conf_bits_no_overlap() {
assert_eq!(
ANA_CONF_PLLA_FORCE_PU & ANA_CONF_PLLA_FORCE_PD,
0,
"PU and PD bits must not overlap"
);
}
#[test]
fn ana_conf_bit_positions() {
assert_eq!(ANA_CONF_PLLA_FORCE_PD, 1 << 23);
assert_eq!(ANA_CONF_PLLA_FORCE_PU, 1 << 24);
}
#[test]
fn ana_conf_register_address() {
assert_eq!(ANA_CONF_REG, 0x3FF4_8030);
}
#[test]
fn apll_constants() {
assert_eq!(I2C_APLL, 0x6D);
assert_eq!(I2C_APLL_HOSTID, 3);
}
#[test]
fn io_mux_base_consistent_with_ext_regs() {
assert_eq!(IO_MUX_BASE, crate::regs::ext::IO_MUX_BASE);
}
#[test]
fn gpio_register_layout() {
assert_eq!(GPIO_FUNC_OUT_SEL_BASE, 0x3FF4_4530);
assert_eq!(GPIO_ENABLE_W1TS, 0x3FF4_4024);
}
#[test]
fn mcu_sel_mask_covers_function_5() {
let func5_shifted = 5u32 << 12;
assert_eq!(func5_shifted & MCU_SEL_MASK, func5_shifted);
}
#[test]
fn fun_drv_max_strength() {
let max_drv = 3u32 << 10;
assert_eq!(max_drv & FUN_DRV_MASK, max_drv);
}
fn fout_mhz_q16(c: ApllCoefficients, xtal_mhz: u32) -> u64 {
let num = (xtal_mhz as u64)
* (((c.sdm2 as u64 + 4) << 16) + (c.sdm1 as u64 * 256) + c.sdm0 as u64);
let denom = 2 * (c.o_div as u64 + 2);
num / denom
}
fn assert_50mhz(c: ApllCoefficients, xtal_mhz: u32) {
let q16 = fout_mhz_q16(c, xtal_mhz);
let target_q16 = 50u64 << 16;
let drift = q16.abs_diff(target_q16);
assert!(
drift < 100,
"fout for {} MHz XTAL is {} (Q16) — drift {} from 50 MHz target",
xtal_mhz,
q16,
drift
);
}
#[test]
fn apll_coefficients_xtal_40_lands_on_50mhz() {
assert_50mhz(ApllCoefficients::for_xtal(XtalFreq::Mhz40), 40);
}
#[test]
fn apll_coefficients_xtal_32_lands_on_50mhz() {
assert_50mhz(ApllCoefficients::for_xtal(XtalFreq::Mhz32), 32);
}
#[test]
fn apll_coefficients_xtal_26_lands_on_50mhz() {
assert_50mhz(ApllCoefficients::for_xtal(XtalFreq::Mhz26), 26);
}
#[test]
fn apll_coefficients_register_field_widths() {
for xtal in [XtalFreq::Mhz26, XtalFreq::Mhz32, XtalFreq::Mhz40] {
let c = ApllCoefficients::for_xtal(xtal);
assert!(
c.o_div < 32,
"o_div for {:?} = {} doesn't fit 5 bits",
xtal,
c.o_div
);
assert!(
c.sdm2 < 64,
"sdm2 for {:?} = {} doesn't fit 6 bits",
xtal,
c.sdm2
);
}
}
}