zynq7000_hal/ddr/
ll.rs

1//! Low-level DDR configuration module.
2use arbitrary_int::{prelude::*, u2, u3, u6};
3use zynq7000::ddrc::{MmioDdrController, regs::*};
4use zynq7000::slcr::{clocks::DciClockControl, ddriob::DdriobConfig};
5
6use crate::{clocks::DdrClocks, time::Hertz};
7
8const DCI_MAX_FREQ: Hertz = Hertz::from_raw(10_000_000);
9
10// These values were extracted from the ps7_init files and are not documented in the TMR.
11// zynq-rs uses the same values.. I assume they are constant.
12
13pub const DRIVE_SLEW_ADDR_CFG: u32 = 0x0018_c61c;
14pub const DRIVE_SLEW_DATA_CFG: u32 = 0x00f9_861c;
15pub const DRIVE_SLEW_DIFF_CFG: u32 = 0x00f9_861c;
16pub const DRIVE_SLEW_CLOCK_CFG: u32 = 0x00f9_861c;
17
18#[derive(Debug, Clone, Copy)]
19pub struct DciClkConfig {
20    div0: u6,
21    div1: u6,
22}
23
24/// Calculate the required DCI divisors for the given DDR clock.
25pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig {
26    calculate_dci_divisors_with_ddr_clk(ddr_clks.ref_clk())
27}
28
29/// Calculate the required DCI divisors for the given DDR clock frequency.
30pub fn calculate_dci_divisors_with_ddr_clk(ddr_clk: Hertz) -> DciClkConfig {
31    let target_div = ddr_clk.raw().div_ceil(DCI_MAX_FREQ.raw());
32    let mut config = DciClkConfig {
33        div0: u6::new(u6::MAX.value()),
34        div1: u6::new(u6::MAX.value()),
35    };
36
37    let mut best_error = 0;
38    for divisor0 in 1..63 {
39        for divisor1 in 1..63 {
40            let current_div = (divisor0 as u32) * (divisor1 as u32);
41            let error = current_div.abs_diff(target_div);
42            if error < best_error {
43                config.div0 = u6::new(divisor0 as u8);
44                config.div1 = u6::new(divisor1 as u8);
45                best_error = error;
46            }
47        }
48    }
49    config
50}
51
52/// Configure the DCI module by configure its clock divisors and enabling it.
53///
54/// # Safety
55///
56/// This function writes to DCI related registers. It should only be called once during
57/// DDR initialization.
58pub unsafe fn configure_dci(ddr_clk: &DdrClocks) {
59    let cfg = calculate_dci_divisors(ddr_clk);
60    // Safety: Only writes to DCI clock related registers.
61    unsafe {
62        crate::Slcr::with(|slcr| {
63            slcr.clk_ctrl().write_dci_clk_ctrl(
64                DciClockControl::builder()
65                    .with_divisor_1(cfg.div1)
66                    .with_divisor_0(cfg.div0)
67                    .with_clk_act(true)
68                    .build(),
69            );
70        });
71    }
72}
73
74/// Calibrates the IOB impedance for DDR3 memory according to to TRM p.325, DDR IOB Impedance
75/// calibration.
76///
77/// This function will also enable the DCI clock with the provided clock configuration.
78/// You can use [calculate_dci_divisors] to calculate the divisor values for the given DDR clock,
79/// or you can hardcode the values if they are fixed.
80///
81/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so
82/// the user can set other configuration values which are not reliant on DDR operation before
83/// polling for completion.
84///
85/// # Safety
86///
87/// This function writes to the DDR IOB related registers. It should only be called once during
88/// DDR initialization.
89pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_for_done: bool) {
90    unsafe {
91        calibrate_iob_impedance(
92            dci_clk_cfg,
93            u3::new(0),
94            u2::new(0),
95            u3::new(0b001),
96            u3::new(0),
97            u2::new(0),
98            poll_for_done,
99        );
100    }
101}
102
103/// Calibrates the IOB impedance according to to TRM p.325, DDR IOB Impedance calibration.
104///
105/// This function will also enable the DCI clock with the provided clock configuration.
106/// You can use [calculate_dci_divisors] to calculate the divisor values for the given DDR clock,
107/// or you can hardcode the values if they are fixed.
108///
109/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so
110/// the user can set other configuration values which are not reliant on DDR operation before
111/// polling for completion.
112///
113/// # Safety
114///
115/// This function writes to the DDR IOB related registers. It should only be called once during
116/// DDR initialization.
117pub unsafe fn calibrate_iob_impedance(
118    dci_clk_cfg: DciClkConfig,
119    pref_opt2: u3,
120    pref_opt1: u2,
121    nref_opt4: u3,
122    nref_opt2: u3,
123    nref_opt1: u2,
124    poll_for_done: bool,
125) {
126    // Safety: Only writes to DDR IOB related registers.
127    let mut slcr = unsafe { crate::slcr::Slcr::steal() };
128    slcr.modify(|slcr| {
129        slcr.clk_ctrl().write_dci_clk_ctrl(
130            DciClockControl::builder()
131                .with_divisor_1(dci_clk_cfg.div1)
132                .with_divisor_0(dci_clk_cfg.div0)
133                .with_clk_act(true)
134                .build(),
135        );
136        let mut ddriob = slcr.ddriob();
137        ddriob.modify_dci_ctrl(|mut val| {
138            val.set_reset(true);
139            val
140        });
141        ddriob.modify_dci_ctrl(|mut val| {
142            val.set_reset(false);
143            val
144        });
145        ddriob.modify_dci_ctrl(|mut val| {
146            val.set_reset(true);
147            val
148        });
149        ddriob.modify_dci_ctrl(|mut val| {
150            val.set_pref_opt2(pref_opt2);
151            val.set_pref_opt1(pref_opt1);
152            val.set_nref_opt4(nref_opt4);
153            val.set_nref_opt2(nref_opt2);
154            val.set_nref_opt1(nref_opt1);
155            val
156        });
157        ddriob.modify_dci_ctrl(|mut val| {
158            val.set_update_control(false);
159            val
160        });
161        ddriob.modify_dci_ctrl(|mut val| {
162            val.set_enable(true);
163            val
164        });
165        if poll_for_done {
166            while !slcr.ddriob().read_dci_status().done() {
167                // Wait for the DDR IOB impedance calibration to complete.
168                cortex_ar::asm::nop();
169            }
170        }
171    });
172}
173
174/// Static configuration for DDR IOBs.
175pub struct DdriobConfigSet {
176    pub addr0: DdriobConfig,
177    pub addr1: DdriobConfig,
178    pub data0: DdriobConfig,
179    pub data1: DdriobConfig,
180    pub diff0: DdriobConfig,
181    pub diff1: DdriobConfig,
182    pub clock: DdriobConfig,
183}
184
185/// # Safety
186///
187/// This function writes to the IOB related registers. It should only be called once during
188/// DDR initialization.
189pub unsafe fn configure_iob(cfg_set: &DdriobConfigSet) {
190    // Safety: Only configures IOB related registers.
191    let mut slcr = unsafe { crate::slcr::Slcr::steal() };
192    slcr.modify(|slcr| {
193        let mut ddriob = slcr.ddriob();
194        ddriob.write_ddriob_addr0(cfg_set.addr0);
195        ddriob.write_ddriob_addr1(cfg_set.addr1);
196
197        ddriob.write_ddriob_data0(cfg_set.data0);
198        ddriob.write_ddriob_data1(cfg_set.data1);
199
200        ddriob.write_ddriob_diff0(cfg_set.diff0);
201        ddriob.write_ddriob_diff1(cfg_set.diff1);
202
203        ddriob.write_ddriob_clock(cfg_set.clock);
204
205        // These values were extracted from the ps7_init files and are not documented in the TRM.
206        // zynq-rs uses the same values.. I assume they are constant.
207        ddriob.write_ddriob_drive_slew_addr(DRIVE_SLEW_ADDR_CFG);
208        ddriob.write_ddriob_drive_slew_data(DRIVE_SLEW_DATA_CFG);
209        ddriob.write_ddriob_drive_slew_diff(DRIVE_SLEW_DIFF_CFG);
210        ddriob.write_ddriob_drive_slew_clock(DRIVE_SLEW_CLOCK_CFG);
211    });
212}
213
214/// Full static DDRC configuration set.
215#[derive(Debug)]
216pub struct DdrcConfigSet {
217    pub ctrl: DdrcControl,
218    pub two_rank: TwoRankConfig,
219    pub hpr: LprHprQueueControl,
220    pub lpr: LprHprQueueControl,
221    pub wr: WriteQueueControl,
222    pub dram_param_0: DramParamReg0,
223    pub dram_param_1: DramParamReg1,
224    pub dram_param_2: DramParamReg2,
225    pub dram_param_3: DramParamReg3,
226    pub dram_param_4: DramParamReg4,
227    pub dram_init_param: DramInitParam,
228    pub dram_emr: DramEmr,
229    pub dram_emr_mr: DramEmrMr,
230    pub dram_burst8_rdwr: DramBurst8ReadWrite,
231    pub disable_dq: DisableDq,
232    pub dram_addr_map_bank: DramAddrMapBank,
233    pub dram_addr_map_col: DramAddrMapColumn,
234    pub dram_addr_map_row: DramAddrMapRow,
235    pub dram_odt: DramOdt,
236    pub phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt,
237    pub dll_calib: DllCalib,
238    pub odt_delay_hold: OdtDelayHold,
239    pub ctrl_reg1: CtrlReg1,
240    pub ctrl_reg2: CtrlReg2,
241    pub ctrl_reg3: CtrlReg3,
242    pub ctrl_reg4: CtrlReg4,
243    pub ctrl_reg5: CtrlReg5,
244    pub ctrl_reg6: CtrlReg6,
245    pub che_t_zq: CheTZq,
246    pub che_t_zq_short_interval_reg: CheTZqShortInterval,
247    pub deep_powerdown: DeepPowerdown,
248    pub reg_2c: Reg2c,
249    pub reg_2d: Reg2d,
250    pub dfi_timing: DfiTiming,
251    pub che_ecc_ctrl: CheEccControl,
252    pub ecc_scrub: EccScrub,
253    pub phy_receiver_enable: PhyReceiverEnable,
254    pub phy_config: [PhyConfig; 4],
255    pub phy_init_ratio: [PhyInitRatio; 4],
256    pub phy_rd_dqs_config: [PhyDqsConfig; 4],
257    pub phy_wr_dqs_config: [PhyDqsConfig; 4],
258    pub phy_we_cfg: [PhyWriteEnableConfig; 4],
259    pub phy_wr_data_slv: [PhyWriteDataSlaveConfig; 4],
260    pub reg64: Reg64,
261    pub reg65: Reg65,
262    pub page_mask: u32,
263    pub axi_priority_wr_port: [AxiPriorityWritePort; 4],
264    pub axi_priority_rd_port: [AxiPriorityReadPort; 4],
265    pub lpddr_ctrl_0: LpddrControl0,
266    pub lpddr_ctrl_1: LpddrControl1,
267    pub lpddr_ctrl_2: LpddrControl2,
268    pub lpddr_ctrl_3: LpddrControl3,
269}
270
271/// This low-level function sets all the configuration registers.
272///
273/// It does NOT take care of taking the DDR controller out of reset and polling for DDR
274/// configuration completion.
275pub fn configure_ddr_config(ddrc: &mut MmioDdrController<'static>, cfg_set: &DdrcConfigSet) {
276    ddrc.write_ddrc_ctrl(cfg_set.ctrl);
277    // Write all configuration registers.
278    ddrc.write_two_rank_cfg(cfg_set.two_rank);
279    ddrc.write_hpr_queue_ctrl(cfg_set.hpr);
280    ddrc.write_lpr_queue_ctrl(cfg_set.lpr);
281    ddrc.write_wr_reg(cfg_set.wr);
282    ddrc.write_dram_param_reg0(cfg_set.dram_param_0);
283    ddrc.write_dram_param_reg1(cfg_set.dram_param_1);
284    ddrc.write_dram_param_reg2(cfg_set.dram_param_2);
285    ddrc.write_dram_param_reg3(cfg_set.dram_param_3);
286    ddrc.write_dram_param_reg4(cfg_set.dram_param_4);
287    ddrc.write_dram_init_param(cfg_set.dram_init_param);
288    ddrc.write_dram_emr(cfg_set.dram_emr);
289    ddrc.write_dram_emr_mr(cfg_set.dram_emr_mr);
290    ddrc.write_dram_burst8_rdwr(cfg_set.dram_burst8_rdwr);
291    ddrc.write_dram_disable_dq(cfg_set.disable_dq);
292    ddrc.write_phy_cmd_timeout_rddata_cpt(cfg_set.phy_cmd_timeout_rddata_cpt);
293    ddrc.write_dll_calib(cfg_set.dll_calib);
294    ddrc.write_odt_delay_hold(cfg_set.odt_delay_hold);
295    ddrc.write_ctrl_reg1(cfg_set.ctrl_reg1);
296    ddrc.write_ctrl_reg2(cfg_set.ctrl_reg2);
297    ddrc.write_ctrl_reg3(cfg_set.ctrl_reg3);
298    ddrc.write_ctrl_reg4(cfg_set.ctrl_reg4);
299    ddrc.write_ctrl_reg5(cfg_set.ctrl_reg5);
300    ddrc.write_ctrl_reg6(cfg_set.ctrl_reg6);
301    ddrc.write_che_t_zq(cfg_set.che_t_zq);
302    ddrc.write_che_t_zq_short_interval_reg(cfg_set.che_t_zq_short_interval_reg);
303    ddrc.write_deep_powerdown_reg(cfg_set.deep_powerdown);
304    ddrc.write_reg_2c(cfg_set.reg_2c);
305    ddrc.write_reg_2d(cfg_set.reg_2d);
306    ddrc.write_dfi_timing(cfg_set.dfi_timing);
307    ddrc.write_che_ecc_control(cfg_set.che_ecc_ctrl);
308    ddrc.write_ecc_scrub(cfg_set.ecc_scrub);
309    ddrc.write_phy_receiver_enable(cfg_set.phy_receiver_enable);
310    for i in 0..4 {
311        // Safety: Indexes are valid.
312        unsafe {
313            ddrc.write_phy_config_unchecked(i, cfg_set.phy_config[i]);
314            ddrc.write_phy_init_ratio_unchecked(i, cfg_set.phy_init_ratio[i]);
315            ddrc.write_phy_rd_dqs_cfg_unchecked(i, cfg_set.phy_rd_dqs_config[i]);
316            ddrc.write_phy_wr_dqs_cfg_unchecked(i, cfg_set.phy_wr_dqs_config[i]);
317            ddrc.write_phy_we_cfg_unchecked(i, cfg_set.phy_we_cfg[i]);
318            ddrc.write_phy_wr_data_slave_unchecked(i, cfg_set.phy_wr_data_slv[i]);
319        }
320    }
321    ddrc.write_reg_64(cfg_set.reg64);
322    ddrc.write_reg_65(cfg_set.reg65);
323    ddrc.write_page_mask(cfg_set.page_mask);
324    for i in 0..4 {
325        // Safety: Indexes are valid.
326        unsafe {
327            ddrc.write_axi_priority_wr_port_unchecked(i, cfg_set.axi_priority_wr_port[i]);
328            ddrc.write_axi_priority_rd_port_unchecked(i, cfg_set.axi_priority_rd_port[i]);
329        }
330    }
331    ddrc.write_lpddr_ctrl_0(cfg_set.lpddr_ctrl_0);
332    ddrc.write_lpddr_ctrl_1(cfg_set.lpddr_ctrl_1);
333    ddrc.write_lpddr_ctrl_2(cfg_set.lpddr_ctrl_2);
334    ddrc.write_lpddr_ctrl_3(cfg_set.lpddr_ctrl_3);
335}