From 12597f8aa3d3f692a40413283d57d6a6e7c21cb4 Mon Sep 17 00:00:00 2001 From: souldbminersmwc Date: Thu, 10 Sep 2026 19:17:08 -0400 Subject: [PATCH] DRAMID from MRR --- Source/hoc-clk/sysmodule/src/board/board.cpp | 5 + Source/hoc-clk/sysmodule/src/board/board.hpp | 1 + Source/hoc-clk/sysmodule/src/soc/dram_mrr.cpp | 393 ++++++++++++++++++ Source/hoc-clk/sysmodule/src/soc/dram_mrr.hpp | 105 +++++ 4 files changed, 504 insertions(+) create mode 100644 Source/hoc-clk/sysmodule/src/soc/dram_mrr.cpp create mode 100644 Source/hoc-clk/sysmodule/src/soc/dram_mrr.hpp diff --git a/Source/hoc-clk/sysmodule/src/board/board.cpp b/Source/hoc-clk/sysmodule/src/board/board.cpp index bc79045d..addd4c64 100644 --- a/Source/hoc-clk/sysmodule/src/board/board.cpp +++ b/Source/hoc-clk/sysmodule/src/board/board.cpp @@ -49,6 +49,7 @@ #include "board_volt.hpp" #include #include +#include "../soc/dram_mrr.hpp" namespace board { @@ -238,6 +239,10 @@ namespace board { } u8 GetDramID() { + return soc::mrr::IdentifyDramId(); + } + + u8 GetFuseDramId() { return gDramID; } diff --git a/Source/hoc-clk/sysmodule/src/board/board.hpp b/Source/hoc-clk/sysmodule/src/board/board.hpp index 453d5fa0..9cb3d1ea 100644 --- a/Source/hoc-clk/sysmodule/src/board/board.hpp +++ b/Source/hoc-clk/sysmodule/src/board/board.hpp @@ -54,6 +54,7 @@ namespace board { HocClkSocType GetSocType(); HocClkConsoleType GetConsoleType(); u8 GetDramID(); + u8 GetFuseDramId(); u8 GetGpuSpeedoBracket(); bool IsDram8GB(); void SetDisplayRefreshDockedState(bool docked); diff --git a/Source/hoc-clk/sysmodule/src/soc/dram_mrr.cpp b/Source/hoc-clk/sysmodule/src/soc/dram_mrr.cpp new file mode 100644 index 00000000..74c20a5f --- /dev/null +++ b/Source/hoc-clk/sysmodule/src/soc/dram_mrr.cpp @@ -0,0 +1,393 @@ +/* + * Copyright (c) CtCaer + * + * Copyright (c) Souldbminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "dram_mrr.hpp" +#include "../board/board.hpp" + +#include +#include +#include + +namespace soc::mrr { + constexpr u64 EmcPhysBase = 0x7001B000; + constexpr u64 Emc0PhysBase = 0x7001E000; + constexpr u64 Emc1PhysBase = 0x7001F000; + + constexpr u32 EmcRegMrr = 0xEC; + constexpr u32 EmcRegAdrCfg = 0x10; + constexpr u32 EmcRegFbioCfg7 = 0x584; + constexpr u32 EmcRegEmcStatus = 0x2B4; + constexpr u32 EmcStatusMrrDivld = (1u << 20); + constexpr int EmcStatusUpdateTimeout = 100000; + + static bool SmcRead(u64 phys_addr, u32 *out) { + SecmonArgs args = {}; + args.X[0] = 0xF0000002; + args.X[1] = phys_addr; + args.X[2] = 0; + args.X[3] = 0; + svcCallSecureMonitor(&args); + if (args.X[1] == phys_addr) { + return false; + } + *out = static_cast(args.X[1]); + return true; + } + + static bool SmcWrite(u64 phys_addr, u32 value) { + SecmonArgs args = {}; + args.X[0] = 0xF0000002; + args.X[1] = phys_addr; + args.X[2] = 0xFFFFFFFF; + args.X[3] = value; + svcCallSecureMonitor(&args); + return args.X[1] != phys_addr; + } + + static bool EmcRead(u32 offset, u32 *out) { return SmcRead(EmcPhysBase + offset, out); } + static bool EmcWrite(u32 offset, u32 value) { return SmcWrite(EmcPhysBase + offset, value); } + static bool Emc0Read(u32 offset, u32 *out) { return SmcRead(Emc0PhysBase + offset, out); } + static bool Emc1Read(u32 offset, u32 *out) { return SmcRead(Emc1PhysBase + offset, out); } + + /* 0 = ignore */ + + const DramRevisionMap rev[DRAM_COUNT] = { + /* Erista */ + { .soc = HocClkSocType_Erista, .mfg = MFG_Samsung, .major = 0, .minor = 0, .density = 0, .densityCount = 0 }, /* HB-MGCH */ + { .soc = HocClkSocType_Erista, .mfg = MFG_SKHynix, .major = 0, .minor = 0, .density = 0, .densityCount = 0 }, /* NLE */ + { .soc = HocClkSocType_Erista, .mfg = MFG_Micron, .major = 0, .minor = 0, .density = 0, .densityCount = 0 }, /* WT:C */ + + /* Mariko */ + /* Samsung */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_Samsung, .major = 6, .minor = 0x10, .density = 1, .densityCount = 2 }, /* AM-MGCJ (6.10, 4GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_Samsung, .major = 7, .minor = 0, .density = 1, .densityCount = 2 }, /* AA-MGCL (7.00, 4GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_Samsung, .major = 8, .minor = 0, .density = 1, .densityCount = 2 }, /* AB-MGCL (8.00, 4GB) */ + + /* Micron */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 4, .minor = 0x10, .density = 1, .densityCount = 2 }, /* WT:E (4.10, 4GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 5, .minor = 0, .density = 1, .densityCount = 2 }, /* WT:F (5.00, 4GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 7, .minor = 0, .density = 1, .densityCount = 2 }, /* WT:B (7.00, 4GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 7, .minor = 0, .density = 1, .densityCount = 4 }, /* WT:B (7.00, 8GB) */ + + /* SK Hynix */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 4, .minor = 0, .density = 1, .densityCount = 2 }, /* NME (4.00, 4GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 6, .minor = 0, .density = 1, .densityCount = 2 }, /* NEE (6.00, 4GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 6, .minor = 0, .density = 1, .densityCount = 4 }, /* NEE (6.00, 8GB) */ + { .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 8, .minor = 0, .density = 1, .densityCount = 2 }, /* x267 (8.00, 4GB) */ + }; + + /* Ported from hekate */ + + bool IsMrrAvailable() { + u32 val = 0; + return EmcRead(EmcRegAdrCfg, &val); + } + + static int _sdram_wait_emc_status(u32 reg_offset, u32 bit_mask, bool updated_state, s32 emc_channel) + { + for (s32 i = 0; i < EmcStatusUpdateTimeout; i++) + { + u32 val = 0; + bool ok; + if (emc_channel) + { + if (emc_channel != 1) + return 1; + + ok = Emc1Read(reg_offset, &val); + } + else + { + ok = EmcRead(reg_offset, &val); + } + + if (ok && (((val & bit_mask) != 0) == updated_state)) + return 0; + + usleep(1); + } + + return 1; + } + + static bool _sdram_req_mrr_data(u32 data, bool dual_channel) + { + if (!EmcWrite(EmcRegMrr, data)) + return false; + if (_sdram_wait_emc_status(EmcRegEmcStatus, EmcStatusMrrDivld, true, EMC_CHAN0) != 0) + return false; + if (dual_channel && _sdram_wait_emc_status(EmcRegEmcStatus, EmcStatusMrrDivld, true, EMC_CHAN1) != 0) + return false; + return true; + } + + struct EmcTopology { + u32 ranks; + u32 channels; + }; + + static bool QueryTopology(EmcTopology *topo) { + u32 tmp = 0; + if (!EmcRead(EmcRegAdrCfg, &tmp)) + return false; + topo->ranks = (tmp & 1) + 1; + if (!EmcRead(EmcRegFbioCfg7, &tmp)) + return false; + u32 ch = (tmp >> 1) & 3; + topo->channels = (ch & 1) + ((ch & 2) >> 1); + return topo->channels != 0; + } + + static void ClearMrrLeftovers() { + u32 tmp = 0; + for (u32 i = 0; i < 16; i++) + { + (void)EmcRead(EmcRegMrr, &tmp); + usleep(1); + } + } + + static void StoreMrrBytes(u32 mrr, u8 *lo, u8 *hi) { + *lo = mrr & 0xFF; + *hi = (mrr >> 8) & 0xFF; + } + + static bool ReadMrxRank(emc_mr_t mrx, bool dual_rank, bool dual_channel, emc_mr_data_t *out) { + memset(out, 0xFF, sizeof(*out)); + u32 mrr = 0; + + // Get Device 0 (Rank 0) info from both dram chips (channels). + if (!_sdram_req_mrr_data((2u << 30) | (static_cast(mrx) << 16), dual_channel)) + return false; + + // Ram module 0 info. + if (!Emc0Read(EmcRegMrr, &mrr)) + return false; + StoreMrrBytes(mrr, &out->chip0.rank0_ch0, &out->chip0.rank0_ch1); + + // Ram module 1 info. + if (dual_channel) + { + if (!Emc1Read(EmcRegMrr, &mrr)) + return false; + StoreMrrBytes(mrr, &out->chip1.rank0_ch0, &out->chip1.rank0_ch1); + } + + // If Rank 1 exists, get info. + if (dual_rank) + { + // Get Device 1 (Rank 1) info from both dram chips (channels). + if (!_sdram_req_mrr_data((1u << 30) | (static_cast(mrx) << 16), dual_channel)) + return false; + + // Ram module 0 info. + if (!Emc0Read(EmcRegMrr, &mrr)) + return false; + StoreMrrBytes(mrr, &out->chip0.rank1_ch0, &out->chip0.rank1_ch1); + + // Ram module 1 info. + if (dual_channel) + { + if (!Emc1Read(EmcRegMrr, &mrr)) + return false; + StoreMrrBytes(mrr, &out->chip1.rank1_ch0, &out->chip1.rank1_ch1); + } + } + + return true; + } + + static bool ReadMrxSet(emc_mr_data_t *vendor, emc_mr_data_t *rev0, emc_mr_data_t *rev1, + emc_mr_data_t *density, EmcTopology *topo) { + EmcTopology t = {}; + if (!QueryTopology(&t)) + return false; + const bool dual_rank = t.ranks > 1; + const bool dual_channel = t.channels > 1; + + ClearMrrLeftovers(); + + if (!ReadMrxRank(MR5_MAN_ID, dual_rank, dual_channel, vendor)) + return false; + if (!ReadMrxRank(MR6_REV_ID1, dual_rank, dual_channel, rev0)) + return false; + if (!ReadMrxRank(MR7_REV_ID2, dual_rank, dual_channel, rev1)) + return false; + if (!ReadMrxRank(MR8_DENSITY, dual_rank, dual_channel, density)) + return false; + + *topo = t; + return true; + } + + emc_mr_data_t sdram_read_mrx(emc_mr_t mrx) + { + emc_mr_data_t data; + memset(&data, 0xFF, sizeof(emc_mr_data_t)); + + EmcTopology topo = {}; + if (!QueryTopology(&topo)) + return data; + + ClearMrrLeftovers(); + + (void)ReadMrxRank(mrx, topo.ranks > 1, topo.channels > 1, &data); + return data; + } + + bool ReadRamMr4(u8 *mr4) { + EmcTopology topo = {}; + if (!QueryTopology(&topo)) + return false; + + ClearMrrLeftovers(); + + emc_mr_data_t data = {}; + if (!ReadMrxRank(MR4_TEMP, topo.ranks > 1, topo.channels > 1, &data)) + return false; + + *mr4 = data.chip0.rank0_ch0; + return true; + } + + /* MR8 density field (bits 5:2) to MB */ + static bool DecodeDensityMb(u8 mr8, u32 *mb) { + switch ((mr8 & 0x3C) >> 2) + { + case 2: *mb = 512; break; + case 3: *mb = 768; break; + case 4: *mb = 1024; break; + case 5: *mb = 1536; break; + case 6: *mb = 2048; break; + default: return false; + } + return true; + } + + static bool MatchModule(HocClkSocType soc, DramMfg mfg, u8 major, u8 minor, + u32 densityMb, u32 densityCount, RealDramModule *mod) { + for (int i = 0; i < DRAM_COUNT; i++) + { + const DramRevisionMap &e = rev[i]; + if (e.soc != soc || e.mfg != mfg) + continue; + if (soc != HocClkSocType_Erista) + { + if (e.major != major || e.minor != minor) + continue; + if (e.density * 1024u != densityMb) + continue; + if (e.densityCount != densityCount) + continue; + } + *mod = static_cast(i); + return true; + } + return false; + } + + static u8 FuseIdForModule(RealDramModule mod, HocClkConsoleType console, u8 fuseFallback) { + switch (mod) + { + case DRAM_HBMGCH: + case DRAM_NLE: + case DRAM_WTC: + return fuseFallback; + case DRAM_AMMGCJ: + if (console == HocClkConsoleType_Hoag) return 12; + if (console == HocClkConsoleType_Iowa) return 8; + return fuseFallback; + case DRAM_AAMGCL: + if (console == HocClkConsoleType_Iowa) return 17; + if (console == HocClkConsoleType_Hoag) return 19; + if (console == HocClkConsoleType_Aula) return 24; + return fuseFallback; + case DRAM_ABMGCL: + if (console == HocClkConsoleType_Iowa) return 20; + if (console == HocClkConsoleType_Hoag) return 21; + if (console == HocClkConsoleType_Aula) return 22; + return fuseFallback; + case DRAM_WTE: + if (console == HocClkConsoleType_Iowa) return 11; + if (console == HocClkConsoleType_Hoag) return 15; + return fuseFallback; + case DRAM_WTF: + if (console == HocClkConsoleType_Iowa) return 25; + if (console == HocClkConsoleType_Hoag) return 26; + if (console == HocClkConsoleType_Aula) return 27; + return fuseFallback; + case DRAM_WTB: + if (console == HocClkConsoleType_Iowa) return 32; + if (console == HocClkConsoleType_Hoag) return 33; + if (console == HocClkConsoleType_Aula) return 34; + return fuseFallback; + case DRAM_WTB_8GB: + case DRAM_NEE_8GB: + /* Used in atmosphere and commonly burnt for 8gb upgrade */ + return 28; + case DRAM_NME: + if (console == HocClkConsoleType_Iowa) return 10; + if (console == HocClkConsoleType_Hoag) return 14; + return fuseFallback; + case DRAM_NEE: + if (console == HocClkConsoleType_Iowa) return 6; + if (console == HocClkConsoleType_Hoag) return 3; + if (console == HocClkConsoleType_Aula) return 5; + return fuseFallback; + case DRAM_X267: + if (console == HocClkConsoleType_Iowa) return 29; + if (console == HocClkConsoleType_Hoag) return 30; + if (console == HocClkConsoleType_Aula) return 31; + return fuseFallback; + default: + return fuseFallback; + } + } + + u8 IdentifyDramId() { + const u8 fuseId = board::GetFuseDramId(); + const HocClkSocType soc = board::GetSocType(); + + if (soc == HocClkSocType_Erista) + return fuseId; + + if (!IsMrrAvailable()) + return fuseId; + + emc_mr_data_t vendor = {}, rev0 = {}, rev1 = {}, density = {}; + EmcTopology topo = {}; + if (!ReadMrxSet(&vendor, &rev0, &rev1, &density, &topo)) + return fuseId; + + DramMfg mfg = static_cast(vendor.chip0.rank0_ch0); + + u32 densityMb = 0; + if (!DecodeDensityMb(density.chip0.rank0_ch0, &densityMb)) + return fuseId; + + RealDramModule mod = DRAM_COUNT; + if (!MatchModule(soc, mfg, rev0.chip0.rank0_ch0, rev1.chip0.rank0_ch0, + densityMb, topo.ranks * topo.channels, &mod)) + return fuseId; + + return FuseIdForModule(mod, board::GetConsoleType(), fuseId); + } + + +} \ No newline at end of file diff --git a/Source/hoc-clk/sysmodule/src/soc/dram_mrr.hpp b/Source/hoc-clk/sysmodule/src/soc/dram_mrr.hpp new file mode 100644 index 00000000..c37f540a --- /dev/null +++ b/Source/hoc-clk/sysmodule/src/soc/dram_mrr.hpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) CtCaer + * + * Copyright (c) Souldbminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include + +namespace soc::mrr { + typedef enum RealDramModule { + /* Erista */ + DRAM_HBMGCH = 0, + DRAM_NLE, + DRAM_WTC, + + /* Mariko */ + + /* Samsung */ + DRAM_AMMGCJ, + DRAM_AAMGCL, + DRAM_ABMGCL, + + /* Micron */ + DRAM_WTE, + DRAM_WTF, + DRAM_WTB, + DRAM_WTB_8GB, + + /* SK Hynix */ + DRAM_NME, + DRAM_NEE, + DRAM_NEE_8GB, + DRAM_X267, + DRAM_COUNT, + } RealDramModule; + + typedef enum _emc_mr_t + { + MR0_FEAT = 0, + MR4_TEMP = 4, + MR5_MAN_ID = 5, + MR6_REV_ID1 = 6, + MR7_REV_ID2 = 7, + MR8_DENSITY = 8, + } emc_mr_t; + + enum + { + EMC_CHAN0 = 0, + EMC_CHAN1 = 1 + }; + + typedef struct _emc_mr_chip_data_t + { + // Device 0. + u8 rank0_ch0; + u8 rank0_ch1; + + // Device 1. + u8 rank1_ch0; + u8 rank1_ch1; + } emc_mr_chip_data_t; + + typedef struct _emc_mr_data_t + { + emc_mr_chip_data_t chip0; + emc_mr_chip_data_t chip1; + } emc_mr_data_t; + + typedef enum DramMfg { + MFG_Samsung = 1, + MFG_SKHynix = 6, + MFG_Micron = 255, + } DramMfg; + + typedef struct DramRevisionMap { + HocClkSocType soc; + DramMfg mfg; + u8 major; + u8 minor; + u8 density; // GB per die + u8 densityCount; // number of dies (2 = 4GB, 4 = 8GB) + } DramRevisionMap; + + extern const DramRevisionMap rev[DRAM_COUNT]; + + emc_mr_data_t sdram_read_mrx(emc_mr_t mrx); + bool IsMrrAvailable(); + + bool ReadRamMr4(u8 *mr4); + + u8 IdentifyDramId(); +} \ No newline at end of file