From fe47c5c6759770ed3c757659af5ec10f37d3a9f5 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Thu, 14 May 2026 15:56:15 +0300 Subject: [PATCH 1/2] clocks: rename pll_shadow_* to ddr_pll/eth_pll, add video_pll + lock status Follow-up to #162. The `pll_shadow_0c` / `pll_shadow_14` entries shipped there were honest empirical observations ("vary by per-die HPM, don't drive CPU clock") but used misleading labels: those registers aren't shadows, they are real PLL configuration pairs for non-CPU domains. Source: the V4A / HI3516CV500 mask-ROM reverse engineering at github.com/widgetii/HI3516CV500-SDK/blob/master/sdk/bootrom/bootrom-re/ regmap-crg.h names them explicitly: APLL_CONFIG_0/1 = CRG_BASE + 0x00/0x04 CPU PLL DPLL_CONFIG_0/1 = CRG_BASE + 0x08/0x0C DDR PLL EPLL_CONFIG_0/1 = CRG_BASE + 0x10/0x14 Ethernet PLL VPLL_CONFIG_0/1 = CRG_BASE + 0x18/0x1C Video PLL PLL_LOCK_STAT = CRG_BASE + 0x1E8 lock status V4A is one generation up from V4 but shares the CRG layout (same APLL definition recurs in the Hi3516A SDK kernel patch and was empirically validated on V4 in #162), so the per-PLL labelling carries over. Changes: - `struct raw_reg_info` gains an optional `reg2` field so a single entry can dump both CONFIG_0 and CONFIG_1 of a PLL pair. - Rename `pll_shadow_0c` -> `ddr_pll`, `pll_shadow_14` -> `eth_pll`. Add `video_pll` at (0x18, 0x1C) which was previously unexposed. - Add `pll_lock_status` reading PERI_CRG_PLL122 (0x120101E8) raw, with a note explaining the V4A bootrom polls `0xB` (bits 0/1/3 for APLL/DPLL/EPLL) but V4 reads `0x5` (bits 0/2) on all three lab boards -- so V4's per-bit assignment likely differs from V4A's; ship the raw value, leave per-bit decode as a TODO. FBDIV decode for DPLL/EPLL/VPLL is also TODO: empirically the FBDIV-shaped bytes sit at bits [23:16] (0x8F/0x97/0x77 in the field data) rather than [11:0] like APLL, so the layout differs. Need a DDR-throughput probe and ethernet PHY rate cross-check before shipping a frequency calculation. The raw config pair is dumped verbatim so users can fleet-compare without misreading. Verified on three lab boards: cpu_pll.freq_mhz still 900 (unchanged), new fields render as expected, no regressions in ddr / hpm / cpu_running / cpubench outputs. The hi3516ev300 (HiSi-branded) firmware reads zero for both DPLL and EPLL config pairs while DDR still runs at 450 MHz, suggesting the HiSi firmware leaves those PLLs gated and derives DDR clock differently; the Goke firmware actively programs DPLL and EPLL. Out of scope for this PR. Co-authored-by: Claude Opus 4.7 (1M context) --- src/clocks.c | 21 ++++++-- src/clocks.h | 13 +++-- src/hal/hisi/clocks_v4.c | 106 +++++++++++++++++++++++++++------------ 3 files changed, 101 insertions(+), 39 deletions(-) diff --git a/src/clocks.c b/src/clocks.c index 34217da..f654720 100644 --- a/src/clocks.c +++ b/src/clocks.c @@ -112,14 +112,27 @@ static cJSON *decode_pll(const struct pll_info *pll) { static cJSON *decode_raw(const struct raw_reg_info *r) { cJSON *j_inner = cJSON_CreateObject(); - uint32_t raw; - if (!mem_reg(r->reg, &raw, OP_READ)) { + uint32_t raw1; + if (!mem_reg(r->reg, &raw1, OP_READ)) { ADD_PARAM("error", "register read failed"); ADD_PARAM_FMT("reg", "0x%08x", r->reg); return j_inner; } - ADD_PARAM_FMT("reg", "0x%08x", r->reg); - ADD_PARAM_FMT("raw", "0x%08x", raw); + if (r->reg2) { + /* Two-register PLL config pair: report both raws. */ + uint32_t raw2; + bool ok2 = mem_reg(r->reg2, &raw2, OP_READ); + ADD_PARAM_FMT("ctrl_reg1", "0x%08x", r->reg); + ADD_PARAM_FMT("ctrl_reg1_raw", "0x%08x", raw1); + ADD_PARAM_FMT("ctrl_reg2", "0x%08x", r->reg2); + if (ok2) + ADD_PARAM_FMT("ctrl_reg2_raw", "0x%08x", raw2); + else + ADD_PARAM("ctrl_reg2_raw", ""); + } else { + ADD_PARAM_FMT("reg", "0x%08x", r->reg); + ADD_PARAM_FMT("raw", "0x%08x", raw1); + } if (r->note) ADD_PARAM("note", r->note); return j_inner; diff --git a/src/clocks.h b/src/clocks.h index ff7f0f9..6313aa0 100644 --- a/src/clocks.h +++ b/src/clocks.h @@ -64,14 +64,19 @@ struct hpm_info { const char *aux_name; }; -/* Raw register dump entry. Used for mask-ROM-written diagnostic slots that - * look PLL-shaped but don't drive any clock (e.g. V4 HPM-shadow registers at - * 0x12010014 / 0x1201000c — values vary by per-die silicon binning but - * empirically have zero effect on CPU/peripheral clock). */ +/* Raw register dump entry. Two flavours: + * - single register: reg2 = 0 + * - two-register PLL config pair (e.g. {APLL,DPLL,EPLL,VPLL}_CONFIG_0/1 + * in the HiSilicon CRG): set both reg (CONFIG_0) and reg2 (CONFIG_1) + * + * The two-register form is for PLLs whose FBDIV/REFDIV bit layout we + * haven't verified yet — we dump the raw words so users can correlate + * with vendor docs without us shipping a wrong decode. */ struct raw_reg_info { const char *name; const char *label; uint32_t reg; + uint32_t reg2; /* 0 = single-register entry */ const char *note; }; diff --git a/src/hal/hisi/clocks_v4.c b/src/hal/hisi/clocks_v4.c index c603b0e..569d5bc 100644 --- a/src/hal/hisi/clocks_v4.c +++ b/src/hal/hisi/clocks_v4.c @@ -7,26 +7,49 @@ * vendor u-boot trees and by benching all three lab boards at the same * CPU clock (ipctool#161 ground-truth, 2026-05-14). * - * CPU PLL (APLL) decode borrowed from the Hi3516A SDK kernel patch - * (`struct hi3516a_pll_clock` in linux-4.9.37.patch): - * ctrl_reg1 = CRG_BASE + 0x00: FRACDIV[23:0], POSTDIV1[26:24], - * POSTDIV2[30:28] ctrl_reg2 = CRG_BASE + 0x04: FBDIV[11:0], REFDIV[17:12] f = - * 24 MHz * FBDIV / (REFDIV * POSTDIV1 * POSTDIV2) + * Register-pair map (first-party from the V4A / HI3516CV500 mask-ROM + * reverse engineering at + * github.com/widgetii/HI3516CV500-SDK/blob/master/sdk/bootrom/bootrom-re/ + * regmap-crg.h; V4A is one generation up but shares the CRG layout — the + * same APLL definition recurs in the Hi3516A SDK kernel patch and is + * empirically validated on V4 below): * - * Validated empirically on V4: read pair (0x12010000, 0x12010004) = + * APLL_CONFIG_0 = CRG_BASE + 0x00 (PERI_CRG_PLL0) CPU PLL + * APLL_CONFIG_1 = CRG_BASE + 0x04 (PERI_CRG_PLL1) + * DPLL_CONFIG_0 = CRG_BASE + 0x08 (PERI_CRG_PLL2) DDR PLL + * DPLL_CONFIG_1 = CRG_BASE + 0x0C (PERI_CRG_PLL3) + * EPLL_CONFIG_0 = CRG_BASE + 0x10 (PERI_CRG_PLL4) Ethernet PLL + * EPLL_CONFIG_1 = CRG_BASE + 0x14 (PERI_CRG_PLL5) + * VPLL_CONFIG_0 = CRG_BASE + 0x18 (PERI_CRG_PLL6) Video PLL + * VPLL_CONFIG_1 = CRG_BASE + 0x1C (PERI_CRG_PLL7) + * PLL_LOCK_STAT = CRG_BASE + 0x1E8 (PERI_CRG_PLL122) + * V4A bootrom polls `& 0xB == 0xB` => bit 0 = APLL, bit 1 = DPLL, + * bit 3 = EPLL locked (bit 2 likely VPLL; not polled at boot). + * + * APLL FBDIV decode (CRG_BASE + 0x00 / +0x04), confirmed against + * `struct hi3516a_pll_clock` in Hi3516EV200_SDK_V1.0.1.2's + * linux-4.9.37.patch: + * ctrl_reg1 (+0x00): FRACDIV[23:0], POSTDIV1[26:24], POSTDIV2[30:28] + * ctrl_reg2 (+0x04): FBDIV[11:0], REFDIV[17:12] + * f = 24 MHz * FBDIV / (REFDIV * POSTDIV1 * POSTDIV2) + * + * Validated empirically: read pair (0x12010000, 0x12010004) = * (0x12000000, 0x0100104B) decodes to FBDIV=75, REFDIV=1, POSTDIV1=2, - * POSTDIV2=1 → 900 MHz, within 0.2% of the multi-pattern bench - * triangulation (see `ipctool cpubench`). + * POSTDIV2=1 -> 900 MHz, within 0.2% of `ipctool cpubench` multi-pattern + * triangulation on all three V4 lab boards. * - * NOTE: registers 0x12010014 and 0x1201000c on Goke-branded boards hold - * FBDIV-shaped values written by the mask ROM based on per-die HPM - * binning, BUT they do not drive any active clock — empirically confirmed - * by running identical CPU benchmarks on three V4 boards with three - * different values at 0x12010014 and finding identical 900 MHz operation. - * The issue #161 body identifies 0x12010014 as "CPU PLL FBDIV"; that - * interpretation is falsified. We surface those registers as raw - * diagnostic dumps so users can still spot per-die HPM correlation - * without misreading them as clock dividers. + * DPLL / EPLL / VPLL: register pairs identified per CV500 bootrom RE + * above, but the FBDIV bit layout DIFFERS from APLL. Field values seen on + * gk7205v300: + * DPLL_CONFIG_1 = 0x018F0000 (issue #161 body decoded `0x8F` -> 1144) + * EPLL_CONFIG_1 = 0x01770000 (issue #161 body decoded `0x77` -> 952) + * If FBDIV lived at bits [11:0] (APLL layout), both would read as 0 and + * the PLLs would be gated -- but DDR and Ethernet are clearly running. + * So FBDIV for non-APLL PLLs is at bits [23:16] (8-bit). REFDIV / + * POSTDIV1 / POSTDIV2 positions: TBD; bench-validate against a DDR + * throughput probe + ethernet PHY rate read before shipping decoded + * freq_mhz. For now we just dump the raw config pair so users can spot + * fleet differences. */ #include "clocks.h" @@ -52,7 +75,7 @@ static const struct pll_info v4_plls[] = { }, }; -/* CRG[0x80] bits[5:3] — DDR clock mux (see issue #161). */ +/* CRG[0x80] bits[5:3] -- DDR clock mux (see issue #161). */ static const struct mux_entry v4_ddr_table[] = { {0b000, 24}, {0b001, 450}, @@ -70,7 +93,7 @@ static const struct mux_info v4_muxes[] = { .table = v4_ddr_table, .table_len = sizeof(v4_ddr_table) / sizeof(v4_ddr_table[0]), .rate_mult = - 4, /* DDR3 quad-pumped — TODO: verify for LPDDRx variants */ + 4, /* DDR3 quad-pumped -- TODO: verify for LPDDRx variants */ }, }; @@ -85,26 +108,47 @@ static const struct hpm_info v4_hpms[] = { .window_max = 350, /* HPM_CORE_MAX */ .bin_min = 190, /* HPM_CORE_VALUE_MIN */ .bin_max = 310, /* HPM_CORE_VALUE_MAX */ - .aux_reg = 0x120280D8, /* HPM_CORE_REG0 — per-die fingerprint */ + .aux_reg = 0x120280D8, /* HPM_CORE_REG0 -- per-die fingerprint */ .aux_name = "hpm_core_reg0", }, }; -/* Mask-ROM-written HPM-shadow registers — vary by per-die silicon binning, - * empirically do NOT drive any active clock. Surfaced for diagnostic / - * fleet-comparison purposes only. See block comment at top of file. */ +/* Non-APLL PLL config pairs + lock status. FBDIV decode for these PLLs is + * TODO (bit layout differs from APLL; see block comment above). For now + * we just dump raw words so users can correlate with vendor docs and + * fleet-compare. */ static const struct raw_reg_info v4_raws[] = { { - .name = "pll_shadow_0c", - .label = "PLL-shadow @ CRG[0x0c]", - .reg = 0x1201000C, - .note = "mask-ROM HPM-bin shadow; not a live FBDIV (see clocks_v4.c)", + .name = "ddr_pll", + .label = "DDR PLL (DPLL)", + .reg = 0x12010008, /* DPLL_CONFIG_0 / PERI_CRG_PLL2 */ + .reg2 = 0x1201000C, /* DPLL_CONFIG_1 / PERI_CRG_PLL3 */ + .note = "FBDIV at bits [23:16] (different layout than APLL); " + "decode TBD -- see clocks_v4.c", + }, + { + .name = "eth_pll", + .label = "Ethernet PLL (EPLL)", + .reg = 0x12010010, /* EPLL_CONFIG_0 / PERI_CRG_PLL4 */ + .reg2 = 0x12010014, /* EPLL_CONFIG_1 / PERI_CRG_PLL5 */ + .note = "FBDIV at bits [23:16] (different layout than APLL); " + "decode TBD -- see clocks_v4.c", + }, + { + .name = "video_pll", + .label = "Video PLL (VPLL)", + .reg = 0x12010018, /* VPLL_CONFIG_0 / PERI_CRG_PLL6 */ + .reg2 = 0x1201001C, /* VPLL_CONFIG_1 / PERI_CRG_PLL7 */ + .note = "decode TBD -- see clocks_v4.c", }, { - .name = "pll_shadow_14", - .label = "PLL-shadow @ CRG[0x14]", - .reg = 0x12010014, - .note = "mask-ROM HPM-bin shadow; not a live FBDIV (see clocks_v4.c)", + .name = "pll_lock_status", + .label = "PLL lock status", + .reg = 0x120101E8, /* PERI_CRG_PLL122 */ + .note = "per-bit PLL lock; bit 0 = APLL (CPU). V4A bootrom RE " + "polls 0xB (bits 0/1/3 for APLL/DPLL/EPLL) at boot, but " + "V4 typically reads 0x5 (bits 0/2), so the DPLL/EPLL/VPLL " + "bit assignment on V4 likely differs -- verify per-chip.", }, }; From 5a376eb988d736975100afa93a224b55ae5359d9 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Thu, 14 May 2026 16:08:17 +0300 Subject: [PATCH 2/2] clocks: brief survey output, drop family/notes/non-decoded entries PR review feedback (#163): the YAML emitted by the no-arg `ipctool` survey was too verbose for a daily-driver summary, and the previous commit's `note:` fields + `family:` line added noise without helping anyone read the output. Changes: - `clocks_build_json()` gains a `bool brief` parameter. - `brief = true` (used by `ipctool` no-arg survey): emits only `cpu_pll.freq_mhz`, `ddr.data_rate_mbps`, `hpm.bin`. - `brief = false` (used by `ipctool clocks`): emits the full register-by-register YAML the subcommand has always produced. - Remove the `family:` line; the chip name is already in `chip.model` one level up. - Remove every `note:` field. They were either workarounds for "we haven't fully decoded this register" or restated info already obvious from sibling fields. - Remove `pll_shadow_0c/14` (#162) and `ddr_pll/eth_pll/video_pll/ pll_lock_status` (this PR's earlier commit). Their FBDIV bit layout on V4 differs from APLL (field-shaped bytes sit at [23:16] rather than [11:0]) and the REFDIV/POSTDIV positions aren't confirmed -- shipping the raw words with a "decode TBD" note was noise. Per review: decypher or remove totally -- removed until a DDR-throughput probe and ethernet PHY rate cross-check anchor the layout. - Keep the lock-bit signal that IS reliable: APLL lock from PERI_CRG_PLL122 bit 0. Expose it as `cpu_pll.locked: true/false` in the full output (bit 0 = APLL on V4 confirmed across all three lab boards; other bits' assignment on V4 still TBD). Survey output is now (Goke V300 example): clocks: cpu_pll: freq_mhz: 900 ddr: data_rate_mbps: 1800 hpm: bin: mid Verified end-to-end on all three lab boards (hi3516ev300 OpenIPC, gk7205v300 OpenIPC, gk7205v300 XM Sofia): identical 8-line `clocks:` section in survey mode; full `ipctool clocks` output unchanged except for the dropped notes/family and the added cpu_pll.locked. Co-authored-by: Claude Opus 4.7 (1M context) --- src/clocks.c | 173 +++++++++++++++------------------------ src/clocks.h | 32 +++----- src/hal/hisi/clocks_v4.c | 99 ++++++---------------- src/main.c | 2 +- 4 files changed, 104 insertions(+), 202 deletions(-) diff --git a/src/clocks.c b/src/clocks.c index f654720..435ed76 100644 --- a/src/clocks.c +++ b/src/clocks.c @@ -41,7 +41,7 @@ static uint32_t extract_field(uint32_t raw, uint8_t shift, uint8_t width) { return (raw >> shift) & mask; } -static cJSON *decode_pll(const struct pll_info *pll) { +static cJSON *decode_pll(const struct pll_info *pll, bool brief) { cJSON *j_inner = cJSON_CreateObject(); /* Read ctrl_reg1 (FRACDIV / POSTDIV1 / POSTDIV2) */ @@ -56,9 +56,6 @@ static cJSON *decode_pll(const struct pll_info *pll) { if (!ok1 || !ok2) { ADD_PARAM("error", "register read failed"); - ADD_PARAM_FMT("ctrl_reg1", "0x%08x", pll->ctrl_reg1); - if (pll->ctrl_reg2) - ADD_PARAM_FMT("ctrl_reg2", "0x%08x", pll->ctrl_reg2); return j_inner; } @@ -76,6 +73,23 @@ static cJSON *decode_pll(const struct pll_info *pll) { if (pll->refdiv_width == 0) refdiv = 1; + /* Compute frequency. f = input * (FBDIV + FRACDIV/2^frac_width) / + * (REFDIV * POSTDIV1 * POSTDIV2). */ + double freq_mhz = 0.0; + uint32_t denom = refdiv * pdiv1 * pdiv2; + if (fbdiv != 0 && denom != 0) { + uint64_t numer_khz = (uint64_t)pll->input_khz * fbdiv; + if (pll->frac_width) + numer_khz += + ((uint64_t)pll->input_khz * fracdiv) >> pll->frac_width; + freq_mhz = (double)numer_khz / (double)denom / 1000.0; + } + + if (brief) { + ADD_PARAM_NUM("freq_mhz", freq_mhz); + return j_inner; + } + ADD_PARAM_FMT("ctrl_reg1", "0x%08x", pll->ctrl_reg1); ADD_PARAM_FMT("ctrl_reg1_raw", "0x%08x", raw1); if (pll->ctrl_reg2 && pll->ctrl_reg2 != pll->ctrl_reg1) { @@ -88,70 +102,28 @@ static cJSON *decode_pll(const struct pll_info *pll) { ADD_PARAM_NUM("postdiv2", pdiv2); if (pll->frac_width) ADD_PARAM_FMT("fracdiv", "0x%06x", fracdiv); - - if (fbdiv == 0) { - ADD_PARAM("note", "PLL gated (FBDIV=0)"); - ADD_PARAM_NUM("freq_mhz", 0); - return j_inner; - } - uint32_t denom = refdiv * pdiv1 * pdiv2; - if (denom == 0) { - ADD_PARAM("note", "invalid divisor (REFDIV*POSTDIV1*POSTDIV2 = 0)"); - ADD_PARAM_NUM("freq_mhz", 0); - return j_inner; - } - /* f = input * (FBDIV + FRACDIV/2^24) / (REFDIV * POSTDIV1 * POSTDIV2) - * Compute in microhertz to keep the integer part exact. */ - uint64_t numer_khz = (uint64_t)pll->input_khz * fbdiv; - if (pll->frac_width) - numer_khz += ((uint64_t)pll->input_khz * fracdiv) >> pll->frac_width; - double freq_mhz = (double)numer_khz / (double)denom / 1000.0; ADD_PARAM_NUM("freq_mhz", freq_mhz); - return j_inner; -} -static cJSON *decode_raw(const struct raw_reg_info *r) { - cJSON *j_inner = cJSON_CreateObject(); - uint32_t raw1; - if (!mem_reg(r->reg, &raw1, OP_READ)) { - ADD_PARAM("error", "register read failed"); - ADD_PARAM_FMT("reg", "0x%08x", r->reg); - return j_inner; - } - if (r->reg2) { - /* Two-register PLL config pair: report both raws. */ - uint32_t raw2; - bool ok2 = mem_reg(r->reg2, &raw2, OP_READ); - ADD_PARAM_FMT("ctrl_reg1", "0x%08x", r->reg); - ADD_PARAM_FMT("ctrl_reg1_raw", "0x%08x", raw1); - ADD_PARAM_FMT("ctrl_reg2", "0x%08x", r->reg2); - if (ok2) - ADD_PARAM_FMT("ctrl_reg2_raw", "0x%08x", raw2); - else - ADD_PARAM("ctrl_reg2_raw", ""); - } else { - ADD_PARAM_FMT("reg", "0x%08x", r->reg); - ADD_PARAM_FMT("raw", "0x%08x", raw1); + /* Optional lock-bit check (e.g. PERI_CRG_PLL122 bit 0 = APLL on V4). */ + if (pll->lock_reg) { + uint32_t lock_raw; + if (mem_reg(pll->lock_reg, &lock_raw, OP_READ)) { + bool locked = (lock_raw >> pll->lock_bit) & 1u; + cJSON_AddItemToObject(j_inner, "locked", cJSON_CreateBool(locked)); + } } - if (r->note) - ADD_PARAM("note", r->note); return j_inner; } -static cJSON *decode_mux(const struct mux_info *mux) { +static cJSON *decode_mux(const struct mux_info *mux, bool brief) { cJSON *j_inner = cJSON_CreateObject(); uint32_t raw; if (!mem_reg(mux->reg, &raw, OP_READ)) { ADD_PARAM("error", "register read failed"); - ADD_PARAM_FMT("reg", "0x%08x", mux->reg); return j_inner; } uint8_t sel = (raw >> mux->sel_shift) & mux->sel_mask; - ADD_PARAM_FMT("reg", "0x%08x", mux->reg); - ADD_PARAM_FMT("raw", "0x%08x", raw); - ADD_PARAM_NUM("cksel", sel); - uint16_t mhz = 0; bool found = false; for (size_t i = 0; i < mux->table_len; i++) { @@ -161,12 +133,23 @@ static cJSON *decode_mux(const struct mux_info *mux) { break; } } + + if (brief) { + if (mux->rate_mult) + ADD_PARAM_NUM("data_rate_mbps", + (uint32_t)mhz * (found ? mux->rate_mult : 0)); + else + ADD_PARAM_NUM("freq_mhz", mhz); + return j_inner; + } + + ADD_PARAM_FMT("reg", "0x%08x", mux->reg); + ADD_PARAM_FMT("raw", "0x%08x", raw); + ADD_PARAM_NUM("cksel", sel); if (found) { ADD_PARAM_NUM("freq_mhz", mhz); if (mux->rate_mult) ADD_PARAM_NUM("data_rate_mbps", (uint32_t)mhz * mux->rate_mult); - } else { - ADD_PARAM("note", "cksel value not in known table"); } return j_inner; } @@ -188,19 +171,23 @@ static const char *hpm_bin(uint16_t v, const struct hpm_info *h) { return "high"; } -static cJSON *decode_hpm(const struct hpm_info *h) { +static cJSON *decode_hpm(const struct hpm_info *h, bool brief) { cJSON *j_inner = cJSON_CreateObject(); uint32_t raw; if (!mem_reg(h->reg, &raw, OP_READ) || raw == 0xFFFFFFFF) { - /* HPM register absent on this variant — caller should treat NULL-ish - * by simply omitting; we return an empty object and let the parent - * decide. */ + /* HPM register absent on this variant — caller treats NULL by + * omitting the section entirely. */ cJSON_Delete(j_inner); return NULL; } uint16_t value = (raw >> h->value_shift) & h->value_mask; const char *bin = hpm_bin(value, h); + if (brief) { + ADD_PARAM("bin", bin); + return j_inner; + } + ADD_PARAM_FMT("reg", "0x%08x", h->reg); ADD_PARAM_FMT("raw", "0x%08x", raw); ADD_PARAM_NUM("value", value); @@ -211,12 +198,6 @@ static cJSON *decode_hpm(const struct hpm_info *h) { cJSON_AddItemToArray(window, cJSON_CreateNumber(h->bin_max)); cJSON_AddItemToObject(j_inner, "binning_window", window); - if (!strcmp(bin, "low") || !strcmp(bin, "below_window")) { - ADD_PARAM("note", - "low-bin silicon; mask ROM may have selected a reduced PLL " - "multiplier at boot"); - } - if (h->aux_reg) { uint32_t aux; if (mem_reg(h->aux_reg, &aux, OP_READ)) { @@ -278,36 +259,7 @@ static cJSON *build_cpu_running(void) { return j_inner; } -static void add_plls(cJSON *parent, const struct clock_family *fam) { - for (size_t i = 0; i < fam->n_plls; i++) { - cJSON *p = decode_pll(&fam->plls[i]); - cJSON_AddItemToObject(parent, fam->plls[i].name, p); - } -} - -static void add_muxes(cJSON *parent, const struct clock_family *fam) { - for (size_t i = 0; i < fam->n_muxes; i++) { - cJSON *m = decode_mux(&fam->muxes[i]); - cJSON_AddItemToObject(parent, fam->muxes[i].name, m); - } -} - -static void add_hpms(cJSON *parent, const struct clock_family *fam) { - for (size_t i = 0; i < fam->n_hpms; i++) { - cJSON *h = decode_hpm(&fam->hpms[i]); - if (h) - cJSON_AddItemToObject(parent, fam->hpms[i].name, h); - } -} - -static void add_raws(cJSON *parent, const struct clock_family *fam) { - for (size_t i = 0; i < fam->n_raws; i++) { - cJSON *r = decode_raw(&fam->raws[i]); - cJSON_AddItemToObject(parent, fam->raws[i].name, r); - } -} - -cJSON *clocks_build_json(void) { +cJSON *clocks_build_json(bool brief) { /* Make sure chip detection has run and chip_generation is populated. */ if (!getchipname()) return NULL; @@ -317,17 +269,26 @@ cJSON *clocks_build_json(void) { return NULL; cJSON *j_inner = cJSON_CreateObject(); - ADD_PARAM("family", fam->label); - - add_plls(j_inner, fam); - add_muxes(j_inner, fam); - add_hpms(j_inner, fam); - add_raws(j_inner, fam); - cJSON *running = build_cpu_running(); - if (running) - cJSON_AddItemToObject(j_inner, "cpu_running", running); + for (size_t i = 0; i < fam->n_plls; i++) { + cJSON *p = decode_pll(&fam->plls[i], brief); + cJSON_AddItemToObject(j_inner, fam->plls[i].name, p); + } + for (size_t i = 0; i < fam->n_muxes; i++) { + cJSON *m = decode_mux(&fam->muxes[i], brief); + cJSON_AddItemToObject(j_inner, fam->muxes[i].name, m); + } + for (size_t i = 0; i < fam->n_hpms; i++) { + cJSON *h = decode_hpm(&fam->hpms[i], brief); + if (h) + cJSON_AddItemToObject(j_inner, fam->hpms[i].name, h); + } + if (!brief) { + cJSON *running = build_cpu_running(); + if (running) + cJSON_AddItemToObject(j_inner, "cpu_running", running); + } return j_inner; } @@ -382,7 +343,7 @@ int clocks_cmd(int argc, char **argv) { return EXIT_FAILURE; } - cJSON *clocks = clocks_build_json(); + cJSON *clocks = clocks_build_json(false); if (!clocks) { fprintf(stderr, "clocks: failed to build clock info\n"); return EXIT_FAILURE; diff --git a/src/clocks.h b/src/clocks.h index 6313aa0..ed76cb7 100644 --- a/src/clocks.h +++ b/src/clocks.h @@ -1,6 +1,7 @@ #ifndef CLOCKS_H #define CLOCKS_H +#include #include #include @@ -32,6 +33,8 @@ struct pll_info { uint8_t refdiv_shift; uint8_t refdiv_width; /* 0 = refdiv fixed to 1 */ uint32_t input_khz; /* crystal frequency; 24000 on V4 */ + uint32_t lock_reg; /* 0 = no lock-bit check (e.g. PERI_CRG_PLL122) */ + uint8_t lock_bit; /* bit index in lock_reg */ }; struct mux_entry { @@ -64,22 +67,6 @@ struct hpm_info { const char *aux_name; }; -/* Raw register dump entry. Two flavours: - * - single register: reg2 = 0 - * - two-register PLL config pair (e.g. {APLL,DPLL,EPLL,VPLL}_CONFIG_0/1 - * in the HiSilicon CRG): set both reg (CONFIG_0) and reg2 (CONFIG_1) - * - * The two-register form is for PLLs whose FBDIV/REFDIV bit layout we - * haven't verified yet — we dump the raw words so users can correlate - * with vendor docs without us shipping a wrong decode. */ -struct raw_reg_info { - const char *name; - const char *label; - uint32_t reg; - uint32_t reg2; /* 0 = single-register entry */ - const char *note; -}; - struct clock_family { int chip_id; /* matches chip_generation, e.g. HISI_V4 */ const char *label; @@ -89,14 +76,17 @@ struct clock_family { size_t n_muxes; const struct hpm_info *hpms; size_t n_hpms; - const struct raw_reg_info *raws; - size_t n_raws; }; /* Builds the cJSON tree for the current chip. Returns NULL on unsupported - * chip family. Used by both the `clocks`/`freq` subcommand and the default - * `ipctool` YAML survey. */ -cJSON *clocks_build_json(void); + * chip family. + * + * brief = true : only the headline numbers (cpu_pll.freq_mhz, + * ddr.data_rate_mbps, hpm.bin) -- used by the default + * `ipctool` no-arg survey to keep its YAML compact. + * brief = false : full detail (raw register values, all PLL fields, + * HPM aux register, etc.) -- used by `ipctool clocks`. */ +cJSON *clocks_build_json(bool brief); int clocks_cmd(int argc, char **argv); diff --git a/src/hal/hisi/clocks_v4.c b/src/hal/hisi/clocks_v4.c index 569d5bc..5ba04c4 100644 --- a/src/hal/hisi/clocks_v4.c +++ b/src/hal/hisi/clocks_v4.c @@ -2,33 +2,36 @@ * (3516EV200, 3516EV300, 3518EV300, 3516DV200, 7205V200/V210/V300, * 7202V300/V330, 7201V200/V300, 7605V100, 7205V500/V510/V530). * - * Same silicon die across HiSilicon and Goke V300 brandings — verified by + * Same silicon die across HiSilicon and Goke V300 brandings -- verified by * matching `arch/arm/include/asm/arch-*v300/platform.h` between the two * vendor u-boot trees and by benching all three lab boards at the same * CPU clock (ipctool#161 ground-truth, 2026-05-14). * - * Register-pair map (first-party from the V4A / HI3516CV500 mask-ROM - * reverse engineering at - * github.com/widgetii/HI3516CV500-SDK/blob/master/sdk/bootrom/bootrom-re/ - * regmap-crg.h; V4A is one generation up but shares the CRG layout — the - * same APLL definition recurs in the Hi3516A SDK kernel patch and is - * empirically validated on V4 below): + * Register-pair map (per the V4A / HI3516CV500 mask-ROM reverse + * engineering at widgetii/HI3516CV500-SDK sdk/bootrom/bootrom-re/ + * regmap-crg.h -- V4A is one generation up but shares the CRG layout): * - * APLL_CONFIG_0 = CRG_BASE + 0x00 (PERI_CRG_PLL0) CPU PLL - * APLL_CONFIG_1 = CRG_BASE + 0x04 (PERI_CRG_PLL1) - * DPLL_CONFIG_0 = CRG_BASE + 0x08 (PERI_CRG_PLL2) DDR PLL - * DPLL_CONFIG_1 = CRG_BASE + 0x0C (PERI_CRG_PLL3) - * EPLL_CONFIG_0 = CRG_BASE + 0x10 (PERI_CRG_PLL4) Ethernet PLL - * EPLL_CONFIG_1 = CRG_BASE + 0x14 (PERI_CRG_PLL5) - * VPLL_CONFIG_0 = CRG_BASE + 0x18 (PERI_CRG_PLL6) Video PLL - * VPLL_CONFIG_1 = CRG_BASE + 0x1C (PERI_CRG_PLL7) - * PLL_LOCK_STAT = CRG_BASE + 0x1E8 (PERI_CRG_PLL122) - * V4A bootrom polls `& 0xB == 0xB` => bit 0 = APLL, bit 1 = DPLL, - * bit 3 = EPLL locked (bit 2 likely VPLL; not polled at boot). + * APLL_CONFIG_0 = CRG_BASE + 0x00 CPU PLL + * APLL_CONFIG_1 = CRG_BASE + 0x04 + * DPLL_CONFIG_0 = CRG_BASE + 0x08 DDR PLL + * DPLL_CONFIG_1 = CRG_BASE + 0x0C + * EPLL_CONFIG_0 = CRG_BASE + 0x10 Ethernet PLL + * EPLL_CONFIG_1 = CRG_BASE + 0x14 + * VPLL_CONFIG_0 = CRG_BASE + 0x18 Video PLL + * VPLL_CONFIG_1 = CRG_BASE + 0x1C + * PLL_LOCK_STAT = CRG_BASE + 0x1E8 bit 0 = APLL locked + * (other bits TBD on V4) * - * APLL FBDIV decode (CRG_BASE + 0x00 / +0x04), confirmed against + * Only APLL is decoded here; DPLL / EPLL / VPLL FBDIV bit layout differs + * from APLL (field-shaped bytes sit at bits [23:16] rather than [11:0]) + * and the REFDIV / POSTDIV positions aren't confirmed -- they'd need a + * DDR-throughput probe and ethernet PHY rate cross-check before being + * worth shipping. + * + * APLL FBDIV decode (CRG_BASE + 0x00 / +0x04), per * `struct hi3516a_pll_clock` in Hi3516EV200_SDK_V1.0.1.2's - * linux-4.9.37.patch: + * linux-4.9.37.patch -- same struct shape used by V4A bootrom RE for + * APLL_CONFIG_0/1: * ctrl_reg1 (+0x00): FRACDIV[23:0], POSTDIV1[26:24], POSTDIV2[30:28] * ctrl_reg2 (+0x04): FBDIV[11:0], REFDIV[17:12] * f = 24 MHz * FBDIV / (REFDIV * POSTDIV1 * POSTDIV2) @@ -37,19 +40,6 @@ * (0x12000000, 0x0100104B) decodes to FBDIV=75, REFDIV=1, POSTDIV1=2, * POSTDIV2=1 -> 900 MHz, within 0.2% of `ipctool cpubench` multi-pattern * triangulation on all three V4 lab boards. - * - * DPLL / EPLL / VPLL: register pairs identified per CV500 bootrom RE - * above, but the FBDIV bit layout DIFFERS from APLL. Field values seen on - * gk7205v300: - * DPLL_CONFIG_1 = 0x018F0000 (issue #161 body decoded `0x8F` -> 1144) - * EPLL_CONFIG_1 = 0x01770000 (issue #161 body decoded `0x77` -> 952) - * If FBDIV lived at bits [11:0] (APLL layout), both would read as 0 and - * the PLLs would be gated -- but DDR and Ethernet are clearly running. - * So FBDIV for non-APLL PLLs is at bits [23:16] (8-bit). REFDIV / - * POSTDIV1 / POSTDIV2 positions: TBD; bench-validate against a DDR - * throughput probe + ethernet PHY rate read before shipping decoded - * freq_mhz. For now we just dump the raw config pair so users can spot - * fleet differences. */ #include "clocks.h" @@ -72,6 +62,8 @@ static const struct pll_info v4_plls[] = { .refdiv_shift = 12, .refdiv_width = 6, .input_khz = 24000, + .lock_reg = 0x120101E8, /* PERI_CRG_PLL122 */ + .lock_bit = 0, /* APLL on V4 */ }, }; @@ -113,45 +105,6 @@ static const struct hpm_info v4_hpms[] = { }, }; -/* Non-APLL PLL config pairs + lock status. FBDIV decode for these PLLs is - * TODO (bit layout differs from APLL; see block comment above). For now - * we just dump raw words so users can correlate with vendor docs and - * fleet-compare. */ -static const struct raw_reg_info v4_raws[] = { - { - .name = "ddr_pll", - .label = "DDR PLL (DPLL)", - .reg = 0x12010008, /* DPLL_CONFIG_0 / PERI_CRG_PLL2 */ - .reg2 = 0x1201000C, /* DPLL_CONFIG_1 / PERI_CRG_PLL3 */ - .note = "FBDIV at bits [23:16] (different layout than APLL); " - "decode TBD -- see clocks_v4.c", - }, - { - .name = "eth_pll", - .label = "Ethernet PLL (EPLL)", - .reg = 0x12010010, /* EPLL_CONFIG_0 / PERI_CRG_PLL4 */ - .reg2 = 0x12010014, /* EPLL_CONFIG_1 / PERI_CRG_PLL5 */ - .note = "FBDIV at bits [23:16] (different layout than APLL); " - "decode TBD -- see clocks_v4.c", - }, - { - .name = "video_pll", - .label = "Video PLL (VPLL)", - .reg = 0x12010018, /* VPLL_CONFIG_0 / PERI_CRG_PLL6 */ - .reg2 = 0x1201001C, /* VPLL_CONFIG_1 / PERI_CRG_PLL7 */ - .note = "decode TBD -- see clocks_v4.c", - }, - { - .name = "pll_lock_status", - .label = "PLL lock status", - .reg = 0x120101E8, /* PERI_CRG_PLL122 */ - .note = "per-bit PLL lock; bit 0 = APLL (CPU). V4A bootrom RE " - "polls 0xB (bits 0/1/3 for APLL/DPLL/EPLL) at boot, but " - "V4 typically reads 0x5 (bits 0/2), so the DPLL/EPLL/VPLL " - "bit assignment on V4 likely differs -- verify per-chip.", - }, -}; - const struct clock_family clocks_family_v4 = { .chip_id = HISI_V4, .label = "Hisilicon V4 / Goke V300", @@ -161,6 +114,4 @@ const struct clock_family clocks_family_v4 = { .n_muxes = sizeof(v4_muxes) / sizeof(v4_muxes[0]), .hpms = v4_hpms, .n_hpms = sizeof(v4_hpms) / sizeof(v4_hpms[0]), - .raws = v4_raws, - .n_raws = sizeof(v4_raws) / sizeof(v4_raws[0]), }; diff --git a/src/main.c b/src/main.c index 9743f75..aa348ca 100644 --- a/src/main.c +++ b/src/main.c @@ -143,7 +143,7 @@ static cJSON *build_yaml() { add_yaml_fragment(root, "ram", detect_ram()); add_yaml_fragment(root, "firmware", detect_firmare()); add_yaml_fragment(root, "sensors", detect_sensors()); - add_yaml_fragment(root, "clocks", clocks_build_json()); + add_yaml_fragment(root, "clocks", clocks_build_json(true)); return root; }