diff mbox series

[07/10] ram: metanoia: Add driver for MT2824 DDR controller

Message ID 20261002-mt2824-v1-7-bc44cf0e0289@bootlin.com
State Under Review
Delegated to: Tim Ouyang
Headers show
Series riscv: Add support for Metanoia MT2824 SoC and MT5824 EVB | expand

Commit Message

Benoît Monin Oct. 2, 2026, 1:02 p.m. UTC
Add a DDR controller driver for the Metanoia MT2824 SoC, supporting
initialization and training of the DDR subsystem in SPL, and implementing
the driver-model for the RAM uclass.

The driver relies on a firmware to configure the DDR controller, and
expects the firmware to be found via a device tree reg entry named "fw".

The driver expects the DDR firmware to be packaged inside a FIT image,
as the firmware node of the default configuration, and it supports signed
FIT image.

Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
 drivers/ram/Kconfig      |   7 +
 drivers/ram/Makefile     |   2 +
 drivers/ram/mt2824-ddr.c | 706 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 715 insertions(+)
diff mbox series

Patch

diff --git a/drivers/ram/Kconfig b/drivers/ram/Kconfig
index acd6d9cfd6a..feb1754e40e 100644
--- a/drivers/ram/Kconfig
+++ b/drivers/ram/Kconfig
@@ -139,6 +139,13 @@  config K3_MULTI_DDR
 	  responsible for interleaving between the controllers.
 	default y if SOC_K3_J721S2 || SOC_K3_J784S4
 
+config SPL_METANOIA_MT2824_DDR
+	bool "Metanoia MT2824 DDR driver in SPL"
+	depends on SPL_RAM
+	help
+	   Enable support for the internal DDR Memory Controller
+	   of the Metanoia MT2824 SoC in the SPL.
+
 source "drivers/ram/aspeed/Kconfig"
 source "drivers/ram/cadence/Kconfig"
 source "drivers/ram/octeon/Kconfig"
diff --git a/drivers/ram/Makefile b/drivers/ram/Makefile
index 82afd5fcbcc..6e1b86127ce 100644
--- a/drivers/ram/Makefile
+++ b/drivers/ram/Makefile
@@ -19,6 +19,8 @@  obj-$(CONFIG_K3_DDRSS) += k3-ddrss/
 
 obj-$(CONFIG_IMXRT_SDRAM) += imxrt_sdram.o
 
+obj-$(CONFIG_SPL_METANOIA_MT2824_DDR) += mt2824-ddr.o
+
 obj-$(CONFIG_RAM_SIFIVE) += sifive/
 ifdef CONFIG_XPL_BUILD
 obj-$(CONFIG_SPL_STARFIVE_DDR) += starfive/
