/*
 * DF-4931 replica — rv740_dpm.c spread-spectrum clk_v computation
 *
 * Verbatim userland replica of the two SS blocks of
 *   sys/dev/drm/radeon/rv740_dpm.c:
 *     - sclk block : rv740_dpm.c:159-175  (clk_v at :166)
 *     - mclk block : rv740_dpm.c:246-268  (clk_v at :259-260)
 * with the field masks/shifts verbatim from sys/dev/drm/radeon/rv740d.h:100-116
 *   CG_SPLL_SPREAD_SPECTRUM    : SSEN(1<<0), CLK_S(x)=(x)<<4, CLK_S_MASK=0xfff<<4
 *   CG_SPLL_SPREAD_SPECTRUM_2  : CLK_V(x)=(x)<<0, CLK_V_MASK=0x3ffffff<<0
 *   MPLL_SS1                   : CLKV(x)=(x)<<0, CLKV_MASK=0x3ffffff<<0
 *   MPLL_SS2                   : CLKS(x)=(x)<<0, CLKS_MASK=0xfff<<0
 *
 * The numerators
 *   4 * ss.percentage * fbdiv                          (sclk, :166)
 *   0x40000 * ss.percentage * (whole_fb_div + frac/8)  (mclk, :259-260)
 * are evaluated in 32-bit unsigned arithmetic. ss.percentage (u16) and the
 * fb divisors are VBIOS-derived; for entirely plausible values the product
 * exceeds 2^32, wraps, and the wrapped (wrong) clk_v is programmed into the
 * 26-bit CLKV/CLK_V SMC fields — silently, because the *correct* value also
 * fits the field.
 *
 * do_div(tmp, reference_clock) is plain 64-bit division here (math64.h:36-42).
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF4931] -o rv740_ssv rv740_ssv.c
 * Exit:  0 ok / 98 SS-depth misprogrammed (baseline bug demonstrated)
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef uint32_t u32;
typedef uint16_t u16;
typedef uint64_t u64;

/* ---- rv740d.h:100-116 verbatim ---- */
#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)
#define CLKV(x)         ((x) << 0)
#define CLKV_MASK       (0x3ffffff << 0)
#define CLKS(x)         ((x) << 0)
#define CLKS_MASK       (0xfff << 0)

/* struct radeon_atom_ss (radeon.h): VBIOS ASIC_InternalSS_Info fields */
struct radeon_atom_ss {
	u16 percentage;
	u16 rate;
};

static int failures;

/* ---------------- sclk block: rv740_dpm.c:121-185 (SS part :159-175) ---- */
static void sclk_case(const char *name, u32 engine_clock, u32 ref_div,
		      u32 post_div, u32 reference_clock,
		      struct radeon_atom_ss ss)
{
	u32 reference_divider = 1 + ref_div;
	u64 tmp = (u64)engine_clock * reference_divider * post_div * 16384;
	u32 fbdiv = (u32)(tmp / reference_clock);	/* do_div, :144-146 */
	u32 vco_freq = engine_clock * post_div;		/* :161 (SS match key) */
	u32 clk_s = reference_clock * 5 / (reference_divider * ss.rate); /* :165 */
	u32 clk_v;
	u64 num_exact = 4ULL * ss.percentage * fbdiv;
	u64 clk_v_exact = num_exact / ((u64)clk_s * 10000);
	u32 cg_spll_spread_spectrum, cg_spll_spread_spectrum_2;
	int exact_fits = (clk_v_exact <= 0x3ffffff);

	(void)vco_freq;
#ifdef FIX_DF4931
	clk_v = (u32)clk_v_exact;			/* 64-bit numerator */
#else
	/* rv740_dpm.c:166 verbatim — u32 numerator wraps */
	clk_v = 4 * ss.percentage * fbdiv / (clk_s * 10000);
#endif

	cg_spll_spread_spectrum   = CLK_S(clk_s) | SSEN;
	cg_spll_spread_spectrum_2 = CLK_V(clk_v);

	printf("[sclk %s] engine_clock=%u ref_div=%u post_div=%u ref_clk=%u\n",
	       name, engine_clock, ref_div, post_div, reference_clock);
	printf("  ss.percentage=%u ss.rate=%u -> fbdiv=%u clk_s=%u\n",
	       ss.percentage, ss.rate, fbdiv, clk_s);
	printf("  numerator 4*pct*fbdiv: exact=%llu  u32-wrapped=%u%s\n",
	       (unsigned long long)num_exact,
	       (u32)(4u * ss.percentage * fbdiv),
	       num_exact > 0xffffffffULL ? "  << WRAPS u32" : "");
	printf("  clk_v programmed=%u   exact=%llu%s\n", clk_v,
	       (unsigned long long)clk_v_exact,
	       exact_fits ? "  (exact FITS the 26-bit field)" :
			    "  (exact would overflow the field)");
	printf("  SMC words: SPREAD_SPECTRUM=0x%08x SPREAD_SPECTRUM_2=0x%08x\n",
	       cg_spll_spread_spectrum, cg_spll_spread_spectrum_2);

	if (exact_fits && clk_v != clk_v_exact) {
		printf("  [BUG] SSEN programmed with wrong depth: %u instead of "
		       "%llu (off by %.1fx)\n", clk_v,
		       (unsigned long long)clk_v_exact,
		       (double)clk_v_exact / (clk_v ? clk_v : 1));
		failures++;
	}
	printf("\n");
}

