DF-4858 / mclkss.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | /* * DF-4858 — verbatim userland replica of the memory-clock spread-spectrum * block of rv730_populate_mclk_value() (sys/dev/drm/radeon/rv730_dpm.c:119-196), * in particular the wrong-variable store: * * rv730_dpm.c:180 mpll_ss2 &= ~CLK_V_MASK; * rv730_dpm.c:181 mpll_ss |= CLK_V(clk_v); <-- TYPO: must be mpll_ss2 * * Consequences replicated here: * (V1) the CLK_S field (bits 4-15, just programmed at :177) of mpll_ss is * clobbered by the low bits of clk_v; * (V2) the CLK_V field (bits 0-25) of mpll_ss2 is cleared and never set: * the SMC state table receives vMPLL_SS2 with SS depth 0 while SSEN * is enabled in vMPLL_SS -> corrupted mclk spread-spectrum programming. * * Register macros verbatim from sys/dev/drm/radeon/rv730d.h:45-65,132-138. * Proof of intent: rv740_dpm.c:262-266 (same tree) programs CLKV/CLKS into * the correct words; upstream Linux's rv730 sclk block in the same function * pair uses cg_spll_spread_spectrum_2 |= CLK_V(clk_v) — only this mclk block * has the typo (present in upstream Linux master as well). * * Modes: * real realistic SS-enabled VBIOS values -> silent field corruption * crafted max-amplitude SS values -> large CLK_S clobber * Build with -DFIX_DF4858 to compile the one-line fix into :181. */ #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef uint32_t u32; typedef uint64_t u64; typedef uint8_t u8; typedef uint16_t u16; #define cpu_to_be32(x) __builtin_bswap32(x) /* ---- rv730d.h (verbatim) ---- */ #define MPLL_DIVEN (1 << 2) #define MPLL_REF_DIV(x) ((x) << 4) #define MPLL_REF_DIV_MASK (0x3f << 4) #define MPLL_HILEN(x) ((x) << 12) #define MPLL_HILEN_MASK (0xf << 12) #define MPLL_LOLEN(x) ((x) << 16) #define MPLL_LOLEN_MASK (0xf << 16) #define MPLL_FB_DIV(x) ((x) << 0) #define MPLL_FB_DIV_MASK (0x3ffffff << 0) #define MPLL_DITHEN (1 << 28) #define SSEN (1 << 0) #define CLK_S(x) ((x) << 4) #define CLK_S_MASK (0xfff << 4) #define CLK_V(x) ((x) << 0) #define CLK_V_MASK (0x3ffffff << 0) struct atom_clock_dividers { u32 fb_div, ref_div, post_div; int enable_post_div, enable_dithen; }; struct radeon_atom_ss { u16 percentage, rate; }; struct mclk730 { u32 vMCLK_PWRMGT_CNTL, vDLL_CNTL, mclk_value, vMPLL_FUNC_CNTL, vMPLL_FUNC_CNTL2, vMPLL_FUNC_CNTL3, vMPLL_SS, vMPLL_SS2; }; /* ==== replica of rv730_populate_mclk_value SS path (rv730_dpm.c:119-196) ==== */ static int replica_mclk_value(u32 memory_clock, u32 mpll_reference_freq_boot, u32 mpll_ss_boot, u32 mpll_ss2_boot, struct atom_clock_dividers *dividers, struct radeon_atom_ss *ss, struct mclk730 *mclk) { u32 mpll_func_cntl = 0x00290043, mpll_func_cntl_2 = 0x00000301, mpll_func_cntl_3 = 0x00018a60; u32 mclk_pwrmgt_cntl = 0x00000000, dll_cntl = 0x00000000; u32 mpll_ss = mpll_ss_boot; /* :129 */ u32 mpll_ss2 = mpll_ss2_boot; /* :130 */ u32 post_divider, reference_divider; int violations = 0; reference_divider = dividers->ref_div + 1; /* :140 */ if (dividers->enable_post_div) /* :142-146 */ post_divider = ((dividers->post_div >> 4) & 0xf) + (dividers->post_div & 0xf) + 2; else post_divider = 1; if (dividers->enable_post_div) /* :149-164 */ mpll_func_cntl |= MPLL_DIVEN; else mpll_func_cntl &= ~MPLL_DIVEN; mpll_func_cntl &= ~(MPLL_REF_DIV_MASK | MPLL_HILEN_MASK | MPLL_LOLEN_MASK); mpll_func_cntl |= MPLL_REF_DIV(dividers->ref_div); mpll_func_cntl |= MPLL_HILEN((dividers->post_div >> 4) & 0xf); mpll_func_cntl |= MPLL_LOLEN(dividers->post_div & 0xf); mpll_func_cntl_3 &= ~MPLL_FB_DIV_MASK; mpll_func_cntl_3 |= MPLL_FB_DIV(dividers->fb_div); if (dividers->enable_dithen) mpll_func_cntl_3 |= MPLL_DITHEN; /* pi->mclk_ss == true and radeon_atombios_get_asic_ss_info() matched: :166-183 */ { u32 vco_freq = memory_clock * post_divider; /* :168 (SS match key) */ u32 reference_clock = mpll_reference_freq_boot; /* :172 */ u32 clk_s = reference_clock * 5 / (reference_divider * ss->rate); /* :173 */ u32 clk_v = ss->percentage * dividers->fb_div / (clk_s * 10000); /* :174 */ u32 clk_s_expected, clk_v_in_ss2; printf(" SS entry matched (vco=%u kHz): rate=%u percentage=%u -> clk_s=%u clk_v=%u\n", vco_freq, ss->rate, ss->percentage, clk_s, clk_v); mpll_ss &= ~CLK_S_MASK; /* :176 */ mpll_ss |= CLK_S(clk_s); /* :177 */ mpll_ss |= SSEN; /* :178 */ mpll_ss2 &= ~CLK_V_MASK; /* :180 */ #ifdef FIX_DF4858 mpll_ss2 |= CLK_V(clk_v); /* proposed fix for :181 */ #else mpll_ss |= CLK_V(clk_v); /* :181 <-- WRONG VARIABLE (verbatim) */ #endif /* verify what the SMC state table actually receives */ clk_s_expected = (mpll_ss & CLK_S_MASK) >> 4; clk_v_in_ss2 = mpll_ss2 & CLK_V_MASK; printf(" mpll_ss =0x%08x (CLK_S field=%u, expected %u)%s\n", mpll_ss, clk_s_expected, clk_s, clk_s_expected != clk_s ? " [V1: CLK_S CLOBBERED BY clk_v]" : ""); printf(" mpll_ss2 =0x%08x (CLK_V field=%u, expected %u)%s%s\n", mpll_ss2, clk_v_in_ss2, clk_v, clk_v_in_ss2 != clk_v ? " [V2: CLK_V LOST]" : "", (clk_v_in_ss2 == 0 && (mpll_ss & SSEN)) ? " [V3: SSEN with zero SS depth]" : ""); if (clk_s_expected != clk_s) { violations++; printf("VIOLATION V1\n"); } if (clk_v_in_ss2 != clk_v) { violations++; printf("VIOLATION V2\n"); } } mclk->vMCLK_PWRMGT_CNTL = cpu_to_be32(mclk_pwrmgt_cntl); /* :186-193 */ mclk->vDLL_CNTL = cpu_to_be32(dll_cntl); mclk->mclk_value = cpu_to_be32(memory_clock); mclk->vMPLL_FUNC_CNTL = cpu_to_be32(mpll_func_cntl); mclk->vMPLL_FUNC_CNTL2 = cpu_to_be32(mpll_func_cntl_2); mclk->vMPLL_FUNC_CNTL3 = cpu_to_be32(mpll_func_cntl_3); mclk->vMPLL_SS = cpu_to_be32(mpll_ss); mclk->vMPLL_SS2 = cpu_to_be32(mpll_ss2); printf(" SMC table: vMPLL_SS=0x%08x vMPLL_SS2=0x%08x\n", __builtin_bswap32(mclk->vMPLL_SS), __builtin_bswap32(mclk->vMPLL_SS2)); return violations; } int main(int argc, char **argv) { struct atom_clock_dividers div; struct radeon_atom_ss ss; struct mclk730 mclk; const char *mode = argc > 1 ? argv[1] : "real"; memset(&div, 0, sizeof(div)); memset(&ss, 0, sizeof(ss)); memset(&mclk, 0, sizeof(mclk)); div.ref_div = 1; div.post_div = 0x11; div.enable_post_div = 1; div.enable_dithen = 1; if (strcmp(mode, "real") == 0) { /* realistic SS-enabled board: mpll ref 27 MHz, 3 kHz spread rate, * 20000 bps percentage, fb_div 932 */ div.fb_div = 932; ss.rate = 3000; ss.percentage = 20000; } else if (strcmp(mode, "crafted") == 0) { /* crafted VBIOS SS entry: max amplitude */ div.fb_div = 2047; ss.rate = 6750; ss.percentage = 65535; } else { fprintf(stderr, "usage: %s real|crafted\n", argv[0]); return 2; } printf("DF-4858 mode=%s (boot mpll_ss=0x00000110 mpll_ss2=0x0bcdef12)\n", mode); return replica_mclk_value(800000, 2700, 0x00000110, 0x0bcdef12, &div, &ss, &mclk) ? 98 : 0; } |