diff --git a/drivers/ram/mt2824-ddr.c b/drivers/ram/mt2824-ddr.c
new file mode 100644
index 00000000000..c01c30089ca
--- /dev/null
+++ b/drivers/ram/mt2824-ddr.c
@@ -0,0 +1,706 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <dm.h>
+#include <image.h>
+#include <ram.h>
+#include <reset.h>
+#include <dm/device_compat.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <u-boot/crc.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* controller registers */
+#define CTRL_STAT				0x00000004
+#define CTRL_PWRCTL				0x00000030
+#define CTRL_RFSHCTL3				0x00000060
+#define CTRL_INIT7				0x000000ec
+#define CTRL_DFITMG0				0x00000190
+#define CTRL_DFITMG1				0x00000194
+#define CTRL_DFIMISC				0x000001b0
+#define CTRL_DFISTAT				0x000001bc
+#define CTRL_SWCTL				0x00000320
+#define CTRL_SWSTAT				0x00000324
+
+#define CTRL_STAT_MODE				GENMASK(2, 0)
+#define CTRL_STAT_NORMAL			1
+
+#define CTRL_PWRCTL_SELFREF			BIT(5)
+#define CTRL_PWRCTL_CLKDIS			BIT(3)
+#define CTRL_PWRCTL_PWRDN			BIT(1)
+
+#define CTRL_RFSHCTL3_DIS_AUTO			BIT(0)
+
+#define CTRL_INIT7_MR22				GENMASK(31, 16)
+#define CTRL_INIT7_MR14				GENMASK(15, 0)
+
+#define CTRL_DFITMG0_CTRL_DELAY			GENMASK(28, 24)
+#define CTRL_DFITMG0_RDDATA_USE_DFI_PHY_CLK	BIT(23)
+#define CTRL_DFITMG0_RDDATA_EN			GENMASK(22, 16)
+#define CTRL_DFITMG0_WRDATA_USE_DFI_PHY_CLK	BIT(15)
+#define CTRL_DFITMG0_TPHY_WRDATA		GENMASK(13, 8)
+#define CTRL_DFITMG0_TPHY_WRLAT			GENMASK(5, 0)
+
+#define CTRL_DFITMG1_WRDATA_DELAY		GENMASK(20, 16)
+#define CTRL_DFITMG1_CLK_DIS			GENMASK(12, 8)
+#define CTRL_DFITMG1_CLK_EN			GENMASK(4, 0)
+
+#define CTRL_DFIMISC_START			BIT(5)
+#define CTRL_DFIMISC_COMPLETE			BIT(0)
+
+#define CTRL_DFISTAT_COMPLETE			BIT(0)
+
+#define CTRL_SWCTL_DONE				BIT(0)
+#define CTRL_SWSTAT_DONE			BIT(0)
+
+/* PHY registers */
+#define PHY_DQS_DELAY_GRP0			0x000201a0
+#define PHY_RDPTR_INITVAL			0x0004005c
+#define PHY_ICCM_BASE				0x000a0000
+#define PHY_DCCM_BASE				0x000a8000
+#define PHY_DCCM_V26				0x000a804c
+#define PHY_DCCM_V27				0x000a804e
+#define PHY_DCCM_V40				0x000a8080
+#define PHY_PMU_MUX_SEL				0x001a0000
+#define PHY_UCTSHADOWREGS			0x001a0008
+#define PHY_DCTWRITEPROT			0x001a0062
+#define PHY_UCTWRITEONLYSHADOW			0x001a0064
+#define PHY_UCTDATWRITEONLYSHADOW		0x001a0068
+#define PHY_UC_CTRL				0x001a0132
+
+#define PHY_DQS_DELAY_COARSE			GENMASK(9, 6)
+#define PHY_DQS_DELAY_FINE			GENMASK(4, 0)
+
+#define PHY_ICCM_SIZE				0x00008000
+#define PHY_DCCM_SIZE				0x00002000
+
+#define PHY_PMU_MUX_SEL_UC			BIT(0)
+
+#define PHY_UC_CTRL_RESET			BIT(3)
+#define PHY_UC_CTRL_STALL			BIT(0)
+
+/* DDR parameters */
+#define PARAM_BL				16
+#define PARAM_CLK_DIS				2
+#define PARAM_CLK_EN				2
+#define PARAM_MISC				0
+#define PARAM_MR22				5
+#define PARAM_RD				0
+#define PARAM_RL				28
+#define PARAM_TDQSS				1
+#define PARAM_WL				14
+#define PARAM_WR				0
+#define PARAM_WRDATA				2
+
+#define TRAINING_STAGES				2
+
+#define POLL_TIMEOUT				1000000
+
+#define MSG_TRAINING_DONE			0x07
+#define MSG_TRAINING_FAIL			0xff
+
+#define FW_MAGIC				0x44445246 /* "DDRF" */
+#define FW_VERSION				0x01000000
+
+/* Base address used in the firmware for the controller */
+#define CTRL_FW_BASE				0x10060000
+
+enum {
+	FW_COMP_CTRL_CFG = 0,
+	FW_COMP_PHY_CFG,
+	FW_COMP_PMU_1D_IMEM,
+	FW_COMP_PMU_1D_DMEM,
+	FW_COMP_PMU_2D_IMEM,
+	FW_COMP_PMU_2D_DMEM,
+	FW_COMP_PIE_IMAGE,
+	FW_COMP_MAX
+};
+
+struct fw_comp {
+	u32 offset;
+	u32 size;
+	u32 crc32;
+	u32 reserved;
+};
+
+struct fw_header {
+	u32 magic;
+	u32 version;
+	u32 header_size;
+	u32 total_size;
+	u32 flags;
+	u32 timestamp;
+	u32 reserved1;
+	u32 reserved2;
+	struct fw_comp comps[FW_COMP_MAX];
+	u32 header_crc32;
+	u32 reserved[27]; /* Padding to 256 bytes */
+};
+
+struct fw_reg {
+	u32 addr;
+	u32 value;
+};
+
+struct mt2824_ddr_priv {
+	void __iomem *ctrl;
+	resource_size_t ctrl_size;
+	void __iomem *phy;
+	resource_size_t phy_size;
+	void *fw;
+	resource_size_t fw_size;
+	struct reset_ctl rst;
+	const void *fw_img;
+};
+
+static int get_fw_img_from_fit(const void *fit, size_t fit_size,
+			       const void **fw_img, size_t *fw_size)
+{
+	int conf_noffset;
+	const char *img_name;
+	int img_noffset;
+
+	if (fit_check_format(fit, fit_size))
+		return -EINVAL;
+
+	conf_noffset = fit_conf_get_node(fit, NULL);
+	if (conf_noffset < 0)
+		return -EINVAL;
+
+	printf("## Checking hash(es) for config %s ... ",
+	       fit_get_name(fit, conf_noffset, NULL));
+	if (fit_config_verify(fit, conf_noffset))
+		return -EPERM;
+	puts("OK\n");
+
+	img_name = fdt_getprop(fit, conf_noffset, FIT_FIRMWARE_PROP, NULL);
+	if (!img_name)
+		return -EINVAL;
+
+	img_noffset = fit_image_get_node(fit, img_name);
+	if (img_noffset < 0)
+		return -EINVAL;
+
+	if (fit_image_get_emb_data(fit, img_noffset, fw_img, fw_size))
+		return -EINVAL;
+
+	printf("## Checking hash(es) for image %s ... ", img_name);
+	if (!fit_image_verify_with_data(fit, img_noffset, gd_fdt_blob(), *fw_img, *fw_size))
+		return -EPERM;
+	puts("OK\n");
+
+	return 0;
+}
+
+static int check_firmware(struct udevice *dev)
+{
+	struct mt2824_ddr_priv *priv = dev_get_priv(dev);
+	const struct fw_header *header;
+	u32 calculated_crc;
+	int ret = 0;
+	size_t fw_size;
+
+	ret = get_fw_img_from_fit(priv->fw, priv->fw_size, &priv->fw_img, &fw_size);
+	if (ret)
+		return ret;
+
+	header = priv->fw_img;
+
+	if (header->magic != FW_MAGIC) {
+		dev_err(dev, "Invalid firmware magic: 0x%08x (expected 0x%08x)\n",
+			header->magic, FW_MAGIC);
+		return -EINVAL;
+	}
+
+	if (header->version != FW_VERSION) {
+		dev_err(dev, "Invalid firmware version %u\n", header->version);
+		return -EINVAL;
+	}
+
+	if (header->header_size != sizeof(struct fw_header)) {
+		dev_err(dev, "Invalid firmware header size %u\n", header->header_size);
+		return -EINVAL;
+	}
+
+	if (header->total_size > fw_size) {
+		dev_err(dev, "firmware too big\n");
+		return -EINVAL;
+	}
+
+	calculated_crc = crc32(0, priv->fw_img, offsetof(struct fw_header, header_crc32));
+	if (calculated_crc != header->header_crc32) {
+		dev_err(dev, "Header CRC mismatch: calculated=0x%08x, expected=0x%08x\n",
+			calculated_crc, header->header_crc32);
+		return -EINVAL;
+	}
+
+	for (int i = 0; i < FW_COMP_MAX; i++) {
+		u32 offset = header->comps[i].offset;
+		u32 size = header->comps[i].size;
+		u32 expected_crc = header->comps[i].crc32;
+
+		if (size == 0) {
+			dev_err(dev, "Component #%d is empty\n", i);
+			ret = -EINVAL;
+			continue;
+		}
+
+		if ((resource_size_t)offset + size > fw_size) {
+			dev_err(dev, "Component #%d out of range\n", i);
+			ret = -EINVAL;
+			continue;
+		}
+
+		calculated_crc = crc32(0, priv->fw_img + offset, size);
+		if (calculated_crc != expected_crc) {
+			dev_err(dev, "Component #%d CRC verification failed\n", i);
+			ret = -EINVAL;
+		}
+	}
+
+	return ret;
+}
+
+static int ddr_ctrl_load_config(struct mt2824_ddr_priv *priv)
+{
+	const struct fw_header *header = priv->fw_img;
+	const struct fw_reg *reg;
+	unsigned int nb_regs;
+	u32 addr;
+
+	/* address + value per register */
+	nb_regs = header->comps[FW_COMP_CTRL_CFG].size / sizeof(struct fw_reg);
+	reg = priv->fw_img + header->comps[FW_COMP_CTRL_CFG].offset;
+
+	/* Apply each register configuration */
+	for (unsigned int i = 0; i < nb_regs; i++, reg++) {
+		addr = reg->addr;
+
+		/*
+		 * Some firmware version use an absolute address,
+		 * convert to a relative offset
+		 */
+		if (addr >= CTRL_FW_BASE)
+			addr -= CTRL_FW_BASE;
+
+		if (addr >= priv->ctrl_size)
+			return -EINVAL;
+
+		writel(reg->value, priv->ctrl + addr);
+	}
+
+	return 0;
+}
+
+static int ddr_ctrl_init(struct mt2824_ddr_priv *priv)
+{
+	u32 val;
+
+	setbits_le32(priv->ctrl + CTRL_RFSHCTL3, CTRL_RFSHCTL3_DIS_AUTO);
+	clrbits_le32(priv->ctrl + CTRL_PWRCTL, CTRL_PWRCTL_PWRDN | CTRL_PWRCTL_CLKDIS);
+	writel(0, priv->ctrl + CTRL_SWCTL);
+	clrbits_le32(priv->ctrl + CTRL_DFIMISC, CTRL_DFIMISC_COMPLETE);
+	setbits_le32(priv->ctrl + CTRL_SWCTL, CTRL_SWCTL_DONE);
+
+	return readl_poll_timeout(priv->ctrl + CTRL_SWSTAT, val,
+				  val == CTRL_SWSTAT_DONE, POLL_TIMEOUT);
+}
+
+static int ddr_phy_load_comp(struct mt2824_ddr_priv *priv, unsigned int comp)
+{
+	const struct fw_header *header = priv->fw_img;
+	const struct fw_reg *reg;
+	unsigned int nb_regs;
+
+	/* address + value per register */
+	nb_regs = header->comps[comp].size / sizeof(struct fw_reg);
+	reg = priv->fw_img + header->comps[comp].offset;
+
+	/* Apply each register configuration */
+	for (unsigned int i = 0; i < nb_regs; i++, reg++) {
+		if (reg->addr >= priv->phy_size / 2)
+			return -EINVAL;
+		writew((u16)(reg->value & 0xFFFF), priv->phy + reg->addr * 2);
+	}
+
+	return 0;
+}
+
+static int phy_load_training_data(struct mt2824_ddr_priv *priv, unsigned int comp,
+				  void __iomem *base, resource_size_t size)
+{
+	const struct fw_header *header = priv->fw_img;
+	unsigned int nb_vals;
+	const u16 *val;
+
+	if (header->comps[comp].size > size)
+		return -EINVAL;
+
+	nb_vals = header->comps[comp].size / sizeof(u16);
+	val = priv->fw_img + header->comps[comp].offset;
+
+	for (unsigned int i = 0; i < nb_vals; i++) {
+		writew(*val, base + i * 2);
+		val++;
+	}
+
+	return 0;
+}
+
+static u32 get_mail(struct mt2824_ddr_priv *priv, int mode)
+{
+	u32 mail;
+	u16 val;
+
+	readw_poll_sleep_timeout(priv->phy + PHY_UCTSHADOWREGS, val,
+				 !(val & BIT(0)), mode ? 0 : 100, 0);
+
+	mail = readw(priv->phy + PHY_UCTWRITEONLYSHADOW);
+	if (mode == 1)
+		mail |= (readw(priv->phy + PHY_UCTDATWRITEONLYSHADOW) << 16);
+
+	writew(0, priv->phy + PHY_DCTWRITEPROT);
+	readw_poll_timeout(priv->phy + PHY_UCTSHADOWREGS, val, val, 0);
+	writew(1, priv->phy + PHY_DCTWRITEPROT);
+
+	return mail;
+}
+
+static void decode_streaming_message(struct mt2824_ddr_priv *priv)
+{
+	u32 msg_len = get_mail(priv, 1) & 0xffff;
+
+	for (unsigned int i = 0; i < msg_len; i++)
+		get_mail(priv, 1);
+}
+
+static void decode_major_message(struct mt2824_ddr_priv *priv, u32 mail)
+{
+	switch (mail) {
+	case MSG_TRAINING_DONE:
+		debug("DDRPHY: Training has run successfully\n");
+		break;
+	case MSG_TRAINING_FAIL:
+		debug("DDRPHY: Training has failed\n");
+		break;
+	case 0x00:
+		debug("DDRPHY: End of initialization\n");
+		break;
+	case 0x01:
+		debug("DDRPHY: End of fine write leveling\n");
+		break;
+	case 0x02:
+		debug("DDRPHY: End of read enable training\n");
+		break;
+	case 0x03:
+		debug("DDRPHY: End of read delay center optimization\n");
+		break;
+	case 0x04:
+		debug("DDRPHY: End of write delay center optimization\n");
+		break;
+	case 0x05:
+		debug("DDRPHY: End of 2D read delay/voltage center optimization\n");
+		break;
+	case 0x06:
+		debug("DDRPHY: End of 2D write delay/voltage center optimization\n");
+		break;
+	case 0x09:
+		debug("DDRPHY: End of max read latency training\n");
+		break;
+	case 0x0a:
+		debug("DDRPHY: End of read dq deskew training\n");
+		break;
+	case 0x0b:
+		debug("DDRPHY: Reserved\n");
+		break;
+	case 0x0c:
+		debug("DDRPHY: End of all DB training (MREP/DWL/MRD/MWD complete)\n");
+		break;
+	case 0x0d:
+		debug("DDRPHY: End of CA training\n");
+		break;
+	case 0xfd:
+		debug("DDRPHY: End of MPR read delay center optimization\n");
+		break;
+	case 0xfe:
+		debug("DDRPHY: End of Write leveling coarse delay\n");
+		break;
+	case 0x08:
+		debug("DDRPHY: Start streaming message mode\n");
+		decode_streaming_message(priv);
+		break;
+	default:
+		debug("DDRPHY: Error, unknown major_message %x\n", mail);
+	}
+}
+
+static int ddr_phy_wait_training_done(struct mt2824_ddr_priv *priv)
+{
+	u32 mail = 0;
+
+	do {
+		mail = get_mail(priv, 0);
+		decode_major_message(priv, mail);
+	} while (mail != MSG_TRAINING_DONE && mail != MSG_TRAINING_FAIL);
+
+	return (mail == MSG_TRAINING_DONE) ? 0 : -EIO;
+}
+
+static void write_mr14_to_register(struct mt2824_ddr_priv *priv)
+{
+	u16 v26 = readw(priv->phy + PHY_DCCM_V26);
+	u16 v27 = readw(priv->phy + PHY_DCCM_V27);
+	u16 v40 = readw(priv->phy + PHY_DCCM_V40);
+
+	u8 b0 = (v26 >> 8) & 0xff;
+	u8 b1 = v27 & 0xff;
+	u8 b2 = v40 & 0xff;
+	u8 b3 = (v40 >> 8) & 0xff;
+
+	u32 sum = (u32)b0 + b1 + b2 + b3;
+	u8 avg = (sum + 2) / 4;
+
+	writel(FIELD_PREP(CTRL_INIT7_MR22, PARAM_MR22) | FIELD_PREP(CTRL_INIT7_MR14, avg),
+	       priv->ctrl + CTRL_INIT7);
+}
+
+static int ddr_phy_training(struct mt2824_ddr_priv *priv)
+{
+	struct {
+		unsigned int imem_comp;
+		unsigned int dmem_comp;
+	} fw_info[TRAINING_STAGES] = {
+		{FW_COMP_PMU_1D_IMEM, FW_COMP_PMU_1D_DMEM},
+		{FW_COMP_PMU_2D_IMEM, FW_COMP_PMU_2D_DMEM}
+	};
+	int ret;
+
+	for (unsigned int stage = 0; stage < TRAINING_STAGES; stage++) {
+		ret = phy_load_training_data(priv, fw_info[stage].imem_comp,
+					     priv->phy + PHY_ICCM_BASE, PHY_ICCM_SIZE);
+		if (ret)
+			return ret;
+
+		writew(PHY_PMU_MUX_SEL_UC, priv->phy + PHY_PMU_MUX_SEL);
+		writew(0, priv->phy + PHY_PMU_MUX_SEL);
+
+		ret = phy_load_training_data(priv, fw_info[stage].dmem_comp,
+					     priv->phy + PHY_DCCM_BASE, PHY_DCCM_SIZE);
+		if (ret)
+			return ret;
+
+		/*
+		 * MicroContMuxSel=1, written twice to match the PhyInit
+		 * sequence (end of DMEM load and start of the firmware).
+		 */
+		writew(PHY_PMU_MUX_SEL_UC, priv->phy + PHY_PMU_MUX_SEL);
+		writew(PHY_PMU_MUX_SEL_UC, priv->phy + PHY_PMU_MUX_SEL);
+
+		writew(PHY_UC_CTRL_RESET | PHY_UC_CTRL_STALL, priv->phy + PHY_UC_CTRL);
+		writew(PHY_UC_CTRL_STALL, priv->phy + PHY_UC_CTRL);
+		writew(0, priv->phy + PHY_UC_CTRL);
+
+		ret = ddr_phy_wait_training_done(priv);
+		if (ret)
+			return ret;
+
+		writew(PHY_UC_CTRL_STALL, priv->phy + PHY_UC_CTRL);
+		writew(0, priv->phy + PHY_PMU_MUX_SEL);
+
+		if (stage == 1)
+			write_mr14_to_register(priv);
+
+		writew(PHY_PMU_MUX_SEL_UC, priv->phy + PHY_PMU_MUX_SEL);
+		writew(0, priv->phy + PHY_PMU_MUX_SEL);
+	}
+
+	return 0;
+}
+
+static int ddr_ctrl_configure(struct mt2824_ddr_priv *priv)
+{
+	u8 trddata_en = PARAM_RL - 5 + 2 * (PARAM_MISC - PARAM_RD);
+	u8 tphy_wrlat = PARAM_WL - 5 + 2 * (PARAM_MISC - PARAM_WR) + PARAM_TDQSS;
+	u8 twrdata_delay;
+	u8 tctrl_delay;
+	u8 ardptrinitval;
+	u16 dqs_dly;
+	u8 trained_txdqsdly;
+	u32 val;
+	int ret;
+
+	clrbits_le32(priv->ctrl + CTRL_SWCTL, CTRL_SWCTL_DONE);
+	setbits_le32(priv->ctrl + CTRL_DFIMISC, CTRL_DFIMISC_START);
+	setbits_le32(priv->ctrl + CTRL_SWCTL, CTRL_SWCTL_DONE);
+
+	ret = readl_poll_timeout(priv->ctrl + CTRL_SWSTAT, val,
+				 val == CTRL_SWSTAT_DONE, POLL_TIMEOUT);
+	if (ret)
+		return ret;
+
+	ret = readl_poll_timeout(priv->ctrl + CTRL_DFISTAT, val,
+				 FIELD_GET(CTRL_DFISTAT_COMPLETE, val), POLL_TIMEOUT);
+	if (ret)
+		return ret;
+
+	clrbits_le32(priv->ctrl + CTRL_SWCTL, CTRL_SWCTL_DONE);
+	clrbits_le32(priv->ctrl + CTRL_DFIMISC, CTRL_DFIMISC_START);
+	setbits_le32(priv->ctrl + CTRL_DFIMISC, CTRL_DFIMISC_COMPLETE);
+	clrbits_le32(priv->ctrl + CTRL_PWRCTL, CTRL_PWRCTL_SELFREF);
+
+	writew(0, priv->phy + PHY_PMU_MUX_SEL);
+
+	/* read the trained DQS delay and round it up */
+	dqs_dly = readw(priv->phy + PHY_DQS_DELAY_GRP0);
+	trained_txdqsdly = FIELD_GET(PHY_DQS_DELAY_COARSE, dqs_dly) +
+			   (FIELD_GET(PHY_DQS_DELAY_FINE, dqs_dly) +
+			    FIELD_MAX(PHY_DQS_DELAY_FINE)) /
+			   (FIELD_MAX(PHY_DQS_DELAY_FINE) + 1);
+
+	ardptrinitval = readw(priv->phy + PHY_RDPTR_INITVAL);
+	tctrl_delay = ((ardptrinitval / 2) + (PARAM_MISC * 2) * 1 + 3);
+	twrdata_delay = tctrl_delay + (6 + PARAM_BL / 2) + trained_txdqsdly;
+
+	writew(PHY_PMU_MUX_SEL_UC, priv->phy + PHY_PMU_MUX_SEL);
+
+	writel(FIELD_PREP(CTRL_DFITMG0_CTRL_DELAY, tctrl_delay) |
+	       CTRL_DFITMG0_RDDATA_USE_DFI_PHY_CLK |
+	       FIELD_PREP(CTRL_DFITMG0_RDDATA_EN, trddata_en) |
+	       CTRL_DFITMG0_WRDATA_USE_DFI_PHY_CLK |
+	       FIELD_PREP(CTRL_DFITMG0_TPHY_WRDATA, PARAM_WRDATA) |
+	       FIELD_PREP(CTRL_DFITMG0_TPHY_WRLAT, tphy_wrlat),
+	       priv->ctrl + CTRL_DFITMG0);
+
+	writel(FIELD_PREP(CTRL_DFITMG1_WRDATA_DELAY, twrdata_delay) |
+	       FIELD_PREP(CTRL_DFITMG1_CLK_DIS, PARAM_CLK_DIS) |
+	       FIELD_PREP(CTRL_DFITMG1_CLK_EN, PARAM_CLK_EN),
+	       priv->ctrl + CTRL_DFITMG1);
+
+	setbits_le32(priv->ctrl + CTRL_DFIMISC, CTRL_DFIMISC_COMPLETE);
+
+	clrbits_le32(priv->ctrl + CTRL_PWRCTL, CTRL_PWRCTL_SELFREF);
+
+	setbits_le32(priv->ctrl + CTRL_SWCTL, CTRL_SWCTL_DONE);
+	ret = readl_poll_timeout(priv->ctrl + CTRL_SWSTAT, val,
+				 val == CTRL_SWSTAT_DONE, POLL_TIMEOUT);
+	if (ret)
+		return ret;
+
+	ret = readl_poll_timeout(priv->ctrl + CTRL_STAT, val,
+				 FIELD_GET(CTRL_STAT_MODE, val) == CTRL_STAT_NORMAL,
+				 POLL_TIMEOUT);
+	if (ret)
+		return ret;
+
+	clrbits_le32(priv->ctrl + CTRL_RFSHCTL3, CTRL_RFSHCTL3_DIS_AUTO);
+
+	return 0;
+}
+
+static int mt2824_ddr_init(struct udevice *dev)
+{
+	struct mt2824_ddr_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = check_firmware(dev);
+	if (ret)
+		return ret;
+
+	ret = reset_assert(&priv->rst);
+	if (ret)
+		return ret;
+
+	ret = ddr_ctrl_load_config(priv);
+	if (ret)
+		return ret;
+
+	ret = reset_deassert(&priv->rst);
+	if (ret)
+		return ret;
+
+	ret = ddr_ctrl_init(priv);
+	if (ret)
+		return ret;
+
+	ret = ddr_phy_load_comp(priv, FW_COMP_PHY_CFG);
+	if (ret)
+		return ret;
+
+	ret = ddr_phy_training(priv);
+	if (ret)
+		return ret;
+
+	ret = ddr_phy_load_comp(priv, FW_COMP_PIE_IMAGE);
+	if (ret)
+		return ret;
+
+	ret = ddr_ctrl_configure(priv);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mt2824_ddr_probe(struct udevice *dev)
+{
+	struct mt2824_ddr_priv *priv = dev_get_priv(dev);
+	struct resource res;
+	int ret;
+
+	ret = dev_read_resource_byname(dev, "ctrl", &res);
+	if (ret)
+		return ret;
+
+	priv->ctrl_size = resource_size(&res);
+	priv->ctrl = devm_ioremap(dev, res.start, priv->ctrl_size);
+
+	ret = dev_read_resource_byname(dev, "phy", &res);
+	if (ret)
+		return ret;
+
+	priv->phy_size = resource_size(&res);
+	priv->phy = devm_ioremap(dev, res.start, priv->phy_size);
+
+	ret = dev_read_resource_byname(dev, "fw", &res);
+	if (ret)
+		return ret;
+
+	priv->fw_size = resource_size(&res);
+	priv->fw = devm_ioremap(dev, res.start, priv->fw_size);
+
+	ret = reset_get_by_index(dev, 0, &priv->rst);
+	if (ret)
+		return ret;
+
+	return mt2824_ddr_init(dev);
+}
+
+static int mt2824_ddr_get_info(struct udevice *dev, struct ram_info *info)
+{
+	info->base = gd->ram_base;
+	info->size = gd->ram_size;
+
+	return 0;
+}
+
+static const struct ram_ops mt2824_ddr_ops = {
+	.get_info = mt2824_ddr_get_info,
+};
+
+static const struct udevice_id mt2824_ddr_ids[] = {
+	{ .compatible = "metanoia,mt2824-ddrc" },
+	{ }
+};
+
+U_BOOT_DRIVER(mt2824_ddr) = {
+	.name = "mt2824_ddr",
+	.id = UCLASS_RAM,
+	.ops = &mt2824_ddr_ops,
+	.of_match = mt2824_ddr_ids,
+	.probe = mt2824_ddr_probe,
+	.priv_auto = sizeof(struct mt2824_ddr_priv),
+};