improve timings

This commit is contained in:
Lightos1 committed 2026-09-06 15:37:36 +02:00
1 parent 763ef09020
commit bd6d615143
13 files changed
+566 -436

No files matched your search

@@ -0,0 +1,186 @@
/*
* Copyright (c) Lightos_
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../../oc_common.hpp"
#include "../../mtc_timing_value.hpp"
namespace ams::ldr::hoc::pcv {
namespace {
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
return;
}
}
SwitchLatency(latency, indexMax, latencyStep);
}
u32 RoundUpToEven(u32 value) {
return value + (value % 2);
}
double GetTckAvg(u32 khz) {
return 1000'000.0 / static_cast<double>(khz);
}
}
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
u32 GetRlFrequency() {
switch (RL) {
case RL_1333:
return 1333'000;
case RL_1600:
return 1600'000;
case RL_1866:
return 1866'000;
case RL_2133:
return 2133'000;
default:
return 2133'000;
}
}
u32 GetWlFrequency() {
switch (WL) {
case WL_1333:
return 1333'000;
case WL_1600:
return 1600'000;
case WL_1866:
return 1866'000;
case WL_2133:
return 2133'000;
default:
return 2133'000;
}
}
u32 GetWdqsOn() {
/* Set A. */
switch (WL) {
case WL_1333:
return 4;
case WL_1600:
case WL_1866:
return 6;
case WL_2133:
return 8;
default:
return 8;
}
}
u32 CalculateNWr() {
const u32 latencyFrequency = GetWlFrequency();
const double tCK = GetTckAvg(latencyFrequency);
const u32 nWr = CEIL(tWR / tCK);
return RoundUpToEven(nWr);
}
u32 CalculateNTRTP() {
const u32 latencyFrequency = GetRlFrequency();
const double tCK = GetTckAvg(latencyFrequency);
return CEIL(tRTP / tCK);
}
u8 CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == RL) {
rlIndex = i;
break;
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
/* DBI is always enabled. */
return static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
}
}
@@ -15,12 +15,20 @@
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::ldr::hoc::pcv::mariko {
namespace ams::ldr::hoc::pcv {
extern const RextPatch g_rext_table[];
extern const u32 g_rext_table_size;
const RextPatch *FindRext(u32 freq);
struct RextPatch {
u32 freq;
u32 rext;
};
void HandleLatency(u32 freq);
u32 GetRlFrequency();
u32 GetWlFrequency();
u32 GetWdqsOn();
u32 CalculateNWr();
u32 CalculateNTRTP();
u8 CalculateMrw2();
}
@@ -15,189 +15,174 @@
*/
#include "../../mtc_timing_value.hpp"
#include "../common/calculate_timings_common.hpp"
namespace ams::ldr::hoc::pcv::erista {
/* Dumped from bpmp fw mtc table (l4t). */
const RextPatch g_rext_table[] = {
{1'633'000, 0x17}, {1'728'000, 0x17}, {1'795'200, 0x19},
{1'862'400, 0x1A}, {1'894'400, 0x1A}, {1'932'800, 0x1A},
{1'958'400, 0x1A}, {1'996'800, 0x1A}, {2'035'200, 0x1A},
{2'064'000, 0x19}, {2'099'200, 0x19}, {2'131'200, 0x1A},
{2'163'200, 0x1A}, {2'188'800, 0x1A}, {2'227'200, 0x1A},
{2'265'600, 0x1B}, {2'291'200, 0x1B}, {2'329'600, 0x1A},
{2'361'600, 0x1A},
};
namespace {
/* Dumped from bpmp fw mtc table (l4t). */
const RextPatch gRextTable[] = {
{1'633'000, 0x17}, {1'728'000, 0x17}, {1'795'200, 0x19},
{1'862'400, 0x1A}, {1'894'400, 0x1A}, {1'932'800, 0x1A},
{1'958'400, 0x1A}, {1'996'800, 0x1A}, {2'035'200, 0x1A},
{2'064'000, 0x19}, {2'099'200, 0x19}, {2'131'200, 0x1A},
{2'163'200, 0x1A}, {2'188'800, 0x1A}, {2'227'200, 0x1A},
{2'265'600, 0x1B}, {2'291'200, 0x1B}, {2'329'600, 0x1A},
{2'361'600, 0x1A},
};
const RextPatch *LookupRext(u32 freq) {
for (u32 i = 0; i < std::size(g_rext_table); i++) {
if (g_rext_table[i].freq >= freq) {
return &g_rext_table[i];
const RextPatch *LookupRext(u32 freq) {
for (u32 i = 0; i < std::size(gRextTable); i++) {
if (gRextTable[i].freq >= freq) {
return &gRextTable[i];
}
}
}
return nullptr;
}
void GetRext(u32 freq) {
if (auto r = LookupRext(freq)) {
rext = r->rext;
return;
return nullptr;
}
/* Fallback */
rext = 0x1A;
}
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
void GetRext(u32 freq) {
if (auto r = LookupRext(freq)) {
rext = r->rext;
return;
}
/* Fallback */
rext = 0x1A;
}
SwitchLatency(latency, indexMax, latencyStep);
}
void CalculateReadTimings(u32 freq, double tCK_avg) {
tR2P = CalculateNTRTP();
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
double fullRoundDelay = flyByTime + tDQSCK_max;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
/* Read to write turnaround. */
/* Nvidia seem to use a 0.25 constant here for tK1, I am 99% certain this is tDQSTA. */
/* This still left a mismatch; after some brute forcing I got GET_CYCLE_FLOOR(1.5) - 8 (using a static latency) as the cleanest result. */
/* The latency definitely is not WL, but when testing for every latency, it lines up perfectly with wqsOn and wl seems to cancel out? */
tR2W = RL + (BL / 2) + CEIL((fullRoundDelay / tCK_avg) + tDQSTA) - GET_CYCLE_FLOOR(1.5) - GetWdqsOn() + tWPRE - (C.t6_tRTW * 3) + finetRTW;
/* Read data valid: Read command -> data ready to be latched into the registers. */
rdv = RL + GET_CYCLE_CEIL(fullRoundDelay) + 18;
/* Read command -> data poppable at the pad macros, 16 cycles ahead of rdv. */
qpop = rdv - 16;
constexpr double TrimmerStepPs = 3.0;
constexpr double MaxTrimmerSteps = 95.0;
double tCkAvgPs = tCK_avg * 1000.0;
double tckDerated = tCkAvgPs * 0.85;
u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
/* readEyeEndDerated, nv call this quseExtra. */
u32 quseExtra = GET_CYCLE_CEIL(fullRoundDelay - deratedNsDelay);
/* The amount of deration from the perspective of a full round trip. */
u32 readEyeEndDerateMargin = GET_CYCLE_CEIL(fullRoundDelay) - quseExtra;
u32 tQuse = FLOOR((flyByTime + 0.94) / tCK_avg);
u32 readEyeStart = tQuse + RL + 1;
u32 einputMinimal = readEyeStart + readEyeEndDerateMargin - 2;
/* Data valid window, quse aligns with the start of the read eye (probably). */
/* DQS needs to be logically and'ed with quse in order to be valid. */
/* einputMinimal satisfies this. */
quse = einputMinimal;
/* Difference between the two eye edges. */
quse_width = quseExtra - tQuse + 4;
/* input receiver takes time to enable. */
u32 einputLatency = GET_CYCLE_CEIL(10.0);
/* Enable input receiver. */
einput = einputMinimal - einputLatency;
/* Duration of input receiver being enabled? */
einput_duration = einputLatency + quse_width + 4;
/* qrst_duration is CEIL(a / tCK_avg + b) */
/* a and b were derived through a brute forcer. */
u32 qrst_duration = CEIL(1.4 / tCK_avg + 3.5);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
/* quse reset? */
qrst = PACK_U32(qrst_duration, qrstLow);
/* Qsafe, safe to reset quse? */
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
/* Input buffer delay? */
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
GetRext(freq);
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
void CalculateWriteTimings(double tCK_avg) {
/* Write to read turnaround. */
/* According to jedec specifications: nWR >= tWTR + nRTP */
/* Therefore tWTR >= nWR - tRTP. */
const u32 nTWTR = GET_CYCLE_CEIL(tWTRStock);
const u32 nWr = CalculateNWr();
u32 autoPrecharcetWTRMin = nWr - tR2P;
/* tWTR reduction is applied in a weird way to not break existing setups. */
tW2R = WL + 1 + MAX(autoPrecharcetWTRMin, nTWTR) - GET_CYCLE_CEIL(tWTR) + finetWTR;
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.8 / tCK_avg) + MAX(RL + (2.7 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
wdv = WL;
wsv = WL - 2;
wev = WL - 4;
/* Output buffer delay? */
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
void CalculatePowerDownTimings(double tCK_avg) {
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
const double tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
}
}
void CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
u8 GetNWrIndex() {
static const u8 nWrMap[8] = {
6, 10, 16, 20, 24, 30, 34, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
const u32 nWr = CalculateNWr();
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == 32) {
rlIndex = i;
break;
for (u8 i = 0; i < std::size(nWrMap); ++i) {
if (nWrMap[i] >= nWr) {
return i;
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
/* DBI is always enabled. */
mrw2 = static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
return static_cast<u8>(std::size(nWrMap) - 1);
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1331;
WL = WL_1331;
RL = RL_1333;
WL = WL_1333;
/* I don't understand why, I don't want to know why, but for some fucking reason erista doesn't handle RL 40. */
/* This should not be required but for these fuck damn reasons any type of dram oc will crash instantly with RL 40. */
/* But it works fine in l4t? Even when copying the tables it still doesn't work??? WTF */
readLatency[std::size(readLatency) - 1] = 0;
HandleLatency(freq);
GetRext(freq);
CalculateMrw2();
RL = std::min(static_cast<u32>(RL_1866), RL);
tR2P = CEIL((RL * 0.426) - 2.0);
tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (C.t6_tRTW * 3) + finetRTW;
u32 fullRoundDelay = CEIL((flyByTime + tDQSCK_max) / tCK_avg);
rdv = RL + fullRoundDelay + 19;
qpop = rdv - 16;
constexpr double TrimmerStepPs = 3.0;
constexpr double MaxTrimmerSteps = 96.0;
double tCkAvgPs = tCK_avg * 1000.0;
double tckDerated = tCkAvgPs * 0.85;
u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
u32 fullTripDerating = fullRoundDelay - CEIL((flyByTime + tDQSCK_max - deratedNsDelay) / tCK_avg);
u32 readEyeStart = FLOOR((flyByTime + 0.94) / tCK_avg) + RL;
/* Data valid window, quse aligns with the start of the read eye (probably). */
/* DQS needs to be logically and'ed with quse in order to be valid. */
quse = readEyeStart + fullTripDerating - 2;
quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
einput = quse - CEIL(9.928 / tCK_avg);
u32 qrst_duration = FLOOR(8.399 - tCK_avg);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
qrst = PACK_U32(qrst_duration, qrstLow);
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
const s32 wlToRl = static_cast<s32>(WL) - static_cast<s32>(RL);
tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * wlToRl), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
wdv = WL;
wsv = WL - 2;
wev = 0xA + (WL - 14);
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
const double tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
CalculateReadTimings(freq, tCK_avg);
CalculateWriteTimings(tCK_avg);
CalculatePowerDownTimings(tCK_avg);
mrw2 = CalculateMrw2();
}
}
@@ -18,6 +18,7 @@
namespace ams::ldr::hoc::pcv::erista {
u8 GetNWrIndex();
void CalculateTimings(double tCK_avg, u32 freq);
}
@@ -46,6 +46,7 @@ namespace ams::ldr::hoc::pcv::erista {
#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
TABLE->burst_regs.PARAM = VALUE; \
TABLE->shadow_regs_ca_train.PARAM = VALUE; \
TABLE->shadow_regs_quse_train.PARAM = VALUE; \
TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
/* Ram power down */
@@ -109,28 +110,28 @@ namespace ams::ldr::hoc::pcv::erista {
/* Accept imperfection or prepare for suffering. */
// #if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
// WRITE_PARAM_ALL_REG(table, emc_einput, einput);
// WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
// WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
WRITE_PARAM_ALL_REG(table, emc_einput, einput);
WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
// WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
// WRITE_PARAM_ALL_REG(table, emc_quse, quse);
WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
WRITE_PARAM_ALL_REG(table, emc_quse, quse);
WRITE_PARAM_ALL_REG(table, emc_wdv, wdv);
WRITE_PARAM_ALL_REG(table, emc_wsv, wsv);
WRITE_PARAM_ALL_REG(table, emc_wev, wev);
// WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
// WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
// WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
// WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
// WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
// WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
// WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
// WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
// WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
// WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
// WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
// WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast<u32>(mrw2);
table->dram_timings.rl = RL;
// #endif
@@ -223,8 +224,7 @@ namespace ams::ldr::hoc::pcv::erista {
const u32 tRFCabStock = tRFC_values[0] * 2;
table->dram_timings.t_rfc = tRFCabStock;
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast<u32>(mrw2);
table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */
table->emc_mrw = (table->emc_mrw & ~0x70u) | (GetNWrIndex() << 4);
table->emc_cfg_2 = 0x11083D;
table->min_volt = std::clamp(900 + (C.emcDvbShift * 25), 900, 1050);
}
@@ -14,227 +14,209 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "../../mtc_timing_value.hpp"
#include "timing_tables.hpp"
#include "../common/calculate_timings_common.hpp"
namespace ams::ldr::hoc::pcv::mariko {
void GetRext(u32 freq) {
if (auto r = FindRext(freq)) {
rext = r->rext;
return;
}
namespace {
const RextPatch gRextTable[] = {
{1'633'000, 0x17}, {1'666'000, 0x18}, {1'700'000, 0x18},
{1'733'000, 0x18}, {1'766'000, 0x18}, {1'800'000, 0x1A},
{1'833'000, 0x1A}, {1'866'000, 0x1A}, {1'900'000, 0x19},
{1'933'000, 0x19}, {1'966'000, 0x19}, {2'100'000, 0x1A},
{2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
{2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
{2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
{2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
{2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
{2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
{2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
{2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
{2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
{3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
{3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
{3'233'000, 0x1E}, {3'266'000, 0x1F}, {3'300'000, 0x1E},
{3'333'000, 0x1F}, {3'366'000, 0x1F}, {3'400'000, 0x1F},
{3'433'000, 0x1F}, {3'466'000, 0x1F}, {3'500'000, 0x1E},
{3'533'000, 0x1E}, {3'566'000, 0x1E}, {3'600'000, 0x1E},
{3'633'000, 0x1E}, {3'666'000, 0x1E}, {3'700'000, 0x1F},
};
/* Fallback */
rext = 0x1E;
}
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
const RextPatch *LookupRext(u32 freq) {
for (u32 i = 0; i < std::size(gRextTable); i++) {
if (gRextTable[i].freq >= freq) {
return &gRextTable[i];
}
}
return nullptr;
}
return maxIndex;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
void GetRext(u32 freq) {
if (auto r = LookupRext(freq)) {
rext = r->rext;
return;
}
/* Fallback */
rext = 0x1E;
}
SwitchLatency(latency, indexMax, latencyStep);
}
void CalculateActivatePrechargeTimings(bool lowFreq) {
tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
tRC = tRAS + tRPpb;
tRPab = tRPpb + 3;
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
void CalculateActivateWindowTimings(bool lowFreq) {
tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
tFAW = static_cast<u32>(tRRD * 4.0);
}
if (autoLatencyRead && autoLatencyWrite) {
return;
void CalculateReadTimings(u32 freq, double tCK_avg, bool lowFreq) {
u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
s32 finetRTW = C.fineTune_t6_tRTW;
double fullRoundDelay = flyByTime + tDQSCK_max;
/* Read to precharge delay. */
tR2P = CalculateNTRTP();
/* Read to write turnaround. */
/* Nvidia seem to use a 0.25 constant here for tK1, I am 99% certain this is tDQSTA. */
/* This still left a mismatch; after some brute forcing I got GET_CYCLE_FLOOR(1.5) - 8 (using a static latency) as the cleanest result. */
/* The latency definitely is not WL, but when testing for every latency, it lines up perfectly with wqsOn and wl seems to cancel out? */
tR2W = RL + (BL / 2) + CEIL((fullRoundDelay / tCK_avg) + tDQSTA) - GET_CYCLE_FLOOR(1.5) - GetWdqsOn() - (tRTW * 3) + finetRTW;
tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + GET_CYCLE_FLOOR(7.489);
tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
/* Read data valid: Read command -> data ready to be latched into the registers. */
rdv = RL + GET_CYCLE_CEIL(fullRoundDelay) + 16;
/* Read command -> data poppable at the pad macros, 14 cycles ahead of rdv. */
qpop = rdv - 14;
constexpr double TrimmerStepPs = 3.0;
constexpr double MaxTrimmerSteps = 96.0;
double tCkAvgPs = tCK_avg * 1000.0;
double tckDerated = tCkAvgPs * 0.85;
u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
/* readEyeEndDerated, nv call this quseExtra. */
u32 quseExtra = GET_CYCLE_CEIL(fullRoundDelay - deratedNsDelay);
/* The amount of deration from the perspective of a full round trip. */
u32 readEyeEndDerateMargin = GET_CYCLE_CEIL(fullRoundDelay) - quseExtra;
u32 tQuse = FLOOR((flyByTime + 0.94) / tCK_avg);
u32 readEyeStart = tQuse + RL + 1;
u32 einputMinimal = readEyeStart + readEyeEndDerateMargin - 2;
/* Data valid window, quse aligns with the start of the read eye (probably). */
/* DQS needs to be logically and'ed with quse in order to be valid. */
/* einputMinimal satisfies this. */
quse = einputMinimal;
/* input receiver takes time to enable. */
u32 einputLatency = GET_CYCLE_CEIL(10.0);
/* Enable input receiver. */
einput = einputMinimal - einputLatency;
/* Difference between the two eye edges. */
quse_width = quseExtra - tQuse + 4;
/* Duration of input receiver being enabled? */
einput_duration = einputLatency + quse_width + 4;
/* qrst_duration is CEIL(a / tCK_avg + b) */
/* a and b were derived through a brute forcer. */
u32 qrst_duration = CEIL(1.4 / tCK_avg + 3.5);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
/* quse reset? */
qrst = PACK_U32(qrst_duration, qrstLow);
/* Input buffer delay? */
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
/* Qsafe, safe to reset quse? */
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
GetRext(freq);
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
void CalculateWriteTimings(double tCK_avg, bool lowFreq) {
u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
s32 finetWTR = C.fineTune_t7_tWTR;
/* Write to read turnaround. */
/* According to jedec specifications: nWR >= tWTR + nRTP */
/* Therefore tWTR >= nWR - tRTP. */
const u32 nTWTR = GET_CYCLE_CEIL(tWTRStock);
const u32 nWr = CalculateNWr();
u32 autoPrecharcetWTRMin = nWr - tR2P;
/* tWTR reduction is applied in a weird way to not break existing setups. */
tW2R = WL + 1 + MAX(autoPrecharcetWTRMin, nTWTR) - GET_CYCLE_CEIL(tWTR) + finetWTR;
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
wdv = WL;
wsv = WL - 2;
wev = WL - 4;
/* Output buffer delay? */
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
void CalculateRefreshTimings(double tCK_avg, bool lowFreq) {
tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
void CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == RL) {
rlIndex = i;
break;
u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
refresh_raw = 0xFFFF;
if (tREFI != 6) {
refresh_raw = GET_CYCLE_CEIL(tREFpb_values[tREFI]) - 0x40;
refresh_raw = MIN(refresh_raw, static_cast<u32>(0xFFFF));
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
void CalculatePowerDownTimings(double tCK_avg) {
tWTPDEN = CEIL(((1.8 / tCK_avg) + MAX(RL + (2.7 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
/* DBI is always enabled. */
mrw2 = static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
double tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
}
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1331;
WL = WL_1331;
RL = RL_1333;
WL = WL_1333;
HandleLatency(freq);
GetRext(freq);
const bool lowFreq = freq < C.timingEmcTbreak;
tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
s32 finetRTW = C.fineTune_t6_tRTW;
s32 finetWTR = C.fineTune_t7_tWTR;
u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
refresh_raw = 0xFFFF;
if (tREFI != 6) {
refresh_raw = GET_CYCLE_CEIL(tREFpb_values[tREFI]) - 0x40;
refresh_raw = MIN(refresh_raw, static_cast<u32>(0xFFFF));
}
tRC = tRAS + tRPpb;
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
tFAW = static_cast<u32>(tRRD * 4.0);
tRPab = tRPpb + 3;
tR2P = CEIL((RL * 0.426) - 2.0);
tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
double fullRoundDelay = flyByTime + tDQSCK_max;
/* Read data valid: Read command -> data ready to be latched into the registers. */
rdv = RL + GET_CYCLE_CEIL(fullRoundDelay) + 16;
/* Read command -> data poppable at the pad macros, 14 cycles ahead of rdv. */
qpop = rdv - 14;
constexpr double TrimmerStepPs = 3.0;
constexpr double MaxTrimmerSteps = 96.0;
double tCkAvgPs = tCK_avg * 1000.0;
double tckDerated = tCkAvgPs * 0.85;
u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
/* readEyeEndDerated, nv call this quseExtra. */
u32 quseExtra = GET_CYCLE_CEIL(fullRoundDelay - deratedNsDelay);
/* The amount of deration from the perspective of a full round trip. */
u32 readEyeEndDerateMargin = GET_CYCLE_CEIL(fullRoundDelay) - quseExtra;
u32 tQuse = FLOOR((flyByTime + 0.94) / tCK_avg);
u32 readEyeStart = tQuse + RL + 1;
u32 einputMinimal = readEyeStart + readEyeEndDerateMargin - 2;
/* Data valid window, quse aligns with the start of the read eye (probably). */
/* DQS needs to be logically and'ed with quse in order to be valid. */
/* einputMinimal satisfies this. */
quse = einputMinimal;
/* input receiver takes time to enable. */
u32 einputLatency = GET_CYCLE_CEIL(10.0);
/* Enable input receiver. */
einput = einputMinimal - einputLatency;
/* Difference between the two eye edges. */
quse_width = quseExtra - tQuse + 4;
/* Duration of input receiver being enabled? */
einput_duration = einputLatency + quse_width + 4;
/* qrst_duration is CEIL(a / tCK_avg + b) */
/* a and b were derived through a brute forcer. */
u32 qrst_duration = CEIL(1.4 / tCK_avg + 3.5);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
qrst = PACK_U32(qrst_duration, qrstLow);
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.8 / tCK_avg) + MAX(RL + (2.7 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
const s32 wlToRl = static_cast<s32>(WL) - static_cast<s32>(RL);
tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * wlToRl), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
wdv = WL;
wsv = WL - 2;
wev = 0xA + (WL - 14);
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
u32 tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
CalculateMrw2();
CalculateActivatePrechargeTimings(lowFreq);
CalculateActivateWindowTimings(lowFreq);
CalculateReadTimings(freq, tCK_avg, lowFreq);
CalculateWriteTimings(tCK_avg, lowFreq);
CalculateRefreshTimings(tCK_avg, lowFreq);
CalculatePowerDownTimings(tCK_avg);
mrw2 = CalculateMrw2();
}
}
@@ -21,4 +21,3 @@ namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings(double tCK_avg, u32 freq);
}
@@ -57,13 +57,12 @@ namespace ams::ldr::hoc::pcv::mariko {
WRITE_PARAM_ALL_REG(table, emc_cfg, 0xF3200000);
}
CalculateTimings(tCK_avg, table->rate_khz);
u32 trefbw = refresh_raw + 0x40;
trefbw = MIN(trefbw, static_cast<u32>(0x3FFF));
const u32 dyn_self_ref_control = (static_cast<u32>(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000);
CalculateTimings(tCK_avg, table->rate_khz);
WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_rc, MIN(GET_CYCLE_CEIL(tRC), static_cast<u32>(0xB9)));
@@ -1,47 +0,0 @@
/*
* Copyright (c) Lightos_
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../../oc_common.hpp"
#include "timing_tables.hpp"
namespace ams::ldr::hoc::pcv::mariko {
const RextPatch g_rext_table[] = {
{2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
{2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
{2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
{2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
{2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
{2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
{2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
{2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
{2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
{3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
{3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
};
const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]);
const RextPatch *FindRext(u32 freq) {
for (u32 i = 0; i < g_rext_table_size; i++) {
if (g_rext_table[i].freq >= freq) {
return &g_rext_table[i];
}
}
return nullptr;
}
}
@@ -187,11 +187,6 @@ namespace ams::ldr::hoc::pcv {
};
static_assert(sizeof(SharedClkBus) == 0x58);
struct RextPatch {
u32 freq;
u32 rext;
};
constexpr u32 CpuClkOSLimit = 1785'000;
constexpr u32 GpuClkOsLimit = 921'600;
constexpr u32 EmcClkOSLimit = 1600'000;