use log::warn;
use crate::{ChipVersion, Error, Result, Rtl8125};
const OCP_STD_PHY_BASE: u32 = 0xa400;
const EEE_TXIDLE_TIMER_VALUE: u16 = 1500 + 14 + 0x20;
const CSI_PCIE_CTRL: u32 = 0x070c;
const CSI_PCIE_ZRXDC_NONCOMPL: u32 = 1 << 20;
const CSI_L1_ENTRY_LATENCY: u32 = 0x0719;
const CSI_L1_ENTRY_LATENCY_DEFAULT: u8 = 0x27;
impl Rtl8125 {
pub(crate) fn hw_init_8125(&self) -> Result<()> {
self.enable_rxdv_gate();
self.regs.disable_tx_rx();
spin_delay(10_000);
self.regs.clear_now_is_oob();
self.mac_ocp_modify(0xe8de, 1 << 14, 0)?;
self.wait_link_list_ready();
self.mac_ocp_write(0xc0aa, 0x07d0)?;
self.mac_ocp_write(0xc0a6, 0x0150)?;
self.mac_ocp_write(0xc01e, 0x5555)?;
self.wait_link_list_ready();
Ok(())
}
pub(crate) fn hw_start_8125(&mut self) -> Result<()> {
for offset in (0x0a00..0x0b00).step_by(4) {
self.regs.write_vendor_u32(offset, 0);
}
self.set_aspm_clkreq(false)?;
match self.chip {
ChipVersion::Rtl8125A => self.ephy_init(&RTL8125A_EPHY)?,
ChipVersion::Rtl8125B | ChipVersion::Unknown(_) => self.ephy_init(&RTL8125B_EPHY)?,
}
self.hw_start_8125_common()
}
fn hw_start_8125_common(&self) -> Result<()> {
self.regs.clear_ready_to_l23();
self.regs.write_vendor_u16(0x0382, 0x221b);
self.regs.write_vendor_u8(0x4500, 0);
self.regs.write_vendor_u16(0x4800, 0);
self.mac_ocp_modify(0xd40a, 0x0010, 0)?;
self.regs.clear_speed_down();
self.mac_ocp_write(0xc140, 0xffff)?;
self.mac_ocp_write(0xc142, 0xffff)?;
self.mac_ocp_modify(0xd3e2, 0x0fff, 0x03a9)?;
self.mac_ocp_modify(0xd3e4, 0x00ff, 0)?;
self.mac_ocp_modify(0xe860, 0, 0x0080)?;
self.mac_ocp_modify(0xeb58, 0x0001, 0)?;
self.disable_zrxdc_noncompliance_timeout();
self.set_l1_entry_latency(CSI_L1_ENTRY_LATENCY_DEFAULT);
if self.chip == ChipVersion::Rtl8125B {
self.mac_ocp_modify(0xe614, 0x0700, 0x0200)?;
self.mac_ocp_modify(0xe63e, 0x0c30, 0)?;
} else {
self.mac_ocp_modify(0xe614, 0x0700, 0x0400)?;
self.mac_ocp_modify(0xe63e, 0x0c30, 0x0020)?;
}
self.mac_ocp_modify(0xc0b4, 0, 0x000c)?;
self.mac_ocp_modify(0xeb6a, 0x00ff, 0x0033)?;
self.mac_ocp_modify(0xeb50, 0x03e0, 0x0040)?;
self.mac_ocp_modify(0xe056, 0x00f0, 0x0030)?;
self.mac_ocp_modify(0xe040, 0x1000, 0)?;
self.mac_ocp_modify(0xea1c, 0x0003, 0x0001)?;
self.mac_ocp_modify(0xe0c0, 0x4f0f, 0x4403)?;
self.mac_ocp_modify(0xe052, 0x0080, 0x0068)?;
self.mac_ocp_modify(0xd430, 0x0fff, 0x047f)?;
self.mac_ocp_modify(0xea1c, 0x0004, 0)?;
self.mac_ocp_modify(0xeb54, 0, 0x0001)?;
spin_delay(100);
self.mac_ocp_modify(0xeb54, 0x0001, 0)?;
self.regs.clear_vendor_u16_bits(0x1880, 0x0030);
self.mac_ocp_write(0xe098, 0xc302)?;
self.wait_mac_ocp_e00e_low();
self.config_eee_mac()?;
self.disable_rxdv_gate();
Ok(())
}
fn disable_zrxdc_noncompliance_timeout(&self) {
let Some(value) = self.regs.csi_read(CSI_PCIE_CTRL) else {
warn!("RTL8125: failed to read CSI {CSI_PCIE_CTRL:#x}");
return;
};
if !self
.regs
.csi_write(CSI_PCIE_CTRL, value & !CSI_PCIE_ZRXDC_NONCOMPL)
{
warn!("RTL8125: failed to write CSI {CSI_PCIE_CTRL:#x}");
}
}
fn set_l1_entry_latency(&self, value: u8) {
let addr = CSI_L1_ENTRY_LATENCY & !0x3;
let shift = (CSI_L1_ENTRY_LATENCY & 0x3) * 8;
let Some(old) = self.regs.csi_read(addr) else {
warn!("RTL8125: failed to read CSI {addr:#x}");
return;
};
let new = (old & !(0xff << shift)) | (u32::from(value) << shift);
if !self.regs.csi_write(addr, new) {
warn!("RTL8125: failed to write CSI {addr:#x}");
}
}
pub(crate) fn maybe_start_queues(&mut self) {
crate::queue::try_start_queues(self.regs, self.dma.dma_mask(), &self.queue_start);
}
fn enable_rxdv_gate(&self) {
self.regs.enable_rxdv_gate();
spin_delay(2_000);
self.wait_rxtx_empty();
}
fn disable_rxdv_gate(&self) {
self.regs.disable_rxdv_gate();
}
fn wait_rxtx_empty(&self) {
for _ in 0..4_200 {
if self.regs.rxtx_empty() {
return;
}
core::hint::spin_loop();
}
warn!("RTL8125: timed out waiting for RX/TX FIFO empty");
}
fn wait_mac_ocp_e00e_low(&self) {
for _ in 0..10 {
match self.mac_ocp_read(0xe00e) {
Ok(value) if value & (1 << 13) == 0 => return,
Ok(_) => spin_delay(1_000),
Err(err) => {
warn!("RTL8125: failed to read MAC OCP 0xe00e: {err:?}");
return;
}
}
}
warn!("RTL8125: timed out waiting for MAC OCP 0xe00e bit 13 to clear");
}
fn set_aspm_clkreq(&self, enable: bool) -> Result<()> {
if enable {
self.regs.set_aspm_clkreq(true);
self.mac_ocp_modify(0xe094, 0xff00, 0)?;
self.mac_ocp_modify(0xe092, 0x00ff, 1 << 2)?;
} else {
self.mac_ocp_modify(0xe092, 0x00ff, 0)?;
self.regs.set_aspm_clkreq(false);
}
spin_delay(100);
Ok(())
}
fn config_eee_mac(&self) -> Result<()> {
if self.chip == ChipVersion::Rtl8125B {
self.regs.write_eee_txidle_timer(EEE_TXIDLE_TIMER_VALUE);
} else {
self.mac_ocp_modify(0xeb62, 0, (1 << 2) | (1 << 1))?;
}
self.mac_ocp_modify(0xe040, 0, (1 << 1) | 1)
}
pub(crate) fn ack_events(&self, bits: u32) {
self.regs.write_interrupt_status(bits);
}
fn mac_ocp_write(&self, reg: u32, data: u16) -> Result<()> {
validate_ocp_reg(reg)?;
self.regs.start_mac_ocp_write(reg, data);
Ok(())
}
fn mac_ocp_read(&self, reg: u32) -> Result<u16> {
validate_ocp_reg(reg)?;
self.regs.start_mac_ocp_read(reg);
Ok(self.regs.read_mac_ocp_data())
}
fn mac_ocp_modify(&self, reg: u32, mask: u16, set: u16) -> Result<()> {
let data = self.mac_ocp_read(reg)?;
self.mac_ocp_write(reg, (data & !mask) | set)
}
fn ephy_read(&self, reg: u32) -> Result<u16> {
self.regs.start_ephy_read(reg);
for _ in 0..1_000 {
if self.regs.ephy_ready() {
return Ok(self.regs.read_ephy_data());
}
core::hint::spin_loop();
}
Err(Error::HardwareTimeout {
operation: "EPHY read",
})
}
fn ephy_write(&self, reg: u32, value: u16) -> Result<()> {
self.regs.start_ephy_write(reg, value);
for _ in 0..1_000 {
if !self.regs.ephy_ready() {
spin_delay(1_000);
return Ok(());
}
core::hint::spin_loop();
}
Err(Error::HardwareTimeout {
operation: "EPHY write",
})
}
fn ephy_init(&self, entries: &[EphyInfo]) -> Result<()> {
for entry in entries {
let value = (self.ephy_read(entry.offset.into())? & !entry.mask) | entry.bits;
self.ephy_write(entry.offset.into(), value)?;
}
Ok(())
}
fn phy_ocp_write(&self, reg: u32, data: u16) -> Result<()> {
validate_ocp_reg(reg)?;
self.regs.start_phy_ocp_write(reg, data);
for _ in 0..1_000 {
if !self.regs.phy_ocp_busy() {
return Ok(());
}
core::hint::spin_loop();
}
Err(Error::HardwareTimeout {
operation: "PHY OCP write",
})
}
fn phy_ocp_read(&self, reg: u32) -> Result<u16> {
validate_ocp_reg(reg)?;
self.regs.start_phy_ocp_read(reg);
for _ in 0..1_000 {
if self.regs.phy_ocp_busy() {
return Ok(self.regs.read_phy_ocp_data());
}
core::hint::spin_loop();
}
Err(Error::HardwareTimeout {
operation: "PHY OCP read",
})
}
fn phy_reg_addr(&self, reg: u32) -> u32 {
let reg = if self.phy_ocp_base == OCP_STD_PHY_BASE {
reg
} else {
reg.saturating_sub(0x10)
};
self.phy_ocp_base + reg * 2
}
fn phy_write(&mut self, reg: u32, value: u16) -> Result<()> {
if reg == 0x1f {
self.phy_ocp_base = if value == 0 {
OCP_STD_PHY_BASE
} else {
u32::from(value) << 4
};
return Ok(());
}
self.phy_ocp_write(self.phy_reg_addr(reg), value)
}
fn phy_read(&self, reg: u32) -> Result<u16> {
if reg == 0x1f {
return Ok(if self.phy_ocp_base == OCP_STD_PHY_BASE {
0
} else {
(self.phy_ocp_base >> 4) as u16
});
}
self.phy_ocp_read(self.phy_reg_addr(reg))
}
fn phy_modify(&mut self, reg: u32, mask: u16, set: u16) -> Result<()> {
let data = self.phy_read(reg)?;
self.phy_write(reg, (data & !mask) | set)
}
fn phy_write_paged(&mut self, page: u16, reg: u32, value: u16) -> Result<()> {
let old_page = self.phy_read(0x1f)?;
self.phy_write(0x1f, page)?;
self.phy_write(reg, value)?;
self.phy_write(0x1f, old_page)
}
fn phy_modify_paged(&mut self, page: u16, reg: u32, mask: u16, set: u16) -> Result<()> {
let old_page = self.phy_read(0x1f)?;
self.phy_write(0x1f, page)?;
self.phy_modify(reg, mask, set)?;
self.phy_write(0x1f, old_page)
}
fn phy_param(&mut self, param: u16, mask: u16, set: u16) -> Result<()> {
let old_page = self.phy_read(0x1f)?;
self.phy_write(0x1f, 0x0a43)?;
self.phy_write(0x13, param)?;
self.phy_modify(0x14, mask, set)?;
self.phy_write(0x1f, old_page)
}
fn config_eee_phy_8125a(&mut self) -> Result<()> {
self.phy_modify_paged(0x0a43, 0x11, 0, 1 << 4)?;
self.phy_modify_paged(0x0a4a, 0x11, 0, 1 << 9)?;
self.phy_modify_paged(0x0a42, 0x14, 0, 1 << 7)?;
self.phy_modify_paged(0x0a6d, 0x12, 1, 0)?;
self.phy_modify_paged(0x0a6d, 0x14, 1 << 4, 0)
}
fn config_eee_phy_8125b(&mut self) -> Result<()> {
self.phy_modify_paged(0x0a6d, 0x12, 1, 0)?;
self.phy_modify_paged(0x0a6d, 0x14, 1 << 4, 0)?;
self.phy_modify_paged(0x0a42, 0x14, 1 << 7, 0)?;
self.phy_modify_paged(0x0a4a, 0x11, 1 << 9, 0)
}
pub(crate) fn hw_phy_config(&mut self) -> Result<()> {
match self.chip {
ChipVersion::Rtl8125A => self.hw_phy_config_8125a(),
ChipVersion::Rtl8125B | ChipVersion::Unknown(_) => self.hw_phy_config_8125b(),
}
}
fn hw_phy_config_8125a(&mut self) -> Result<()> {
self.phy_modify_paged(0x0ad4, 0x17, 0, 0x0010)?;
self.phy_modify_paged(0x0ad1, 0x13, 0x03ff, 0x03ff)?;
self.phy_modify_paged(0x0ad3, 0x11, 0x003f, 0x0006)?;
self.phy_modify_paged(0x0ac0, 0x14, 0x1100, 0)?;
self.phy_modify_paged(0x0acc, 0x10, 0x0003, 0x0002)?;
self.phy_modify_paged(0x0ad4, 0x10, 0x00e7, 0x0044)?;
self.phy_modify_paged(0x0ac1, 0x12, 0x0080, 0)?;
self.phy_modify_paged(0x0ac8, 0x10, 0x0300, 0)?;
self.phy_modify_paged(0x0ac5, 0x17, 0x0007, 0x0002)?;
self.phy_write_paged(0x0ad4, 0x16, 0x00a8)?;
self.phy_write_paged(0x0ac5, 0x16, 0x01ff)?;
self.phy_modify_paged(0x0ac8, 0x15, 0x00f0, 0x0030)?;
self.phy_write(0x1f, 0x0b87)?;
self.phy_write(0x16, 0x80a2)?;
self.phy_write(0x17, 0x0153)?;
self.phy_write(0x16, 0x809c)?;
self.phy_write(0x17, 0x0153)?;
self.phy_write(0x1f, 0)?;
self.phy_param(0x8257, 0xffff, 0x020f)?;
self.phy_param(0x80ea, 0xffff, 0x7843)?;
self.phy_modify_paged(0x0d06, 0x14, 0, 0x2000)?;
self.phy_param(0x81a2, 0, 0x0100)?;
self.phy_modify_paged(0x0b54, 0x16, 0xff00, 0xdb00)?;
self.phy_modify_paged(0x0a45, 0x12, 0x0001, 0)?;
self.phy_modify_paged(0x0a5d, 0x12, 0, 0x0020)?;
self.phy_modify_paged(0x0ad4, 0x17, 0x0010, 0)?;
self.phy_modify_paged(0x0a86, 0x15, 0x0001, 0)?;
self.phy_modify_paged(0x0a44, 0x11, 0, 1 << 11)?;
self.config_eee_phy_8125a()
}
fn hw_phy_config_8125b(&mut self) -> Result<()> {
self.phy_modify_paged(0x0a44, 0x11, 0, 0x0800)?;
self.phy_modify_paged(0x0ac4, 0x13, 0x00f0, 0x0090)?;
self.phy_modify_paged(0x0ad3, 0x10, 0x0003, 0x0001)?;
self.phy_write(0x1f, 0x0b87)?;
self.phy_write(0x16, 0x80f5)?;
self.phy_write(0x17, 0x760e)?;
self.phy_write(0x16, 0x8107)?;
self.phy_write(0x17, 0x360e)?;
self.phy_write(0x16, 0x8551)?;
self.phy_modify(0x17, 0xff00, 0x0800)?;
self.phy_write(0x1f, 0)?;
self.phy_modify_paged(0x0bf0, 0x10, 0xe000, 0xa000)?;
self.phy_modify_paged(0x0bf4, 0x13, 0x0f00, 0x0300)?;
for param in [
0x8044, 0x804a, 0x8050, 0x8056, 0x805c, 0x8062, 0x8068, 0x806e, 0x8074, 0x807a,
] {
self.phy_param(param, 0xffff, 0x2417)?;
}
self.phy_modify_paged(0x0a4c, 0x15, 0, 0x0040)?;
self.phy_modify_paged(0x0bf8, 0x12, 0xe000, 0xa000)?;
self.phy_modify_paged(0x0a5b, 0x12, 1 << 15, 0)?;
self.config_eee_phy_8125b()
}
fn wait_link_list_ready(&self) {
for _ in 0..4_200 {
if self.regs.link_list_ready() {
return;
}
core::hint::spin_loop();
}
warn!("RTL8125: timed out waiting for link-list FIFO ready");
}
}
fn validate_ocp_reg(reg: u32) -> Result<()> {
if reg & 0xffff_0001 == 0 {
Ok(())
} else {
Err(Error::InvalidOcpAddress { reg })
}
}
fn spin_delay(iterations: usize) {
for _ in 0..iterations {
core::hint::spin_loop();
}
}
#[derive(Clone, Copy)]
struct EphyInfo {
offset: u8,
mask: u16,
bits: u16,
}
const RTL8125A_EPHY: [EphyInfo; 12] = [
EphyInfo {
offset: 0x04,
mask: 0xffff,
bits: 0xd000,
},
EphyInfo {
offset: 0x0a,
mask: 0xffff,
bits: 0x8653,
},
EphyInfo {
offset: 0x23,
mask: 0xffff,
bits: 0xab66,
},
EphyInfo {
offset: 0x20,
mask: 0xffff,
bits: 0x9455,
},
EphyInfo {
offset: 0x21,
mask: 0xffff,
bits: 0x99ff,
},
EphyInfo {
offset: 0x29,
mask: 0xffff,
bits: 0xfe04,
},
EphyInfo {
offset: 0x44,
mask: 0xffff,
bits: 0xd000,
},
EphyInfo {
offset: 0x4a,
mask: 0xffff,
bits: 0x8653,
},
EphyInfo {
offset: 0x63,
mask: 0xffff,
bits: 0xab66,
},
EphyInfo {
offset: 0x60,
mask: 0xffff,
bits: 0x9455,
},
EphyInfo {
offset: 0x61,
mask: 0xffff,
bits: 0x99ff,
},
EphyInfo {
offset: 0x69,
mask: 0xffff,
bits: 0xfe04,
},
];
const RTL8125B_EPHY: [EphyInfo; 6] = [
EphyInfo {
offset: 0x0b,
mask: 0xffff,
bits: 0xa908,
},
EphyInfo {
offset: 0x1e,
mask: 0xffff,
bits: 0x20eb,
},
EphyInfo {
offset: 0x4b,
mask: 0xffff,
bits: 0xa908,
},
EphyInfo {
offset: 0x5e,
mask: 0xffff,
bits: 0x20eb,
},
EphyInfo {
offset: 0x22,
mask: 0x0030,
bits: 0x0020,
},
EphyInfo {
offset: 0x62,
mask: 0x0030,
bits: 0x0020,
},
];