/* ---------------- mclk block: rv740_dpm.c:187-287 (SS part :246-268) ---- */
static u32 rv740_get_decoded_reference_divider(u32 encoded_ref)	/* :34-67 */
{
	switch (encoded_ref) {
	case 0: return 1;
	case 16: return 2;
	case 17: return 3;
	case 18: return 2;
	case 19: return 3;
	case 20: return 4;
	case 21: return 5;
	default: return 0;
	}
}

static void mclk_case(const char *name, u32 memory_clock, u32 encoded_ref_div,
		      u32 whole_fb_div, u32 frac_fb_div, u32 reference_clock,
		      struct radeon_atom_ss ss)
{
	u32 decoded_ref = rv740_get_decoded_reference_divider(encoded_ref_div);
	u32 clk_s, clk_v, mpll_ss1 = 0, mpll_ss2 = 0;
	u64 num_exact, clk_v_exact;
	int exact_fits;

	if (!decoded_ref)
		return;						/* :256-257 */
	clk_s = reference_clock * 5 / (decoded_ref * ss.rate);	/* :258 */
	num_exact = 0x40000ULL * ss.percentage *
		    (whole_fb_div + (frac_fb_div / 8));
	clk_v_exact = num_exact / ((u64)clk_s * 10000);
	exact_fits = (clk_v_exact <= 0x3ffffff);

#ifdef FIX_DF4931
	clk_v = (u32)clk_v_exact;				/* 64-bit numerator */
#else
	/* rv740_dpm.c:259-260 verbatim — u32 numerator wraps */
	clk_v = 0x40000 * ss.percentage *
		(whole_fb_div + (frac_fb_div / 8)) / (clk_s * 10000);
#endif

	mpll_ss1 |= CLKV(clk_v);				/* :262-263 */
	mpll_ss2 |= CLKS(clk_s);				/* :265-266 */

	printf("[mclk %s] memory_clock=%u ref_clk=%u decoded_ref=%u\n",
	       name, memory_clock, reference_clock, decoded_ref);
	printf("  ss.percentage=%u ss.rate=%u whole_fb_div=%u frac=%u -> clk_s=%u\n",
	       ss.percentage, ss.rate, whole_fb_div, frac_fb_div, clk_s);
	printf("  numerator 0x40000*pct*fb: exact=%llu  u32-wrapped=%u%s\n",
	       (unsigned long long)num_exact,
	       (u32)(0x40000u * ss.percentage *
		     (whole_fb_div + (frac_fb_div / 8))),
	       num_exact > 0xffffffffULL ? "  << WRAPS u32" : "");
	printf("  clk_v programmed=%u   exact=%llu%s\n", clk_v,
	       (unsigned long long)clk_v_exact,
	       exact_fits ? "  (exact FITS the 26-bit field)" :
			    "  (exact would overflow the field)");
	printf("  SMC words: MPLL_SS1=0x%08x MPLL_SS2=0x%08x\n",
	       mpll_ss1, mpll_ss2);

	if (exact_fits && clk_v != clk_v_exact) {
		printf("  [BUG] wrong SS depth in MPLL_SS1: %u instead of %llu "
		       "(off by %.1fx)\n", clk_v,
		       (unsigned long long)clk_v_exact,
		       (double)clk_v_exact / (clk_v ? clk_v : 1));
		failures++;
	}
	printf("\n");
}

int main(void)
{
	printf("== DF-4931 replica: rv740_dpm.c clk_v u32-overflow (sclk :166, "
	       "mclk :259-260) ==\n%s\n\n",
#ifdef FIX_DF4931
	       "mode: FIXED (64-bit numerators)"
#else
	       "mode: BASELINE (kernel arithmetic verbatim)"
#endif
	       );

	/*
	 * S1 — realistic SS-enabled engine-clock level (kHz units, as the
	 * radeon DPM code carries them): 740 MHz engine clock, 27 MHz SPLL
	 * reference, post divider 8, 6% spread at 30 kHz.  fbdiv lands in the
	 * same ~1.8e7 range as the accepted DF-4857 "ok-mode" control.
	 */
	sclk_case("S1 realistic 6%-SS", 740000, 0, 8, 27000,
		  (struct radeon_atom_ss){ .percentage = 600, .rate = 30 });

	/*
	 * S2 — modest (crafted but u16-legal) 30% spread: bigger wrap.
	 */
	sclk_case("S2 crafted 30%-SS", 740000, 0, 8, 27000,
		  (struct radeon_atom_ss){ .percentage = 3000, .rate = 30 });

	/*
	 * M1 — realistic GDDR5 memory-clock level: 800 MHz mclk, 10 MHz MPLL
	 * reference, encoded ref divider 16 (=2), fb divisor 200, 2% spread
	 * at 30 kHz.
	 */
	mclk_case("M1 realistic 2%-SS", 80000, 16, 200, 0, 10000,
		  (struct radeon_atom_ss){ .percentage = 200, .rate = 30 });

	/*
	 * M2 — 5% spread (still ordinary board territory).
	 */
	mclk_case("M2 realistic 5%-SS", 80000, 16, 200, 0, 10000,
		  (struct radeon_atom_ss){ .percentage = 500, .rate = 30 });

	if (failures) {
		printf("RESULT: %d SS depth(s) misprogrammed by the u32 "
		       "numerator wrap\n", failures);
		return 98;
	}
	printf("RESULT: all programmed depths match the exact 64-bit math\n");
	return 0;
}
