forked from M-Labs/zynq-rs
487 lines
17 KiB
Rust
487 lines
17 KiB
Rust
use libregister::{RegisterR, RegisterW, RegisterRW};
|
|
use log::{debug, info, error};
|
|
use crate::{print, println};
|
|
use super::slcr;
|
|
#[cfg(feature = "target_zc706")]
|
|
use super::slcr::DdriobVrefSel;
|
|
use super::clocks::{Clocks, source::{DdrPll, ClockSource}};
|
|
|
|
mod regs;
|
|
|
|
#[cfg(feature = "target_zc706")]
|
|
/// Micron MT41J256M8HX-15E: 667 MHz DDR3
|
|
const DDR_FREQ: u32 = 666_666_666;
|
|
|
|
#[cfg(feature = "target_coraz7")]
|
|
/// Micron MT41K256M16HA-125: 800 MHz DDR3L, max supported 533 MHz
|
|
const DDR_FREQ: u32 = 525_000_000;
|
|
|
|
#[cfg(feature = "target_redpitaya")]
|
|
/// Alliance Memory AS4C256M16D3B: 800 MHz DDR3 at 533 MHz
|
|
const DDR_FREQ: u32 = 533_333_333;
|
|
|
|
#[cfg(feature = "target_kasli_soc")]
|
|
/// MT41K256M16HA-125:E: 800 MHz DDR3L at 533 MHz
|
|
const DDR_FREQ: u32 = 533_333_333;
|
|
|
|
const DCI_MAX_FREQ: u32 = 10_000_000;
|
|
|
|
pub struct DdrRam {
|
|
regs: &'static mut regs::RegisterBlock,
|
|
}
|
|
|
|
impl DdrRam {
|
|
pub fn ddrram() -> Self {
|
|
let clocks = Self::clock_setup();
|
|
Self::configure_iob();
|
|
Self::calibrate_iob_impedance(&clocks);
|
|
let regs = regs::RegisterBlock::ddrc();
|
|
let mut ddr = DdrRam { regs };
|
|
ddr.reset_ddrc(|ddr| ddr.configure());
|
|
ddr
|
|
}
|
|
|
|
/// Zynq-7000 AP SoC Technical Reference Manual:
|
|
/// 10.6.1 DDR Clock Initialization
|
|
fn clock_setup() -> Clocks {
|
|
DdrPll::setup(2 * DDR_FREQ);
|
|
|
|
let clocks = Clocks::get();
|
|
let ddr3x_clk_divisor = 2;
|
|
let ddr2x_clk_divisor = 3;
|
|
debug!("DDR 3x/2x clocks: {}/{}", clocks.ddr / u32::from(ddr3x_clk_divisor), clocks.ddr / u32::from(ddr2x_clk_divisor));
|
|
|
|
slcr::RegisterBlock::unlocked(|slcr| {
|
|
slcr.ddr_clk_ctrl.write(
|
|
slcr::DdrClkCtrl::zeroed()
|
|
.ddr_2xclkact(true)
|
|
.ddr_3xclkact(true)
|
|
.ddr_2xclk_divisor(ddr2x_clk_divisor)
|
|
.ddr_3xclk_divisor(ddr3x_clk_divisor)
|
|
);
|
|
});
|
|
clocks
|
|
}
|
|
|
|
fn calculate_dci_divisors(clocks: &Clocks) -> (u8, u8) {
|
|
let target = (DCI_MAX_FREQ - 1 + clocks.ddr) / DCI_MAX_FREQ;
|
|
|
|
let mut best = None;
|
|
let mut best_error = 0;
|
|
for divisor0 in 1..63 {
|
|
for divisor1 in 1..63 {
|
|
let current = (divisor0 as u32) * (divisor1 as u32);
|
|
let error = if current > target {
|
|
current - target
|
|
} else {
|
|
target - current
|
|
};
|
|
if best.is_none() || best_error > error {
|
|
best = Some((divisor0, divisor1));
|
|
best_error = error;
|
|
}
|
|
}
|
|
}
|
|
best.unwrap()
|
|
}
|
|
|
|
/// Zynq-7000 AP SoC Technical Reference Manual:
|
|
/// 10.6.2 DDR IOB Impedance Calibration
|
|
fn calibrate_iob_impedance(clocks: &Clocks) {
|
|
let (divisor0, divisor1) = Self::calculate_dci_divisors(clocks);
|
|
debug!("DDR DCI clock: {} Hz (divisors={}*{})",
|
|
clocks.ddr / u32::from(divisor0) / u32::from(divisor1),
|
|
divisor0, divisor1);
|
|
|
|
slcr::RegisterBlock::unlocked(|slcr| {
|
|
// Step 1.
|
|
slcr.dci_clk_ctrl.write(
|
|
slcr::DciClkCtrl::zeroed()
|
|
.clkact(true)
|
|
.divisor0(divisor0)
|
|
.divisor1(divisor1)
|
|
);
|
|
|
|
// Step 2.a.
|
|
slcr.ddriob_dci_ctrl.modify(|_, w|
|
|
w.reset(false)
|
|
);
|
|
slcr.ddriob_dci_ctrl.modify(|_, w|
|
|
w.reset(true)
|
|
);
|
|
// Step 3.b. for DDR3/DDR3L
|
|
slcr.ddriob_dci_ctrl.modify(|_, w|
|
|
w.nref_opt1(0)
|
|
.nref_opt2(0)
|
|
.nref_opt4(1)
|
|
.pref_opt1(0)
|
|
.pref_opt2(0)
|
|
);
|
|
// Step 2.c.
|
|
slcr.ddriob_dci_ctrl.modify(|_, w|
|
|
w.update_control(false)
|
|
);
|
|
// Step 2.d.
|
|
slcr.ddriob_dci_ctrl.modify(|_, w|
|
|
w.enable(true)
|
|
);
|
|
// Step 2.e.
|
|
while ! slcr.ddriob_dci_status.read().done() {}
|
|
});
|
|
}
|
|
|
|
/// Zynq-7000 AP SoC Technical Reference Manual:
|
|
/// 10.6.3 DDR IOB Configuration
|
|
fn configure_iob() {
|
|
slcr::RegisterBlock::unlocked(|slcr| {
|
|
let addr_config = slcr::DdriobConfig::zeroed()
|
|
.output_en(slcr::DdriobOutputEn::Obuf);
|
|
slcr.ddriob_addr0.write(addr_config.clone());
|
|
slcr.ddriob_addr1.write(addr_config);
|
|
|
|
#[cfg(feature = "target_zc706")]
|
|
let data0_config = slcr::DdriobConfig::zeroed()
|
|
.inp_type(slcr::DdriobInputType::VrefDifferential)
|
|
.term_en(true)
|
|
.dci_type(slcr::DdriobDciType::Termination)
|
|
.output_en(slcr::DdriobOutputEn::Obuf);
|
|
#[cfg(feature = "target_zc706")]
|
|
let data1_config = data0_config.clone();
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
let data0_config = slcr::DdriobConfig::zeroed()
|
|
.inp_type(slcr::DdriobInputType::VrefDifferential)
|
|
.term_en(true)
|
|
.dci_type(slcr::DdriobDciType::Termination)
|
|
.output_en(slcr::DdriobOutputEn::Obuf);
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
let data1_config = slcr::DdriobConfig::zeroed()
|
|
.pullup_en(true);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
let data0_config = slcr::DdriobConfig::zeroed()
|
|
.inp_type(slcr::DdriobInputType::VrefDifferential)
|
|
.term_en(true)
|
|
.dci_type(slcr::DdriobDciType::Termination)
|
|
.output_en(slcr::DdriobOutputEn::Obuf);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
let data1_config = slcr::DdriobConfig::zeroed()
|
|
.pullup_en(true);
|
|
slcr.ddriob_data0.write(data0_config);
|
|
slcr.ddriob_data1.write(data1_config);
|
|
|
|
#[cfg(feature = "target_zc706")]
|
|
let diff0_config = slcr::DdriobConfig::zeroed()
|
|
.inp_type(slcr::DdriobInputType::Differential)
|
|
.term_en(true)
|
|
.dci_type(slcr::DdriobDciType::Termination)
|
|
.output_en(slcr::DdriobOutputEn::Obuf);
|
|
#[cfg(feature = "target_zc706")]
|
|
let diff1_config = diff0_config.clone();
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
let diff0_config = slcr::DdriobConfig::zeroed()
|
|
.inp_type(slcr::DdriobInputType::Differential)
|
|
.term_en(true)
|
|
.dci_type(slcr::DdriobDciType::Termination)
|
|
.output_en(slcr::DdriobOutputEn::Obuf);
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
let diff1_config = slcr::DdriobConfig::zeroed()
|
|
.pullup_en(true);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
let diff0_config = slcr::DdriobConfig::zeroed()
|
|
.inp_type(slcr::DdriobInputType::Differential)
|
|
.term_en(true)
|
|
.dci_type(slcr::DdriobDciType::Termination)
|
|
.output_en(slcr::DdriobOutputEn::Obuf);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
let diff1_config = slcr::DdriobConfig::zeroed()
|
|
.pullup_en(true);
|
|
slcr.ddriob_diff0.write(diff0_config);
|
|
slcr.ddriob_diff1.write(diff1_config);
|
|
|
|
slcr.ddriob_clock.write(
|
|
slcr::DdriobConfig::zeroed()
|
|
.output_en(slcr::DdriobOutputEn::Obuf)
|
|
);
|
|
|
|
unsafe {
|
|
// Not documented in Technical Reference Manual
|
|
slcr.ddriob_drive_slew_addr.write(0x0018C61C);
|
|
slcr.ddriob_drive_slew_data.write(0x00F9861C);
|
|
slcr.ddriob_drive_slew_diff.write(0x00F9861C);
|
|
slcr.ddriob_drive_slew_clock.write(0x00F9861C);
|
|
}
|
|
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
slcr.ddriob_ddr_ctrl.modify(|_, w| w
|
|
.vref_int_en(false)
|
|
.vref_ext_en_lower(true)
|
|
.vref_ext_en_upper(false)
|
|
.refio_en(true)
|
|
);
|
|
#[cfg(feature = "target_zc706")]
|
|
slcr.ddriob_ddr_ctrl.modify(|_, w| w
|
|
.vref_int_en(true)
|
|
.vref_sel(DdriobVrefSel::Vref0_75V)
|
|
.vref_ext_en_lower(false)
|
|
.vref_ext_en_upper(false)
|
|
);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
slcr.ddriob_ddr_ctrl.modify(|_, w| w
|
|
.vref_int_en(false)
|
|
.vref_ext_en_lower(true)
|
|
.vref_ext_en_upper(false)
|
|
.refio_en(true)
|
|
);
|
|
});
|
|
}
|
|
|
|
fn configure(&mut self) {
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
self.regs.dram_param0.write(
|
|
regs::DramParam0::zeroed()
|
|
.t_rc(0x1a)
|
|
.t_rfc_min(0x9e)
|
|
.post_selfref_gap_x32(0x10)
|
|
);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
self.regs.dram_param0.write(
|
|
regs::DramParam0::zeroed()
|
|
.t_rc(0x1b)
|
|
.t_rfc_min(0xa0)
|
|
.post_selfref_gap_x32(0x10)
|
|
);
|
|
#[cfg(feature = "target_zc706")]
|
|
self.regs.dram_param0.write(
|
|
regs::DramParam0::zeroed()
|
|
.t_rc(0x1b)
|
|
.t_rfc_min(0x56)
|
|
.post_selfref_gap_x32(0x10)
|
|
);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
self.regs.dram_param1.modify(
|
|
|_, w| w
|
|
.wr2pre(0x12)
|
|
.powerdown_to_x32(6)
|
|
.t_faw(0x16)
|
|
.t_ras_max(0x24)
|
|
.t_ras_min(0x13)
|
|
.t_cke(4)
|
|
);
|
|
|
|
self.regs.dram_param2.write(
|
|
regs::DramParam2::zeroed()
|
|
.write_latency(0x5)
|
|
.rd2wr(0x7)
|
|
.wr2rd(0xe)
|
|
.t_xp(0x4)
|
|
.pad_pd(0x0)
|
|
.rd2pre(0x4)
|
|
.t_rcd(0x7)
|
|
);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
self.regs.dram_param3.modify(
|
|
|_, w| w
|
|
.t_ccd(4)
|
|
.t_rrd(6)
|
|
.refresh_margin(2)
|
|
.t_rp(7)
|
|
.refresh_to_x32(8)
|
|
.mobile(false)
|
|
.dfi_dram_clk_disable(false)
|
|
.read_latency(7)
|
|
.mode_ddr1_ddr2(true)
|
|
.dis_pad_pd(false)
|
|
);
|
|
|
|
self.regs.dram_emr_mr.write(
|
|
regs::DramEmrMr::zeroed()
|
|
.mr(0x930)
|
|
.emr(0x4)
|
|
);
|
|
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
self.regs.phy_configs[2].modify(
|
|
|_, w| w.data_slice_in_use(false)
|
|
);
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
self.regs.phy_configs[3].modify(
|
|
|_, w| w.data_slice_in_use(false)
|
|
);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
self.regs.phy_configs[2].modify(
|
|
|_, w| w.data_slice_in_use(false)
|
|
);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
self.regs.phy_configs[3].modify(
|
|
|_, w| w.data_slice_in_use(false)
|
|
);
|
|
|
|
self.regs.phy_cmd_timeout_rddata_cpt.modify(
|
|
|_, w| w
|
|
.rd_cmd_to_data(0x0)
|
|
.wr_cmd_to_data(0x0)
|
|
.we_to_re_delay(0x8)
|
|
.rdc_fifo_rst_disable(false)
|
|
.use_fixed_re(true)
|
|
.rdc_fifo_rst_err_cnt_clr(false)
|
|
.dis_phy_ctrl_rstn(false)
|
|
.clk_stall_level(false)
|
|
.gatelvl_num_of_dq0(0x7)
|
|
.wrlvl_num_of_dq0(0x7)
|
|
);
|
|
|
|
self.regs.reg_2c.write(
|
|
regs::Reg2C::zeroed()
|
|
.wrlvl_max_x1024(0xfff)
|
|
.rdlvl_max_x1024(0xfff)
|
|
.twrlvl_max_error(false)
|
|
.trdlvl_max_error(false)
|
|
.dfi_wr_level_en(true)
|
|
.dfi_rd_dqs_gate_level(true)
|
|
.dfi_rd_data_eye_train(true)
|
|
);
|
|
|
|
self.regs.dfi_timing.write(
|
|
regs::DfiTiming::zeroed()
|
|
.rddata_en(0x6)
|
|
.ctrlup_min(0x3)
|
|
.ctrlup_max(0x40)
|
|
);
|
|
|
|
#[cfg(feature = "target_zc706")]
|
|
self.regs.phy_init_ratios[3].write(
|
|
regs::PhyInitRatio::zeroed()
|
|
.wrlvl_init_ratio(0x21)
|
|
.gatelvl_init_ratio(0xee)
|
|
);
|
|
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
self.regs.reg_64.modify(
|
|
|_, w| w
|
|
.phy_ctrl_slave_ratio(0x100)
|
|
.phy_invert_clkout(true)
|
|
);
|
|
#[cfg(feature = "target_redpitaya")]
|
|
self.regs.reg_64.modify(
|
|
|_, w| w
|
|
.phy_bl2(false)
|
|
.phy_invert_clkout(true)
|
|
.phy_sel_logic(false)
|
|
.phy_ctrl_slave_ratio(0x100)
|
|
.phy_ctrl_slave_force(false)
|
|
.phy_ctrl_slave_delay(0)
|
|
.phy_lpddr(false)
|
|
.phy_cmd_latency(false)
|
|
);
|
|
|
|
self.regs.reg_65.write(
|
|
regs::Reg65::zeroed()
|
|
.wr_rl_delay(0x2)
|
|
.rd_rl_delay(0x4)
|
|
.dll_lock_diff(0xf)
|
|
.use_wr_level(true)
|
|
.use_rd_dqs_gate_level(true)
|
|
.use_rd_data_eye_level(true)
|
|
.dis_calib_rst(false)
|
|
.ctrl_slave_delay(0x0)
|
|
);
|
|
}
|
|
|
|
/// Reset DDR controller
|
|
fn reset_ddrc<F: FnMut(&mut Self)>(&mut self, mut f: F) {
|
|
#[cfg(feature = "target_zc706")]
|
|
let width = regs::DataBusWidth::Width32bit;
|
|
#[cfg(any(feature = "target_coraz7", feature = "target_kasli_soc"))]
|
|
let width = regs::DataBusWidth::Width16bit;
|
|
#[cfg(feature = "target_redpitaya")]
|
|
let width = regs::DataBusWidth::Width16bit;
|
|
self.regs.ddrc_ctrl.modify(|_, w| w
|
|
.soft_rstb(false)
|
|
.powerdown_en(false)
|
|
.data_bus_width(width)
|
|
);
|
|
f(self);
|
|
|
|
#[cfg(feature = "target_zc706")]
|
|
unsafe {
|
|
// row/column address bits
|
|
self.regs.dram_addr_map_bank.write(0x00000777);
|
|
self.regs.dram_addr_map_col.write(0xFFF00000);
|
|
self.regs.dram_addr_map_row.write(0x0F666666);
|
|
}
|
|
#[cfg(any(
|
|
feature = "target_coraz7",
|
|
feature = "target_redpitaya",
|
|
feature = "target_kasli_soc",
|
|
))]
|
|
unsafe {
|
|
// row/column address bits
|
|
self.regs.dram_addr_map_bank.write(0x00000666);
|
|
self.regs.dram_addr_map_col.write(0xFFFF0000);
|
|
self.regs.dram_addr_map_row.write(0x0F555555);
|
|
}
|
|
|
|
self.regs.ddrc_ctrl.modify(|_, w| w
|
|
.soft_rstb(true)
|
|
.powerdown_en(false)
|
|
.data_bus_width(width)
|
|
);
|
|
|
|
while self.status() == regs::ControllerStatus::Init {}
|
|
}
|
|
|
|
pub fn status(&self) -> regs::ControllerStatus {
|
|
self.regs.mode_sts.read().operating_mode()
|
|
}
|
|
|
|
pub fn ptr<T>(&mut self) -> *mut T {
|
|
0x0010_0000 as *mut _
|
|
}
|
|
|
|
/// actually there's 1 MB more but starting at 0x0000_0000
|
|
/// overlaps with OCM.
|
|
pub fn size(&self) -> usize {
|
|
// DDR range ends at 0x3FFF_FFFF in the default SCU address
|
|
// filtering address map
|
|
#[cfg(feature = "target_zc706")]
|
|
let megabytes = 1023;
|
|
#[cfg(any(
|
|
feature = "target_coraz7",
|
|
feature = "target_redpitaya",
|
|
feature = "target_kasli_soc",
|
|
))]
|
|
let megabytes = 512;
|
|
|
|
megabytes * 1024 * 1024
|
|
}
|
|
|
|
pub fn memtest(&mut self) {
|
|
let slice = unsafe {
|
|
core::slice::from_raw_parts_mut(self.ptr(), self.size())
|
|
};
|
|
let patterns: &'static [u32] = &[0xffff_ffff, 0x5555_5555, 0xaaaa_aaaa, 0];
|
|
let mut expected = None;
|
|
for (i, pattern) in patterns.iter().enumerate() {
|
|
info!("memtest phase {} (status: {:?})", i, self.status());
|
|
|
|
for megabyte in 0..slice.len() / (1024 * 1024) {
|
|
let start = megabyte * 1024 * 1024 / 4;
|
|
let end = (megabyte + 1) * 1024 * 1024 / 4;
|
|
for b in slice[start..end].iter_mut() {
|
|
expected.map(|expected| {
|
|
let read: u32 = *b;
|
|
if read != expected {
|
|
error!("{:08X}: expected {:08X}, read {:08X}", b as *mut _ as usize, expected, read);
|
|
}
|
|
});
|
|
*b = *pattern;
|
|
}
|
|
|
|
print!("\r{} MB", megabyte);
|
|
}
|
|
println!(" Ok");
|
|
|
|
expected = Some(*pattern);
|
|
}
|
|
}
|
|
}
|