/*
 * 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;
}
