Skip to content

Instantly share code, notes, and snippets.

@luojia65
Created November 6, 2024 08:53
Show Gist options
  • Select an option

  • Save luojia65/861d2960be3e384970094eec72490957 to your computer and use it in GitHub Desktop.

Select an option

Save luojia65/861d2960be3e384970094eec72490957 to your computer and use it in GitHub Desktop.
allwinner mctl init
use allwinner_hal::ccu::{self, ClockConfig, DramClockSource, FactorN};
use allwinner_rt::soc::d1::{CCU, PHY};
use core::ptr::{read_volatile, write_volatile};
// for verbose prints
const VERBOSE: bool = false;
pub const RAM_BASE: usize = 0x40000000;
// p49 ff
// const CCU: usize = 0x0200_1000;
// const PLL_CPU_CTRL: usize = CCU + 0x0000;
// const PLL_DDR_CTRL: usize = CCU + 0x0010;
// const MBUS_CLK: usize = CCU + 0x0540;
// const DRAM_CLK: usize = CCU + 0x0800;
// const DRAM_BGR: usize = CCU + 0x080c;
/**
* D1 manual p152 3.4 System Configuration
*
* SYS_CFG Base Address 0x03000000
*
* | Register Name | Offset | Description |
* | ------------------- | ------ | ---------------------------------------- |
* | DSP_BOOT_RAMMAP_REG | 0x0008 | DSP Boot SRAM Remap Control Register |
* | VER_REG | 0x0024 | Version Register |
* | EMAC_EPHY_CLK_REG0 | 0x0030 | EMAC-EPHY Clock Register 0 |
* | SYS_LDO_CTRL_REG | 0x0150 | System LDO Control Register |
* | RESCAL_CTRL_REG | 0x0160 | Resistor Calibration Control Register |
* | RES240_CTRL_REG | 0x0168 | 240ohms Resistor Manual Control Register |
* | RESCAL_STATUS_REG | 0x016C | Resistor Calibration Status Register |
*/
const SYS_CFG: usize = 0x0300_0000; // 0x0300_0000 - 0x0300_0FFF
// const VER_REG: usize = SYS_CFG + 0x0024;
// const EMAC_EPHY_CLK_REG0: usize = SYS_CFG + 0x0030;
const SYS_LDO_CTRL_REG: usize = SYS_CFG + 0x0150;
const RES_CAL_CTRL_REG: usize = SYS_CFG + 0x0160;
const RES240_CTRL_REG: usize = SYS_CFG + 0x0168;
// const RES_CAL_STATUS_REG: usize = SYS_CFG + 0x016c;
// const ZQ_INTERNAL: usize = SYS_CFG + 0x016e;
const ZQ_VALUE: usize = SYS_CFG + 0x0172;
const BAR_BASE: usize = 0x0700_0000; // TODO: What do we call this?
const SOME_STATUS: usize = BAR_BASE + 0x05d4; // 0x70005d4
const FOO_BASE: usize = 0x0701_0000; // TODO: What do we call this?
const ANALOG_SYS_PWROFF_GATING_REG: usize = FOO_BASE + 0x0254;
const SOME_OTHER: usize = FOO_BASE + 0x0250; // 0x7010250
const SID_INFO: usize = SYS_CFG + 0x2228; // 0x3002228
// p32 memory mapping
// MSI + MEMC: 0x0310_2000 - 0x0330_1fff
// NOTE: MSI shares the bus clock with CE, DMAC, IOMMU and CPU_SYS; p 38
// TODO: Define *_BASE?
const MSI_MEMC_BASE: usize = 0x0310_2000; // p32 0x0310_2000 - 0x0330_1FFF
// PHY config registers; TODO: fix names
const MC_WORK_MODE_RANK0_1: usize = MSI_MEMC_BASE;
const MC_WORK_MODE_RANK0_2: usize = MSI_MEMC_BASE + 0x0004;
const UNKNOWN1: usize = MSI_MEMC_BASE + 0x0008; // 0x3102008
const UNKNOWN7: usize = MSI_MEMC_BASE + 0x000c; // 0x310200c
const UNKNOWN12: usize = MSI_MEMC_BASE + 0x0014; // 0x3102014
const DRAM_MASTER_CTL1: usize = MSI_MEMC_BASE + 0x0020;
const DRAM_MASTER_CTL2: usize = MSI_MEMC_BASE + 0x0024;
const DRAM_MASTER_CTL3: usize = MSI_MEMC_BASE + 0x0028;
// NOTE: From unused function `bit_delay_compensation` in the
// C code; could be for other platforms?
// const UNKNOWN6: usize = MSI_MEMC_BASE + 0x0100; // 0x3102100
// TODO:
// 0x0310_2200
// 0x0310_2210
// 0x0310_2214
// 0x0310_2230
// 0x0310_2234
// 0x0310_2240
// 0x0310_2244
// 0x0310_2260
// 0x0310_2264
// 0x0310_2290
// 0x0310_2294
// 0x0310_2470
// 0x0310_2474
// 0x0310_31c0
// 0x0310_31c8
// 0x0310_31d0
/*
// NOTE: From unused function `bit_delay_compensation` in the
// C code; could be for other platforms?
// DATX0IOCR x + 4 * size
// DATX0IOCR - DATX3IOCR: 11 registers per block, blocks 0x20 words apart
const DATX0IOCR: usize = MSI_MEMC_BASE + 0x0310; // 0x3102310
const DATX3IOCR: usize = MSI_MEMC_BASE + 0x0510; // 0x3102510
*/
const PHY_AC_MAP1: usize = 0x3102500;
const PHY_AC_MAP2: usize = 0x3102504;
const PHY_AC_MAP3: usize = 0x3102508;
const PHY_AC_MAP4: usize = 0x310250c;
// *_BASE?
// const PIR: usize = MSI_MEMC_BASE + 0x1000; // 0x3103000
// const UNKNOWN15: usize = MSI_MEMC_BASE + 0x1004; // 0x3103004
// const MCTL_CLK: usize = MSI_MEMC_BASE + 0x100c; // 0x310300c
// const PGSR0: usize = MSI_MEMC_BASE + 0x1010; // 0x3103010
// const STATR_X: usize = MSI_MEMC_BASE + 0x1018; // 0x3103018
// const DRAM_MR0: usize = MSI_MEMC_BASE + 0x1030; // 0x3103030
// const DRAM_MR1: usize = MSI_MEMC_BASE + 0x1034; // 0x3103034
// const DRAM_MR2: usize = MSI_MEMC_BASE + 0x1038; // 0x3103038
// const DRAM_MR3: usize = MSI_MEMC_BASE + 0x103c; // 0x310303c
// const DRAM_ODTX: usize = MSI_MEMC_BASE + 0x102c; // 0x310302c
// const PTR3: usize = MSI_MEMC_BASE + 0x1050; // 0x3103050;
// const PTR4: usize = MSI_MEMC_BASE + 0x1054; // 0x3103054;
// const DRAMTMG0: usize = MSI_MEMC_BASE + 0x1058; // 0x3103058;
// const DRAMTMG1: usize = MSI_MEMC_BASE + 0x105c; // 0x310305c;
// const DRAMTMG2: usize = MSI_MEMC_BASE + 0x1060; // 0x3103060;
// const DRAMTMG3: usize = MSI_MEMC_BASE + 0x1064; // 0x3103064;
// const DRAMTMG4: usize = MSI_MEMC_BASE + 0x1068; // 0x3103068;
// const DRAMTMG5: usize = MSI_MEMC_BASE + 0x106c; // 0x310306c;
// // const DRAMTMG6: usize = MSI_MEMC_BASE + 0x1070; // 0x3103070;
// // const DRAMTMG7: usize = MSI_MEMC_BASE + 0x1074; // 0x3103074;
// const DRAMTMG8: usize = MSI_MEMC_BASE + 0x1078; // 0x3103078;
// const UNKNOWN8: usize = MSI_MEMC_BASE + 0x107c; // 0x310307c
// const PITMG0: usize = MSI_MEMC_BASE + 0x1080; // 0x3103080;
// const UNKNOWN13: usize = MSI_MEMC_BASE + 0x108c; // 0x310308c;
// const RFSHTMG: usize = MSI_MEMC_BASE + 0x1090; // 0x3103090;
// const RFSHCTL1: usize = MSI_MEMC_BASE + 0x1094; // 0x3103094;
// const UNKNOWN10: usize = MSI_MEMC_BASE + 0x109c; // 0x310309c
// const UNKNOWN11: usize = MSI_MEMC_BASE + 0x10a0; // 0x31030a0
// const UNKNOWN16: usize = MSI_MEMC_BASE + 0x10c0; // 0x31030c0
// const PGCR0: usize = MSI_MEMC_BASE + 0x1100; // 0x3103100
const MRCTRL0: usize = MSI_MEMC_BASE + 0x1108; // 0x3103108
// const UNKNOWN14: usize = MSI_MEMC_BASE + 0x110c; // 0x310310c
// const IOCVR0: usize = MSI_MEMC_BASE + 0x1110; // 0x3103110
// const IOCVR1: usize = MSI_MEMC_BASE + 0x1114; // 0x3103114
// const DQS_GATING_X: usize = MSI_MEMC_BASE + 0x111c; // 0x310311c
// const UNKNOWN9: usize = MSI_MEMC_BASE + 0x1120; // 0x3103120
// const ZQ_CFG: usize = MSI_MEMC_BASE + 0x1140; // 0x3103140
// const UNDOC1: usize = MSI_MEMC_BASE + 0x1208; // 0x3103208;
// const DAT00IOCR: usize = MSI_MEMC_BASE + 0x1310; // 0x3103310
// const DX0GCR0: usize = MSI_MEMC_BASE + 0x1344; // 0x3103344
// const UNKNOWN4: usize = MSI_MEMC_BASE + 0x1348; // 0x3103348
// const DX1GCR0: usize = MSI_MEMC_BASE + 0x13c4; // 0x31033c4;
// const DAT01IOCR: usize = MSI_MEMC_BASE + 0x1390; // 0x3103390
// const UNKNOWN5: usize = MSI_MEMC_BASE + 0x13c8; // 0x31033C8
// const DAT03IOCR: usize = MSI_MEMC_BASE + 0x1510; // 0x3103510
// TODO: *_BASE ?
const MC_WORK_MODE_RANK1_1: usize = MSI_MEMC_BASE + 0x10_0000;
const MC_WORK_MODE_RANK1_2: usize = MSI_MEMC_BASE + 0x10_0004;
/// Type of DDR SDRAM.
#[repr(u32)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum DramType {
/// Double-Data-Rate Two (DDR2) Synchronous Dynamic Random Access Memory.
Ddr2 = 2,
/// Double-Data-Rate Three (DDR3) Synchronous Dynamic Random Access Memory.
Ddr3 = 3,
/// Low-Power Double-Data-Rate 2 (LPDDR2).
Lpddr2 = 6,
/// Low-Power Double-Data-Rate 3 (LPDDR3).
Lpddr3 = 7,
}
#[repr(C)]
pub struct dram_parameters {
pub dram_clk: u32,
pub dram_type: DramType,
pub dram_zq: u32,
pub dram_odt_en: u32,
pub dram_para1: u32,
pub dram_para2: u32,
pub dram_mr0: u32,
pub dram_mr1: u32,
pub dram_mr2: u32,
pub dram_mr3: u32,
pub dram_tpr0: u32,
pub dram_tpr1: u32,
pub dram_tpr2: u32,
pub dram_tpr3: u32,
pub dram_tpr4: u32,
pub dram_tpr5: u32,
pub dram_tpr6: u32,
pub dram_tpr7: u32,
pub dram_tpr8: u32,
/// overrided_dram_clk = para.dram_tpr9
pub dram_tpr9: u32,
pub dram_tpr10: u32,
pub dram_tpr11: u32,
pub dram_tpr12: u32,
/// should_override = para.dram_tpr13 & (1 << 6) != 0;
/// dqs_gating_mode = (para.dram_tpr13 >> 2) & 0x3;
/// should_auto_scan_dram_config = (para.dram_tpr13 & 0x1) == 0;
pub dram_tpr13: u32,
}
// FIXME: This could be a concise struct. Let Rust piece it together.
/*
//dram_tpr0
tccd : [23:21]
tfaw : [20:15]
trrd : [14:11]
trcd : [10:6 ]
trc : [ 5:0 ]
//dram_tpr1
txp : [27:23]
twtr : [22:20]
trtp : [19:15]
twr : [14:11]
trp : [10:6 ]
tras : [ 5:0 ]
//dram_tpr2
trfc : [20:12]
trefi: [11:0 ]
*/
fn readl(reg: usize) -> u32 {
unsafe { read_volatile(reg as *mut u32) }
}
fn writel(reg: usize, val: u32) {
unsafe {
write_volatile(reg as *mut u32, val);
}
}
fn sdelay(micros: usize) {
let millis = micros * 1000;
unsafe {
for _ in 0..millis {
core::arch::asm!("nop")
}
}
}
fn get_pmu_exists() -> bool {
return false;
}
const PHY_CFG1: [u32; 22] = [
1, 9, 3, 7, 8, 18, 4, 13, 5, 6, 10, 2, 14, 12, 0, 0, 21, 17, 20, 19, 11, 22,
];
const PHY_CFG2: [u32; 22] = [
4, 9, 3, 7, 8, 18, 1, 13, 2, 6, 10, 5, 14, 12, 0, 0, 21, 17, 20, 19, 11, 22,
];
const PHY_CFG3: [u32; 22] = [
1, 7, 8, 12, 10, 18, 4, 13, 5, 6, 3, 2, 9, 0, 0, 0, 21, 17, 20, 19, 11, 22,
];
const PHY_CFG4: [u32; 22] = [
4, 12, 10, 7, 8, 18, 1, 13, 2, 6, 3, 5, 9, 0, 0, 0, 21, 17, 20, 19, 11, 22,
];
const PHY_CFG5: [u32; 22] = [
13, 2, 7, 9, 12, 19, 5, 1, 6, 3, 4, 8, 10, 0, 0, 0, 21, 22, 18, 17, 11, 20,
];
const PHY_CFG6: [u32; 22] = [
3, 10, 7, 13, 9, 11, 1, 2, 4, 6, 8, 5, 12, 0, 0, 0, 20, 1, 0, 21, 22, 17,
];
const PHY_CFG7: [u32; 22] = [
3, 2, 4, 7, 9, 1, 17, 12, 18, 14, 13, 8, 15, 6, 10, 5, 19, 22, 16, 21, 20, 11,
];
// TODO: verify
// This routine seems to have several remapping tables for 22 lines.
// It is unclear which lines are being remapped. It seems to pick
// table PHY_CFG7 for the Nezha board.
unsafe fn mctl_phy_ac_remapping(para: &mut dram_parameters) {
// read SID info @ 0x228
let fuse = (readl(SID_INFO) >> 8) & 0x4;
// // println!("ddr_efuse_type: 0x{:x}", fuse);
let mut phy_cfg0 = [0; 22];
if (para.dram_tpr13 >> 18) & 0x3 > 0 {
// // println!("phy cfg 7");
phy_cfg0 = PHY_CFG7;
} else {
match fuse {
8 => phy_cfg0 = PHY_CFG2,
9 => phy_cfg0 = PHY_CFG3,
10 => phy_cfg0 = PHY_CFG5,
11 => phy_cfg0 = PHY_CFG4,
13 | 14 => {}
12 | _ => phy_cfg0 = PHY_CFG1,
}
}
if para.dram_type == DramType::Ddr2 {
if fuse == 15 {
return;
}
phy_cfg0 = PHY_CFG6;
}
if para.dram_type == DramType::Ddr2 || para.dram_type == DramType::Ddr3 {
let val = (phy_cfg0[4] << 25)
| (phy_cfg0[3] << 20)
| (phy_cfg0[2] << 15)
| (phy_cfg0[1] << 10)
| (phy_cfg0[0] << 5);
writel(PHY_AC_MAP1, val as u32);
let val = (phy_cfg0[10] << 25)
| (phy_cfg0[9] << 20)
| (phy_cfg0[8] << 15)
| (phy_cfg0[7] << 10)
| (phy_cfg0[6] << 5)
| phy_cfg0[5];
writel(PHY_AC_MAP2, val as u32);
let val = (phy_cfg0[15] << 20)
| (phy_cfg0[14] << 15)
| (phy_cfg0[13] << 10)
| (phy_cfg0[12] << 5)
| phy_cfg0[11];
writel(PHY_AC_MAP3, val as u32);
let val = (phy_cfg0[21] << 25)
| (phy_cfg0[20] << 20)
| (phy_cfg0[19] << 15)
| (phy_cfg0[18] << 10)
| (phy_cfg0[17] << 5)
| phy_cfg0[16];
writel(PHY_AC_MAP4, val as u32);
let val = (phy_cfg0[4] << 25)
| (phy_cfg0[3] << 20)
| (phy_cfg0[2] << 15)
| (phy_cfg0[1] << 10)
| (phy_cfg0[0] << 5)
| 1;
writel(PHY_AC_MAP1, val as u32);
}
}
fn dram_vol_set(dram_para: &mut dram_parameters) {
let vol = match dram_para.dram_type {
DramType::Ddr2 => 47, // 1.8V
DramType::Ddr3 => 25, // 1.5V
_ => 0,
};
let mut reg = readl(SYS_LDO_CTRL_REG);
reg &= !(0xff00);
reg |= vol << 8;
reg &= !(0x200000);
writel(SYS_LDO_CTRL_REG, reg);
sdelay(1);
}
fn set_ddr_voltage(val: usize) -> usize {
val
}
fn handler_super_standby() {}
fn dram_enable_all_master() {
writel(DRAM_MASTER_CTL1, 0xffffffff);
writel(DRAM_MASTER_CTL2, 0xff);
writel(DRAM_MASTER_CTL3, 0xffff);
sdelay(10);
}
fn dram_disable_all_master() {
writel(DRAM_MASTER_CTL1, 1);
writel(DRAM_MASTER_CTL2, 0);
writel(DRAM_MASTER_CTL3, 0);
sdelay(10);
}
// Main purpose of sys_init seems to be to initalise the clocks for
// the sdram controller.
// TODO: verify this
fn mctl_sys_init(
should_override: bool,
overrided_dram_clk: u32,
dram_clk: &mut u32,
ccu: &CCU,
phy: &PHY,
) {
let origin_dram_clk = *dram_clk;
let clk = match should_override {
true => overrided_dram_clk,
false => origin_dram_clk,
};
let (m0, m1) = (2, 1);
let n = clk * m0 * m1 / 24;
*dram_clk = n * 24 / (m0 * m1);
unsafe {
ccu::DRAM::reconfigure_with(ccu, ccu::MBUS, |ccu| {
sdelay(5);
let mut val = ccu
.pll_ddr_control
.read()
.mask_pll_output()
.set_pll_n((n - 1) as u8)
.set_pll_m1((m1 - 1) as u8)
.set_pll_m0((m0 - 1) as u8)
.enable_pll()
.enable_pll_ldo();
unsafe { ccu.pll_ddr_control.write(val) };
// Restart PLL locking
val = val.disable_lock();
unsafe { ccu.pll_ddr_control.write(val) };
val = val.enable_lock();
unsafe { ccu.pll_ddr_control.write(val) };
// Wait for PLL to lock
while !ccu.pll_ddr_control.read().is_locked() {
core::hint::spin_loop();
}
// fixme: should we delay 20 us?
// Enable PLL output
unsafe { ccu.pll_ddr_control.modify(|val| val.unmask_pll_output()) };
// return the configure parameters
(DramClockSource::PllDdr, 0, FactorN::N1)
}, |ccu| {
ccu.dram_clock.modify(|v| v.unmask_clock());
sdelay(100);
dram_disable_all_master();
})
};
// turn dram clock gate on, trigger sdr clock update
unsafe {
// TODO: trigger SDR clock update (bit 27)
ccu.dram_clock.modify(|val| {
core::mem::transmute(core::mem::transmute::<_, u32>(val.unmask_clock()) | (0x1 << 27))
});
}
sdelay(5);
// mCTL clock enable
unsafe { phy.clken.write(0x00008000) };
sdelay(10);
}
// Set the Vref mode for the controller
fn mctl_vrefzq_init(para: &mut dram_parameters, phy: &PHY) {
if (para.dram_tpr13 & (1 << 17)) == 0 {
unsafe {
phy.iovcr0.modify(|val| {
let val = val & 0x80808080;
val | para.dram_tpr5 as u32
});
}
if (para.dram_tpr13 & (1 << 16)) == 0 {
unsafe {
phy.iovcr1.modify(|val| {
let val = val & 0xffffff80;
val | para.dram_tpr6 as u32 & 0x7f
});
}
}
}
}
// The main purpose of this routine seems to be to copy an address configuration
// from the dram_para1 and dram_para2 fields to the PHY configuration registers
// (0x3102000, 0x3102004).
fn mctl_com_init(para: &mut dram_parameters, phy: &PHY) {
// purpose ??
let mut val = readl(UNKNOWN1) & 0xffffc0ff;
val |= 0x2000;
writel(UNKNOWN1, val);
// Set sdram type and word width
let mut val = readl(MC_WORK_MODE_RANK0_1) & 0xff000fff;
val |= ((para.dram_type as u32) & 0x7) << 16; // DRAM type
val |= (!para.dram_para2 & 0x1) << 12; // DQ width
if para.dram_type != DramType::Lpddr2 && para.dram_type != DramType::Lpddr3 {
val |= ((para.dram_tpr13 >> 5) & 0x1) << 19; // 2T or 1T
val |= 0x400000;
} else {
val |= 0x480000; // type 6 and 7 must use 1T
}
writel(MC_WORK_MODE_RANK0_1, val);
// init rank / bank / row for single/dual or two different ranks
let val = para.dram_para2;
// ((val & 0x100) && (((val >> 12) & 0xf) != 1)) ? 32 : 16;
let rank = if (val & 0x100) != 0 && (val >> 12) & 0xf != 1 {
2
} else {
1
};
for i in 0..rank {
let ptr = MC_WORK_MODE_RANK0_1 + i * 4;
let mut val = readl(ptr) & 0xfffff000;
val |= (para.dram_para2 >> 12) & 0x3; // rank
val |= ((para.dram_para1 >> (i * 16 + 12)) << 2) & 0x4; // bank - 2
val |= (((para.dram_para1 >> (i * 16 + 4)) - 1) << 4) & 0xff; // row - 1
// convert from page size to column addr width - 3
val |= match (para.dram_para1 >> i * 16) & 0xf {
8 => 0xa00,
4 => 0x900,
2 => 0x800,
1 => 0x700,
_ => 0x600,
};
writel(ptr, val);
}
// set ODTMAP based on number of ranks in use
let val = match readl(MC_WORK_MODE_RANK0_1) & 0x1 {
0 => 0x201,
_ => 0x303,
};
unsafe { phy.odtmap.write(val) };
// set mctl reg 3c4 to zero when using half DQ
if para.dram_para2 & (1 << 0) > 0 {
unsafe { phy.datx[1].gcr.write(0) };
}
// purpose ??
if para.dram_tpr4 > 0 {
let mut val = readl(MC_WORK_MODE_RANK0_1);
val |= (para.dram_tpr4 << 25) & 0x06000000;
writel(MC_WORK_MODE_RANK0_1, val);
let mut val = readl(MC_WORK_MODE_RANK0_2);
val |= ((para.dram_tpr4 >> 2) << 12) & 0x001ff000;
writel(MC_WORK_MODE_RANK0_2, val);
}
}
fn auto_cal_timing(time: u32, freq: u32) -> u32 {
let t = time * freq;
let what = if (t % 1000) != 0 { 1 } else { 0 };
(t / 1000) + what
}
// Main purpose of the auto_set_timing routine seems to be to calculate all
// timing settings for the specific type of sdram used. Read together with
// an sdram datasheet for context on the various variables.
fn auto_set_timing_para(para: &mut dram_parameters, phy: &PHY) {
let dfreq = para.dram_clk;
let dtype = para.dram_type;
let tpr13 = para.dram_tpr13;
//// println!("type = {}\n", dtype);
//// println!("tpr13 = {}\n", tpr13);
// FIXME: Half of this is unused, wat?!
let mut tccd: u32 = 0; // 88(sp)
let mut trrd: u32 = 0; // s7
let mut trcd: u32 = 0; // s3
let mut trc: u32 = 0; // s9
let mut tfaw: u32 = 0; // s10
let mut tras: u32 = 0; // s11
let mut trp: u32 = 0; // 0(sp)
let mut twtr: u32 = 0; // s1
let mut twr: u32 = 0; // s6
let mut trtp: u32 = 0; // 64(sp)
let mut txp: u32 = 0; // a6
let mut trefi: u32 = 0; // s2
let mut trfc: u32 = 0; // a5 / 8(sp)
if para.dram_tpr13 & 0x2 != 0 {
//dram_tpr0
tccd = (para.dram_tpr0 >> 21) & 0x7; // [23:21]
tfaw = (para.dram_tpr0 >> 15) & 0x3f; // [20:15]
trrd = (para.dram_tpr0 >> 11) & 0xf; // [14:11]
trcd = (para.dram_tpr0 >> 6) & 0x1f; // [10:6 ]
trc = (para.dram_tpr0 >> 0) & 0x3f; // [ 5:0 ]
//dram_tpr1
txp = (para.dram_tpr1 >> 23) & 0x1f; // [27:23]
twtr = (para.dram_tpr1 >> 20) & 0x7; // [22:20]
trtp = (para.dram_tpr1 >> 15) & 0x1f; // [19:15]
twr = (para.dram_tpr1 >> 11) & 0xf; // [14:11]
trp = (para.dram_tpr1 >> 6) & 0x1f; // [10:6 ]
tras = (para.dram_tpr1 >> 0) & 0x3f; // [ 5:0 ]
//dram_tpr2
trfc = (para.dram_tpr2 >> 12) & 0x1ff; // [20:12]
trefi = (para.dram_tpr2 >> 0) & 0xfff; // [11:0 ]
} else {
let frq2 = dfreq >> 1; // s0
match dtype {
DramType::Ddr3 => {
// DDR3
trfc = auto_cal_timing(350, frq2);
trefi = auto_cal_timing(7800, frq2) / 32 + 1; // XXX
twr = auto_cal_timing(8, frq2);
twtr = if twr < 2 { 2 } else { twr + 2 }; // + 2 ? XXX
trcd = auto_cal_timing(15, frq2);
twr = if trcd < 2 { 2 } else { trcd };
if dfreq <= 800 {
tfaw = auto_cal_timing(50, frq2);
let trrdc = auto_cal_timing(10, frq2);
trrd = if trrd < 2 { 2 } else { trrdc };
trc = auto_cal_timing(53, frq2);
tras = auto_cal_timing(38, frq2);
txp = trrd; // 10
trp = trcd; // 15
}
}
DramType::Ddr2 => {} // TODO
DramType::Lpddr2 => {} // TODO
DramType::Lpddr3 => {} // TODO
/*
2 => {
// DDR2
tfaw = auto_cal_timing(50, frq2);
trrd = auto_cal_timing(10, frq2);
trcd = auto_cal_timing(20, frq2);
trc = auto_cal_timing(65, frq2);
twtr = auto_cal_timing(8, frq2);
trp = auto_cal_timing(15, frq2);
tras = auto_cal_timing(45, frq2);
trefi = auto_cal_timing(7800, frq2) / 32;
trfc = auto_cal_timing(328, frq2);
txp = 2;
twr = trp; // 15
}
6 => {
// LPDDR2
tfaw = auto_cal_timing(50, frq2);
if tfaw < 4 {
tfaw = 4
};
trrd = auto_cal_timing(10, frq2);
if trrd == 0 {
trrd = 1
};
trcd = auto_cal_timing(24, frq2);
if trcd < 2 {
trcd = 2
};
trc = auto_cal_timing(70, frq2);
txp = auto_cal_timing(8, frq2);
if txp == 0 {
txp = 1;
twtr = 2;
} else {
twtr = txp;
if txp < 2 {
txp = 2;
twtr = 2;
}
}
twr = auto_cal_timing(15, frq2);
if twr < 2 {
twr = 2
};
trp = auto_cal_timing(17, frq2);
tras = auto_cal_timing(42, frq2);
trefi = auto_cal_timing(3900, frq2) / 32;
trfc = auto_cal_timing(210, frq2);
}
7 => {
// LPDDR3
tfaw = auto_cal_timing(50, frq2);
if tfaw < 4 {
tfaw = 4
};
trrd = auto_cal_timing(10, frq2);
if trrd == 0 {
trrd = 1
};
trcd = auto_cal_timing(24, frq2);
if trcd < 2 {
trcd = 2
};
trc = auto_cal_timing(70, frq2);
twtr = auto_cal_timing(8, frq2);
if twtr < 2 {
twtr = 2
};
twr = auto_cal_timing(15, frq2);
if twr < 2 {
twr = 2
};
trp = auto_cal_timing(17, frq2);
tras = auto_cal_timing(42, frq2);
trefi = auto_cal_timing(3900, frq2) / 32;
trfc = auto_cal_timing(210, frq2);
txp = twtr;
}
_ => {
// default
trfc = 128;
trp = 6;
trefi = 98;
txp = 10;
twr = 8;
twtr = 3;
tras = 14;
tfaw = 16;
trc = 20;
trcd = 6;
trrd = 3;
}
*/
}
//assign the value back to the DRAM structure
tccd = 2;
trtp = 4; // not in .S ?
para.dram_tpr0 = (trc << 0) | (trcd << 6) | (trrd << 11) | (tfaw << 15) | (tccd << 21);
para.dram_tpr1 =
(tras << 0) | (trp << 6) | (twr << 11) | (trtp << 15) | (twtr << 20) | (txp << 23);
para.dram_tpr2 = (trefi << 0) | (trfc << 12);
}
let tcksrx: u32; // t1
let tckesr: u32; // t4;
let mut trd2wr: u32; // t6
let trasmax: u32; // t3;
let twtp: u32; // s6 (was twr!)
let tcke: u32; // s8
let tmod: u32; // t0
let tmrd: u32; // t5
let tmrw: u32; // a1
let t_rdata_en: u32; // a4 (was tcwl!)
let tcl: u32; // a0
let wr_latency: u32; // a7
let tcwl: u32; // first a4, then a5
let mr3: u32; // s0
let mr2: u32; // t2
let mr1: u32; // s1
let mr0: u32; // a3
//let dmr3: u32; // 72(sp)
//let trtp:u32; // 64(sp)
//let dmr1: u32; // 56(sp)
let twr2rd: u32; // 48(sp)
let tdinit3: u32; // 40(sp)
let tdinit2: u32; // 32(sp)
let tdinit1: u32; // 24(sp)
let tdinit0: u32; // 16(sp)
let dmr1 = para.dram_mr1;
let dmr3 = para.dram_mr3;
match dtype {
/*
2 =>
// DDR2
// L59:
{
trasmax = dfreq / 30;
if dfreq < 409 {
tcl = 3;
t_rdata_en = 1;
mr0 = 0x06a3;
} else {
t_rdata_en = 2;
tcl = 4;
mr0 = 0x0e73;
}
tmrd = 2;
twtp = twr as u32 + 5;
tcksrx = 5;
tckesr = 4;
trd2wr = 4;
tcke = 3;
tmod = 12;
wr_latency = 1;
mr3 = 0;
mr2 = 0;
tdinit0 = 200 * dfreq + 1;
tdinit1 = 100 * dfreq / 1000 + 1;
tdinit2 = 200 * dfreq + 1;
tdinit3 = 1 * dfreq + 1;
tmrw = 0;
twr2rd = twtr as u32 + 5;
tcwl = 0;
mr1 = dmr1;
}
*/
// DramType::Ddr2 => {}, // TODO
DramType::Ddr3 =>
// DDR3
// L57:
{
trasmax = dfreq / 30;
if dfreq <= 800 {
mr0 = 0x1c70;
tcl = 6;
wr_latency = 2;
tcwl = 4;
mr2 = 24;
} else {
mr0 = 0x1e14;
tcl = 7;
wr_latency = 3;
tcwl = 5;
mr2 = 32;
}
twtp = tcwl + 2 + twtr as u32; // WL+BL/2+tWTR
trd2wr = tcwl + 2 + twr as u32; // WL+BL/2+tWR
twr2rd = tcwl + twtr as u32; // WL+tWTR
tdinit0 = 500 * dfreq + 1; // 500 us
tdinit1 = 360 * dfreq / 1000 + 1; // 360 ns
tdinit2 = 200 * dfreq + 1; // 200 us
tdinit3 = 1 * dfreq + 1; // 1 us
mr1 = dmr1;
t_rdata_en = tcwl; // a5 <- a4
tcksrx = 5;
tckesr = 4;
trd2wr = if ((tpr13 >> 2) & 0x03) == 0x01 || dfreq < 912 {
5
} else {
6
};
tcke = 3; // not in .S ?
tmod = 12;
tmrd = 4;
tmrw = 0;
mr3 = 0;
}
// DramType::Lpddr2 => {} // TODO
/*
6 =>
// LPDDR2
// L61:
{
trasmax = dfreq / 60;
mr3 = dmr3;
twtp = twr as u32 + 5;
mr2 = 6;
// mr1 = 5; // TODO: this is just overwritten (?!)
tcksrx = 5;
tckesr = 5;
trd2wr = 10;
tcke = 2;
tmod = 5;
tmrd = 5;
tmrw = 3;
tcl = 4;
wr_latency = 1;
t_rdata_en = 1;
tdinit0 = 200 * dfreq + 1;
tdinit1 = 100 * dfreq / 1000 + 1;
tdinit2 = 11 * dfreq + 1;
tdinit3 = 1 * dfreq + 1;
twr2rd = twtr as u32 + 5;
tcwl = 2;
mr1 = 195;
mr0 = 0;
}
7 =>
// LPDDR3
{
trasmax = dfreq / 60;
if dfreq < 800 {
tcwl = 4;
wr_latency = 3;
t_rdata_en = 6;
mr2 = 12;
} else {
tcwl = 3;
// tcke = 6; // FIXME: This is always overwritten
wr_latency = 2;
t_rdata_en = 5;
mr2 = 10;
}
twtp = tcwl + 5;
tcl = 7;
mr3 = dmr3;
tcksrx = 5;
tckesr = 5;
trd2wr = 13;
tcke = 3;
tmod = 12;
tdinit0 = 400 * dfreq + 1;
tdinit1 = 500 * dfreq / 1000 + 1;
tdinit2 = 11 * dfreq + 1;
tdinit3 = 1 * dfreq + 1;
tmrd = 5;
tmrw = 5;
twr2rd = tcwl + twtr as u32 + 5;
mr1 = 195;
mr0 = 0;
}
_ => {}
*/
// DramType::Lpddr3 => {} // TODO
_ =>
// L84:
{
twr2rd = 8; // 48(sp)
tcksrx = 4; // t1
tckesr = 3; // t4
trd2wr = 4; // t6
trasmax = 27; // t3
twtp = 12; // s6
tcke = 2; // s8
tmod = 6; // t0
tmrd = 2; // t5
tmrw = 0; // a1
tcwl = 3; // a5
tcl = 3; // a0
wr_latency = 1; // a7
t_rdata_en = 1; // a4
mr3 = 0; // s0
mr2 = 0; // t2
mr1 = 0; // s1
mr0 = 0; // a3
tdinit3 = 0; // 40(sp)
tdinit2 = 0; // 32(sp)
tdinit1 = 0; // 24(sp)
tdinit0 = 0; // 16(sp)
}
}
// L60:
/*
if trtp < tcl - trp + 2 {
trtp = tcl - trp + 2;
}
*/
// FIXME: This always overwrites the above (?!)
trtp = 4;
// Update mode block when permitted
if (para.dram_mr0 & 0xffff0000) == 0 {
para.dram_mr0 = mr0
};
if (para.dram_mr1 & 0xffff0000) == 0 {
para.dram_mr1 = mr1
};
if (para.dram_mr2 & 0xffff0000) == 0 {
para.dram_mr2 = mr2
};
if (para.dram_mr3 & 0xffff0000) == 0 {
para.dram_mr3 = mr3
};
// Set mode registers
unsafe {
phy.mr[0].write(para.dram_mr0);
phy.mr[1].write(para.dram_mr1);
phy.mr[2].write(para.dram_mr2);
phy.mr[3].write(para.dram_mr3);
// should phy.lp3mr11 be named DRAM_ODTX?
phy.lp3mr11.write((para.dram_odt_en >> 4) & 0x3); // ??
}
// Set dram timing DRAMTMG0 - DRAMTMG5
unsafe {
phy.dramtmg[0]
.write((twtp << 24) | (tfaw << 16) as u32 | (trasmax << 8) | (tras << 0) as u32);
phy.dramtmg[1].write((txp << 16) as u32 | (trtp << 8) as u32 | (trc << 0) as u32);
phy.dramtmg[2].write((tcwl << 24) | (tcl << 16) as u32 | (trd2wr << 8) | (twr2rd << 0));
phy.dramtmg[3].write((tmrw << 16) | (tmrd << 12) | (tmod << 0));
phy.dramtmg[4].write(
(trcd << 24) as u32 | (tccd << 16) as u32 | (trrd << 8) as u32 | (trp << 0) as u32,
);
phy.dramtmg[5].write((tcksrx << 24) | (tcksrx << 16) | (tckesr << 8) | (tcke << 0));
}
// Set two rank timing
unsafe {
phy.dramtmg[8].modify(|mut val| {
val &= 0x0fff0000;
val |= if para.dram_clk < 800 {
0xf0006600
} else {
0xf0007600
};
val |= 0x10;
val
})
};
// Set phy interface time PITMG0, PTR3, PTR4
unsafe {
phy.pitmg[0].write((0x2 << 24) | (t_rdata_en << 16) | (0x1 << 8) | (wr_latency << 0));
phy.ptr[3].write((tdinit0 << 0) | (tdinit1 << 20));
phy.ptr[4].write((tdinit2 << 0) | (tdinit3 << 20));
}
// Set refresh timing and mode
unsafe {
phy.rfshtmg.write((trefi << 16) | (trfc << 0));
phy.rfshctl1.write(0x0fff0000 & (trefi << 15));
}
}
fn eye_delay_compensation(para: &mut dram_parameters, phy: &PHY) {
let mut val: u32;
// DATn0IOCR
for i in 0..9 {
unsafe {
phy.datx[0].iocr[i].modify(|mut val| {
val |= (para.dram_tpr11 << 9) & 0x1e00;
val |= (para.dram_tpr12 << 1) & 0x001e;
val
});
}
}
// DATn1IOCR
for i in 0..9 {
unsafe {
phy.datx[1].iocr[i].modify(|mut val| {
val |= ((para.dram_tpr11 >> 4) << 9) & 0x1e00;
val |= ((para.dram_tpr12 >> 4) << 1) & 0x001e;
val
});
}
}
// PGCR0: assert AC loopback FIFO reset
unsafe { phy.pgcr[0].modify(|val| val & 0xfbffffff) };
// ??
val = readl(0x3103334);
val |= ((para.dram_tpr11 >> 16) << 9) & 0x1e00;
val |= ((para.dram_tpr12 >> 16) << 1) & 0x001e;
writel(0x3103334, val);
val = readl(0x3103338);
val |= ((para.dram_tpr11 >> 16) << 9) & 0x1e00;
val |= ((para.dram_tpr12 >> 16) << 1) & 0x001e;
writel(0x3103338, val);
val = readl(0x31033b4);
val |= ((para.dram_tpr11 >> 20) << 9) & 0x1e00;
val |= ((para.dram_tpr12 >> 20) << 1) & 0x001e;
writel(0x31033b4, val);
val = readl(0x31033b8);
val |= ((para.dram_tpr11 >> 20) << 9) & 0x1e00;
val |= ((para.dram_tpr12 >> 20) << 1) & 0x001e;
writel(0x31033b8, val);
val = readl(0x310333c);
val |= ((para.dram_tpr11 >> 16) << 25) & 0x1e000000;
writel(0x310333c, val);
val = readl(0x31033bc);
val |= ((para.dram_tpr11 >> 20) << 25) & 0x1e000000;
writel(0x31033bc, val);
// PGCR0: release AC loopback FIFO reset
unsafe { phy.pgcr[0].modify(|val| val | 0x04000000) };
sdelay(1);
// TODO: unknown regs
// NOTE: dram_tpr10 is set to 0x0 for D1
for i in 0..15 {
let ptr = 0x3103240 + i * 4;
val = readl(ptr);
val |= ((para.dram_tpr10 >> 4) << 8) & 0x0f00;
writel(ptr, val);
}
for i in 0..6 {
let ptr = 0x3103228 + i * 4;
val = readl(ptr);
val |= ((para.dram_tpr10 >> 4) << 8) & 0x0f00;
writel(ptr, val);
}
let val = readl(0x3103218);
writel(0x3103218, val | (para.dram_tpr10 << 8) & 0x0f00);
let val = readl(0x310321c);
writel(0x310321c, val | (para.dram_tpr10 << 8) & 0x0f00);
let val = readl(0x3103280);
writel(0x3103280, val | ((para.dram_tpr10 >> 12) << 8) & 0x0f00);
}
// Init the controller channel. The key part is placing commands in the main
// command register (PIR, 0x3103000) and checking command status (PGSR0, 0x3103010).
fn mctl_channel_init(para: &mut dram_parameters, phy: &PHY) -> Result<(), &'static str> {
let dqs_gating_mode = (para.dram_tpr13 >> 2) & 0x3;
let mut val;
// set DDR clock to half of CPU clock
val = readl(UNKNOWN7) & 0xfffff000;
val |= (para.dram_clk >> 1) - 1;
writel(UNKNOWN7, val);
// MRCTRL0 nibble 3 undocumented
val = readl(MRCTRL0) & 0xfffff0ff;
writel(MRCTRL0, val | 0x300);
unsafe {
// DX0GCR0
phy.datx[0].gcr.modify(|val| {
let mut val = val & 0xffffffcf;
val |= ((!para.dram_odt_en) << 5) & 0x20;
if para.dram_clk > 672 {
val &= 0xffff09f1;
val |= 0x00000400;
} else {
val &= 0xffff0ff1;
}
val
});
// DX1GCR0
phy.datx[1].gcr.modify(|val| {
let mut val = val & 0xffffffcf;
val |= ((!para.dram_odt_en) << 5) & 0x20;
if para.dram_clk > 672 {
val &= 0xffff09f1;
val |= 0x00000400;
} else {
val &= 0xffff0ff1;
}
val
});
}
// 0x3103208 undocumented
unsafe {
phy.aciocr0.modify(|val| val | 0x2);
}
eye_delay_compensation(para, &phy);
//set PLL SSCG ?
val = readl(MRCTRL0);
const PLL_SSCG_X: usize = 0x31030bc;
match dqs_gating_mode {
1 => {
val &= !(0xc0); // FIXME
writel(MRCTRL0, val);
let val = readl(PLL_SSCG_X);
writel(PLL_SSCG_X, val & 0xfffffef8);
}
2 => {
val &= !(0xc0); // FIXME
val |= 0x80;
writel(MRCTRL0, val);
let mut val = readl(PLL_SSCG_X);
val &= 0xfffffef8;
val |= ((para.dram_tpr13 >> 16) & 0x1f) - 2;
val |= 0x100;
writel(PLL_SSCG_X, val);
unsafe {
phy.dxccr.modify(|val| (val & 0x7fffffff) | 0x08000000);
}
}
_ => {
val &= !(0x40); // FIXME
writel(MRCTRL0, val);
sdelay(10);
let val = readl(MRCTRL0);
writel(MRCTRL0, val | 0xc0);
}
}
/*
if para.dram_type == 6 || para.dram_type == 7 {
let val = readl(DQS_GATING_X);
if dqs_gating_mode == 1 {
val &= 0xf7ffff3f;
val |= 0x80000000;
} else {
val &= 0x88ffffff;
val |= 0x22000000;
}
writel(DQS_GATING_X, val);
}
*/
unsafe {
phy.dtcr.modify(|mut val| {
val &= 0xf0000000;
val |= if para.dram_para2 & (1 << 12) > 0 {
0x03000001
} else {
0x01000007
}; // 0x01003087 XXX
val
});
}
if readl(SOME_STATUS) & (1 << 16) > 0 {
val = readl(SOME_OTHER);
writel(SOME_OTHER, val & 0xfffffffd);
sdelay(10);
}
// Set ZQ config
unsafe {
phy.zqcr.modify(|mut val| {
val = val & 0xfc000000;
val |= para.dram_zq & 0x00ffffff;
val |= 0x02000000;
val
});
}
// Initialise DRAM controller
val = if dqs_gating_mode == 1 {
unsafe { phy.pir.write(0x52) }; // prep PHY reset + PLL init + z-cal
unsafe { phy.pir.write(0x53) }; // Go
while phy.pgsr[0].read() & 0x1 == 0 {} // wait for IDONE
sdelay(10);
// 0x520 = prep DQS gating + DRAM init + d-cal
if para.dram_type == DramType::Ddr3 {
0x5a0
}
// + DRAM reset
else {
0x520
}
} else {
if (readl(SOME_STATUS) & (1 << 16)) == 0 {
// prep DRAM init + PHY reset + d-cal + PLL init + z-cal
if para.dram_type == DramType::Ddr3 {
0x1f2
}
// + DRAM reset
else {
0x172
}
} else {
// prep PHY reset + d-cal + z-cal
0x62
}
};
unsafe { phy.pir.write(val) }; // Prep
unsafe { phy.pir.write(val | 1) }; // Go
sdelay(10);
while (phy.pgsr[0].read() & 0x1) == 0 {} // wait for IDONE
if readl(SOME_STATUS) & (1 << 16) > 0 {
unsafe {
phy.pgcr[3].modify(|mut val| {
val &= 0xf9ffffff;
val |= 0x04000000;
val
})
};
sdelay(10);
unsafe { phy.pwrctl.modify(|val| val | 0x1) };
while (phy.statr.read() & 0x7) != 0x3 {}
val = readl(SOME_OTHER);
writel(SOME_OTHER, val & 0xfffffffe);
sdelay(10);
unsafe { phy.pwrctl.modify(|val| val & 0xfffffffe) };
while (phy.statr.read() & 0x7) != 0x1 {}
sdelay(15);
if dqs_gating_mode == 1 {
val = readl(MRCTRL0);
val &= 0xffffff3f;
writel(MRCTRL0, val);
unsafe {
phy.pgcr[3].modify(|mut val| {
val &= 0xf9ffffff;
val |= 0x02000000;
val
})
};
sdelay(1);
unsafe { phy.pir.write(0x401) };
while (phy.pgsr[0].read() & 0x1) == 0 {}
}
}
// Check for training error
val = phy.pgsr[0].read();
if ((val >> 20) & 0xff != 0) && (val & 0x100000) != 0 {
// return Err("DRAM initialisation error : 0"); // TODO
return Err("ZQ calibration error, check external 240 ohm resistor.");
}
// STATR = Zynq STAT? Wait for status 'normal'?
while (phy.statr.read() & 0x1) == 0 {}
unsafe { phy.rfshctl0.modify(|val| val | 0x80000000) };
sdelay(10);
unsafe { phy.rfshctl0.modify(|val| val & 0x7fffffff) };
sdelay(10);
val = readl(UNKNOWN12);
writel(UNKNOWN12, val | 0x80000000);
sdelay(10);
unsafe {
phy.pgcr[3].modify(|val| val & 0xf9ffffff);
}
if dqs_gating_mode == 1 {
unsafe { phy.dxccr.modify(|val| (val & 0xffffff3f) | 0x00000040) };
}
Ok(())
}
// FIXME: Cannot you see that this could be more elegant?
// Perform an init of the controller. This is actually done 3 times. The first
// time to establish the number of ranks and DQ width. The second time to
// establish the actual ram size. The third time is final one, with the final
// settings.
fn mctl_core_init(para: &mut dram_parameters, ccu: &CCU, phy: &PHY) -> Result<(), &'static str> {
let should_override = para.dram_tpr13 & (1 << 6) != 0;
let overrided_dram_clk = para.dram_tpr9;
mctl_sys_init(
should_override,
overrided_dram_clk,
&mut para.dram_clk,
&ccu,
&phy,
);
mctl_vrefzq_init(para, &phy);
mctl_com_init(para, &phy);
unsafe {
mctl_phy_ac_remapping(para);
}
auto_set_timing_para(para, &phy);
mctl_channel_init(para, &phy)
}
// The below routine reads the dram config registers and extracts
// the number of address bits in each rank available. It then calculates
// total memory size in MB.
fn dramc_get_dram_size() -> u32 {
// MC_WORK_MODE0 (not MC_WORK_MODE, low word)
let low = readl(MC_WORK_MODE_RANK0_1);
let mut temp = (low >> 8) & 0xf; // page size - 3
temp += (low >> 4) & 0xf; // row width - 1
temp += (low >> 2) & 0x3; // bank count - 2
temp -= 14; // 1MB = 20 bits, minus above 6 = 14
let size0 = 1 << temp;
// // println!("low {} size0 {}", low, size0);
temp = low & 0x3; // rank count = 0? -> done
if temp == 0 {
return size0;
}
// MC_WORK_MODE1 (not MC_WORK_MODE, high word)
let high = readl(MC_WORK_MODE_RANK0_2);
temp = high & 0x3;
if temp == 0 {
// two identical ranks
return 2 * size0;
}
temp = (high >> 8) & 0xf; // page size - 3
temp += (high >> 4) & 0xf; // row width - 1
temp += (high >> 2) & 0x3; // bank number - 2
temp -= 14; // 1MB = 20 bits, minus above 6 = 14
let size1 = 1 << temp;
// // println!("high {} size1 {}", high, size1);
return size0 + size1; // add size of each rank
}
// The below routine reads the command status register to extract
// DQ width and rank count. This follows the DQS training command in
// channel_init. If error bit 22 is reset, we have two ranks and full DQ.
// If there was an error, figure out whether it was half DQ, single rank,
// or both. Set bit 12 and 0 in dram_para2 with the results.
fn dqs_gate_detect(para: &mut dram_parameters, phy: &PHY) -> Result<&'static str, &'static str> {
if phy.pgsr[0].read() & (1 << 22) != 0 {
let dx0 = (phy.datx[0].gsr0.read() >> 24) & 0x3;
let dx1 = (phy.datx[1].gsr0.read() >> 24) & 0x3;
if dx0 == 2 {
let mut rval = para.dram_para2;
rval &= 0xffff0ff0;
if dx0 != dx1 {
rval |= 0x1;
para.dram_para2 = rval;
return Ok("[AUTO DEBUG] single rank and half DQ!");
}
para.dram_para2 = rval;
// NOTE: D1 should do this here
return Ok("single rank, full DQ");
} else if dx0 == 0 {
let mut rval = para.dram_para2;
rval &= 0xfffffff0; // l 7920
rval |= 0x00001001; // l 7918
para.dram_para2 = rval;
return Ok("dual rank, half DQ!");
} else {
if para.dram_tpr13 & (1 << 29) != 0 {
// l 7935
// // println!("DX0 {}", dx0);
// // println!("DX1 {}", dx1);
}
return Err("dqs gate detect");
}
} else {
let mut rval = para.dram_para2;
rval &= 0xfffffff0;
rval |= 0x00001000;
para.dram_para2 = rval;
return Ok("dual rank, full DQ");
}
}
fn dramc_simple_wr_test(mem_mb: u32, len: u32) -> Result<(), &'static str> {
let offs: usize = (mem_mb as usize >> 1) << 18; // half of memory size
let patt1: u32 = 0x01234567;
let patt2: u32 = 0xfedcba98;
for i in 0..len {
let addr = RAM_BASE + 4 * i as usize;
writel(addr, patt1 + i);
writel(addr + offs, patt2 + i);
}
for i in 0..len {
let addr = RAM_BASE + 4 * i as usize;
let val = readl(addr);
let exp = patt1 + i;
if val != exp {
// // println!("{:x} != {:x} at address {:x}", val, exp, addr);
return Err("base");
}
let val = readl(addr + offs);
let exp = patt2 + i;
if val != exp {
// // println!("{:x} != {:x} at address {:x}", val, exp, addr + offs);
return Err("offs");
}
}
Ok(())
}
// Autoscan sizes a dram device by cycling through address lines and figuring
// out if it is connected to a real address line, or if the address is a mirror.
// First the column and bank bit allocations are set to low values (2 and 9 address
// lines. Then a maximum allocation (16 lines) is set for rows and this is tested.
// Next the BA2 line is checked. This seems to be placed above the column, BA0-1 and
// row addresses. Finally, the column address is allocated 13 lines and these are
// tested. The results are placed in dram_para1 and dram_para2.
fn auto_scan_dram_size(
para: &mut dram_parameters,
ccu: &CCU,
phy: &PHY,
) -> Result<(), &'static str> {
mctl_core_init(para, &ccu, &phy)?;
// write test pattern
for i in 0..64 {
let ptr = RAM_BASE + 4 * i;
let val = if i & 1 > 0 { ptr } else { !ptr };
writel(ptr, val as u32);
}
let maxrank = if para.dram_para2 & 0xf000 == 0 { 1 } else { 2 };
let mut mc_work_mode = MC_WORK_MODE_RANK0_1;
let mut offs = 0;
// Scan per address line, until address wraps (i.e. see shadow)
fn scan_for_addr_wrap() -> u32 {
for i in 11..17 {
let mut done = true;
for j in 0..64 {
let ptr = RAM_BASE + j * 4;
let chk = ptr + (1 << (i + 11));
let exp = if j & 1 != 0 { ptr } else { !ptr };
if readl(chk) != exp as u32 {
done = false;
break;
}
}
if done {
return i;
}
}
return 16;
}
// Scan per address line, until address wraps (i.e. see shadow)
fn scan_for_addr_wrap2() -> u32 {
for i in 9..15 {
let mut done = true;
for j in 0..64 {
let ptr = RAM_BASE + j * 4;
let chk = ptr + (1 << i);
let exp = if j & 1 != 0 { ptr } else { !ptr };
if readl(chk) != exp as u32 {
done = false;
break;
}
}
if done {
return i;
}
}
return 13;
}
for rank in 0..maxrank {
// Set row mode
let mut rval = readl(mc_work_mode);
rval &= 0xfffff0f3;
rval |= 0x000006f0;
writel(mc_work_mode, rval);
while readl(mc_work_mode) != rval {}
let i = scan_for_addr_wrap();
if VERBOSE {
// println!("rank {} row = {}", rank, i);
}
// Store rows in para 1
let shft = 4 + offs;
rval = para.dram_para1;
rval &= !(0xff << shft);
rval |= i << shft;
para.dram_para1 = rval;
if rank == 1 {
// Set bank mode for rank0
rval = readl(MC_WORK_MODE_RANK0_1);
rval &= 0xfffff003;
rval |= 0x000006a4;
writel(MC_WORK_MODE_RANK0_1, rval);
}
// Set bank mode for current rank
rval = readl(mc_work_mode);
rval &= 0xfffff003;
rval |= 0x000006a4;
writel(mc_work_mode, rval);
while readl(mc_work_mode) != rval {}
// Test if bit A23 is BA2 or mirror XXX A22?
let mut j = 0;
for i in 0..63 {
// where to check
let chk = RAM_BASE + (1 << 22) + i * 4;
// pattern
let ptr = RAM_BASE + i * 4;
// expected value
let exp = (if i & 1 != 0 { ptr } else { !ptr }) as u32;
if readl(chk) != exp {
j = 1;
break;
}
}
// let banks = (j + 1) << 2; // 4 or 8
// if VERBOSE {
// // println!("rank {} bank = {}", rank, banks);
// }
// Store banks in para 1
let shft = 12 + offs;
rval = para.dram_para1;
rval &= !(0xf << shft);
rval |= j << shft;
para.dram_para1 = rval;
if rank == 1 {
// Set page mode for rank0
rval = readl(MC_WORK_MODE_RANK0_1);
rval &= 0xfffff003;
rval |= 0x00000aa0;
writel(MC_WORK_MODE_RANK0_1, rval);
}
// Set page mode for current rank
rval = readl(mc_work_mode);
rval &= 0xfffff003;
rval |= 0x00000aa0;
writel(mc_work_mode, rval);
while readl(mc_work_mode) != rval {}
let i = scan_for_addr_wrap2();
let pgsize = if i == 9 { 0 } else { 1 << (i - 10) };
if VERBOSE {
// println!("rank {} page size = {}KB", rank, pgsize);
}
// Store page size
let shft = offs;
rval = para.dram_para1;
rval &= !(0xf << shft);
rval |= pgsize << shft;
para.dram_para1 = rval;
// FIXME: should not be here; those loops are pretty messed up
{
rval = readl(MC_WORK_MODE_RANK1_1); // MC_WORK_MODE
rval &= 0xfffff003;
rval |= 0x000006f0;
writel(MC_WORK_MODE_RANK1_1, rval);
rval = readl(MC_WORK_MODE_RANK1_2); // MC_WORK_MODE2
rval &= 0xfffff003;
rval |= 0x000006f0;
writel(MC_WORK_MODE_RANK1_2, rval);
}
// Move to next rank
if rank != maxrank {
if rank == 1 {
rval = readl(MC_WORK_MODE_RANK1_1); // MC_WORK_MODE
rval &= 0xfffff003;
rval |= 0x000006f0;
writel(MC_WORK_MODE_RANK1_1, rval);
rval = readl(MC_WORK_MODE_RANK1_2); // MC_WORK_MODE2
rval &= 0xfffff003;
rval |= 0x000006f0;
writel(MC_WORK_MODE_RANK1_2, rval);
}
offs += 16; // store rank1 config in upper half of para1
mc_work_mode += 4; // move to MC_WORK_MODE2
}
}
/*
if (maxrank == 2) {
para->dram_para2 &= 0xfffff0ff;
// note: rval is equal to para->dram_para1 here
if ((rval & 0xffff) == ((rval >> 16) & 0xffff)) {
printf("rank1 config same as rank0\n");
}
else {
para->dram_para2 |= 0x00000100;
printf("rank1 config different from rank0\n");
}
}
*/
Ok(())
}
// This routine sets up parameters with dqs_gating_mode equal to 1 and two
// ranks enabled. It then configures the core and tests for 1 or 2 ranks and
// full or half DQ width. it then resets the parameters to the original values.
// dram_para2 is updated with the rank & width findings.
fn auto_scan_dram_rank_width(
para: &mut dram_parameters,
ccu: &CCU,
phy: &PHY,
) -> Result<(), &'static str> {
let s1 = para.dram_tpr13;
let s2 = para.dram_para1;
para.dram_para1 = 0x00b000b0;
para.dram_para2 = (para.dram_para2 & 0xfffffff0) | 0x1000;
para.dram_tpr13 = (s1 & 0xfffffff7) | 0x5; // set DQS probe mode
mctl_core_init(para, &ccu, &phy)?;
if phy.pgsr[0].read() & (1 << 20) != 0 {
return Err("auto scan rank/width");
}
// TODO: print success message
dqs_gate_detect(para, &phy)?;
para.dram_tpr13 = s1;
para.dram_para1 = s2;
Ok(())
}
/* STEP 2 */
/// This routine determines the SDRAM topology.
///
/// It first establishes the number of ranks and the DQ width. Then it scans the
/// SDRAM address lines to establish the size of each rank. It then updates
/// `dram_tpr13` to reflect that the sizes are now known: a re-init will not
/// repeat the autoscan.
fn auto_scan_dram_config(
para: &mut dram_parameters,
ccu: &CCU,
phy: &PHY,
) -> Result<(), &'static str> {
if para.dram_tpr13 & (1 << 14) == 0 {
auto_scan_dram_rank_width(para, &ccu, &phy)?
}
if para.dram_tpr13 & (1 << 0) == 0 {
auto_scan_dram_size(para, &ccu, &phy)?
}
if (para.dram_tpr13 & (1 << 15)) == 0 {
para.dram_tpr13 |= 0x6003;
}
Ok(())
}
/// # Safety
///
/// No warranty. Use at own risk. Be lucky to get values from vendor.
pub fn init_dram(para: &mut dram_parameters, ccu: &CCU, phy: &PHY) -> usize {
// STEP 1: ZQ, gating, calibration and voltage
// Test ZQ status
if para.dram_tpr13 & (1 << 16) > 0 {
if VERBOSE {
// println!("DRAM only has internal ZQ.");
}
writel(RES_CAL_CTRL_REG, readl(RES_CAL_CTRL_REG) | 0x100);
writel(RES240_CTRL_REG, 0);
sdelay(10);
} else {
writel(ANALOG_SYS_PWROFF_GATING_REG, 0); // 0x7010000 + 0x254; l 9655
writel(RES_CAL_CTRL_REG, readl(RES_CAL_CTRL_REG) & !0x003);
sdelay(10);
writel(RES_CAL_CTRL_REG, readl(RES_CAL_CTRL_REG) & !0x108);
sdelay(10);
writel(RES_CAL_CTRL_REG, readl(RES_CAL_CTRL_REG) | 0x001);
sdelay(20);
// if VERBOSE {
// let zq_val = readl(ZQ_VALUE);
// // println!("ZQ: {}", zq_val);
// }
}
// Set voltage
let rc = get_pmu_exists();
if VERBOSE {
// println!("PMU exists? {}", rc);
}
if !rc {
dram_vol_set(para);
} else {
if para.dram_type == DramType::Ddr2 {
set_ddr_voltage(1800);
} else if para.dram_type == DramType::Ddr3 {
set_ddr_voltage(1500);
}
}
// STEP 2: CONFIG
// Set SDRAM controller auto config
if (para.dram_tpr13 & 0x1) == 0 {
if let Err(_msg) = auto_scan_dram_config(para, &ccu, &phy) {
// println!("config fail {}", msg);
return 0;
}
}
// let dtype = match para.dram_type {
// DramType::Ddr2 => "DDR2",
// DramType::Ddr3 => "DDR3",
// _ => "",
// };
// println!("{}@{}MHz", dtype, para.dram_clk);
if VERBOSE {
if (para.dram_odt_en & 0x1) == 0 {
// println!("ODT off");
} else {
// println!("ZQ: {}", para.dram_zq);
}
}
if VERBOSE {
// report ODT
if (para.dram_mr1 & 0x44) == 0 {
// println!("ODT off");
} else {
// println!("ODT: {}", para.dram_mr1);
}
}
// Init core, final run
if let Err(msg) = mctl_core_init(para, &ccu, &phy) {
// println!("init error {}", msg);
return 0;
};
// Get sdram size
let mut rc: u32 = para.dram_para2;
if rc != 0 {
rc = (rc & 0x7fff0000) >> 16;
} else {
rc = dramc_get_dram_size();
para.dram_para2 = (para.dram_para2 & 0xffff) | rc << 16;
}
let mem_size = rc;
if VERBOSE {
// println!("DRAM: {}M", mem_size);
}
// Purpose ??
// What is Auto SR?
if para.dram_tpr13 & (1 << 30) != 0 {
let rc = readl(para.dram_tpr8 as usize);
unsafe {
phy.asrtc.write(if rc == 0 { 0x10000200 } else { rc });
phy.asrc.write(0x40a);
phy.pwrctl.modify(|val| val | 0x1)
};
// // println!("Enable Auto SR");
} else {
unsafe {
phy.asrtc.modify(|val| val & 0xffff0000);
phy.pwrctl.modify(|val| val & !0x1)
};
}
// Purpose ??
rc = phy.pgcr[0].read() & !(0xf000);
if (para.dram_tpr13 & 0x200) == 0 {
if para.dram_type != DramType::Lpddr2 {
unsafe { phy.pgcr[0].write(rc) };
}
} else {
unsafe { phy.pgcr[0].write(rc | 0x5000) };
}
unsafe {
phy.zqcr.modify(|val| val | (1 << 31));
}
if para.dram_tpr13 & (1 << 8) != 0 {
writel(0x31030b8, phy.zqcr.read() | 0x300);
}
let mut rc = readl(MRCTRL0);
if para.dram_tpr13 & (1 << 16) != 0 {
rc &= 0xffffdfff;
} else {
rc |= 0x00002000;
}
writel(MRCTRL0, rc);
// Purpose ??
if para.dram_type == DramType::Lpddr3 {
let rc = phy.odtcfg.read() & 0xfff0ffff;
unsafe { phy.odtcfg.write(rc | 0x0001000) }
}
dram_enable_all_master();
let len = 4096; // NOTE: a commented call outside the if uses 64 in C code
if para.dram_tpr13 & (1 << 28) != 0 {
rc = readl(SOME_STATUS);
if rc & (1 << 16) != 0 {
return 0;
}
if let Err(msg) = dramc_simple_wr_test(mem_size, len) {
// println!("test fail {}", msg);
return 0;
}
// println!("test OK");
}
handler_super_standby();
mem_size as usize
}
pub fn init(ccu: &CCU, phy: &PHY) -> usize {
// taken from SPL
#[rustfmt::skip]
let mut dram_para: dram_parameters = dram_parameters {
dram_clk: 792,
dram_type: DramType::Ddr3,
dram_zq: 0x007b_7bfb,
dram_odt_en: 0x0000_0001,
#[cfg(feature="nezha")]
dram_para1: 0x0000_10f2,
#[cfg(feature="lichee")]
dram_para1: 0x0000_10d2,
dram_para2: 0x0000_0000,
dram_mr0: 0x0000_1c70,
dram_mr1: 0x0000_0042,
#[cfg(feature="nezha")]
dram_mr2: 0x0000_0000,
#[cfg(feature="lichee")]
dram_mr2: 0x0000_0018,
dram_mr3: 0x0000_0000,
dram_tpr0: 0x004a_2195,
dram_tpr1: 0x0242_3190,
dram_tpr2: 0x0008_b061,
dram_tpr3: 0xb478_7896,
dram_tpr4: 0x0000_0000,
dram_tpr5: 0x4848_4848,
dram_tpr6: 0x0000_0048,
dram_tpr7: 0x1620_121e,
dram_tpr8: 0x0000_0000,
dram_tpr9: 0x0000_0000,
dram_tpr10: 0x0000_0000,
#[cfg(feature="nezha")]
dram_tpr11: 0x0076_0000,
#[cfg(feature="lichee")]
dram_tpr11: 0x0087_0000,
#[cfg(feature="nezha")]
dram_tpr12: 0x0000_0035,
#[cfg(feature="lichee")]
dram_tpr12: 0x0000_0024,
#[cfg(feature="nezha")]
dram_tpr13: 0x3405_0101,
#[cfg(feature="lichee")]
dram_tpr13: 0x3405_0100,
};
// // println!("DRAM INIT");
return init_dram(&mut dram_para, &ccu, &phy);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment