PinePhone/DRAM initialization

From OSDev.wiki
Revision as of 23:55, 7 August 2022 by Immibis (talk | contribs) (//)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

There are two things we have to do here: (1) enable the DRAM controller module's clock and reset; (2) configure the DRAM controller.

    • License warning**: the sequence of operations needed was derived from U-boot, but is written here in the author's own code. In the author's opinion, since only the facts and not the code are derived, U-boot's license (GPL) the organization of the code is sufficiently
    • Quality warning**: the code here got DRAM to work for me, but actual correctness is not guaranteed. Especially since the entire DRAM controller is barely documented (in the comments of u-boot and linux drivers), we might be close to some threshold of unreliablity without knowing.

Power up DRAM controller

#define U32_REG(addr) (*(volatile uint32_t*)(addr))

#define PLL_DDR0_CTRL_REG U32_REG(0x01C20020)
#define PLL_DDR0_ENABLE_FLAG 0x80000000

#define PLL_DDR1_CTRL_REG U32_REG(0x01C2004C)
#define PLL_DDR1_ENABLE_FLAG 0x80000000
#define PLL_DDR1_UPDATE_FLAG 0x40000000
#define PLL_DDR1_FACTOR_N(n) ((((n) - 1) << 8) & 0x3F00)

#define MBUS_RST_REG U32_REG(0x01C200FC)
#define MBUS_RST_RELEASE 0x80000000 // zero value means reset, this flag means not reset

#define MBUS_CLK_REG U32_REG(0x01C2015C)
#define MBUS_CLK_CLOCK_ENABLE 0x80000000

#define BUS_CLK_GATING_REG0 U32_REG(0x01C20060)
#define BUS_CLK_RESET_RELEASE_REG0 U32_REG(0x01C202C0) // same bitfields
#define BUS_CLK_REG0_DRAM_GATING 0x00004000

#define DRAM_CFG_REG U32_REG(0x01C200F4)
#define DRAM_CFG_RESET_RELEASE 0x80000000
#define DRAM_CFG_SRC_PLL_DDR1 0x00100000
#define DRAM_CFG_UPDATE_FLAG 0x00010000

static void dsb() {
	// wait for all outstanding memory accesses to complete before continuing.
	// Probably not actually the right thing to do in most cases!
	__asm__ __volatile__("dsb" ::: "memory");
}
static void delay() {
	for(volatile int i = 0; i < 0x1000; i++) {}
}

static void delay() {
	int n = 0x1000;
	while(n--) {
		__asm__ __volatile__("" : "=r"(n) : "0"(n) : "memory");
	}
}

void dram_clock_init()
{
	// A64 User Manual 3.3.6.4. says we should always release reset before releasing clock gate. So logically we should do the reverse order when disabling a clock.
	MBUS_CLK_REG &= ~MBUS_CLK_CLOCK_ENABLE; // disable MBUS clock
	BUS_CLK_GATING_REG0 &= ~BUS_CLK_REG0_DRAM_GATING; // disable DRAM clock
	PLL_DDR0_CTRL_REG &= ~PLL_DDR0_ENABLE_FLAG; // disable DRAM clock (maybe)
	PLL_DDR1_CTRL_REG &= ~PLL_DDR1_ENABLE_FLAG; // disable DRAM clock (maybe)
	MBUS_RST_REG &= ~MBUS_RST_RELEASE; // assert MBUS reset
	BUS_CLK_RESET_RELEASE_REG0 &= ~BUS_CLK_REG0_DRAM_GATING; // assert DRAM reset
	// shouldn't we disable the DRAM controller clock?
	DRAM_CFG_REG &= ~DRAM_CFG_RESET_RELEASE; // assert DRAM controller reset
	dsb();

	// N factor calculation:
	// 553MHz DDR clock; *2 because the DRAM controller internal clock apparently runs at DDR (not surprising I guess)
	// divided by the 24MHz base clock which is apparently the input to the PLL
	// gives 553*2/24 = 46.083, rounded to 46 (which gives 552MHz)
	// note the value in register is this -1 (see the macro) so it's 45 to give an actual divisor of 46
	// note the DDR register values are also calculated based on the 553MHz clock speed
	PLL_DDR1_CTRL_REG = PLL_DDR1_ENABLE_FLAG | PLL_DDR1_UPDATE_FLAG | PLL_DDR1_FACTOR_N(46);

	// Then wait for the PLL change to be processed by the hardware. (Does this wait for the PLL to actually lock? Not clear)
	while (PLL_DDR1_CTRL_REG & PLL_DDR1_UPDATE_FLAG) {}

	// there's also a clock divisor in this register; default (zero value) is divide-by-1
	DRAM_CFG_REG = DRAM_CFG_SRC_PLL_DDR1 | DRAM_CFG_UPDATE_FLAG;
	while(DRAM_CFG_REG & DRAM_CFG_UPDATE_FLAG) {}

	// as mentioned, A64 User Manual 3.3.6.4. says we should always release reset before releasing clock gate.
	MBUS_RST_REG |= MBUS_RST_RELEASE; // release MBUS reset
	MBUS_CLK_REG |= MBUS_CLK_CLOCK_ENABLE; // enable MBUS clock
	BUS_CLK_RESET_RELEASE_REG0 |= BUS_CLK_REG0_DRAM_GATING; // release DRAM reset
	BUS_CLK_GATING_REG0 |= BUS_CLK_REG0_DRAM_GATING; // enable DRAM clock

	// apparently that rule does not apply to this one. Perhaps because the clock enable is inside the block (next register)
	DRAM_CFG_REG |= DRAM_CFG_RESET_RELEASE; // release DRAM controller reset
	delay();

	U32_REG(0x01C6300C) = 0x0000c00e; // enable DRAM controller clock via undocumented register
	delay(); // required delay; breaks if this is removed
}

Configure DRAM controller

According to the A64 User Manual, the DRAM controller initializes itself automatically. Compared to some other processors, this is true. On some other platforms, software has to measure the delay on every wire between the memory chip and the CPU chip, and then decide the optimal delays and tell the DRAM controller how much delay to add to make them all equal. On A64 we don't have to do that, but we still have a bunch of undocumented registers to set...

[TODO]