diff --git a/src/clocks.c b/src/clocks.c index 34217da..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,57 +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 raw; - if (!mem_reg(r->reg, &raw, OP_READ)) { - ADD_PARAM("error", "register read failed"); - ADD_PARAM_FMT("reg", "0x%08x", r->reg); - return j_inner; + /* 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)); + } } - ADD_PARAM_FMT("reg", "0x%08x", r->reg); - ADD_PARAM_FMT("raw", "0x%08x", raw); - 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++) { @@ -148,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; } @@ -175,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); @@ -198,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)) { @@ -265,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; @@ -304,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; } @@ -369,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 ff7f0f9..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,17 +67,6 @@ 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). */ -struct raw_reg_info { - const char *name; - const char *label; - uint32_t reg; - const char *note; -}; - struct clock_family { int chip_id; /* matches chip_generation, e.g. HISI_V4 */ const char *label; @@ -84,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 c603b0e..5ba04c4 100644 --- a/src/hal/hisi/clocks_v4.c +++ b/src/hal/hisi/clocks_v4.c @@ -2,31 +2,44 @@ * (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). * - * 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 (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): * - * Validated empirically on V4: 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`). + * 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) + * + * 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 -- 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) * - * 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. + * Validated empirically: read pair (0x12010000, 0x12010004) = + * (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. */ #include "clocks.h" @@ -49,10 +62,12 @@ 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 */ }, }; -/* 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 +85,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,29 +100,11 @@ 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. */ -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 = "pll_shadow_14", - .label = "PLL-shadow @ CRG[0x14]", - .reg = 0x12010014, - .note = "mask-ROM HPM-bin shadow; not a live FBDIV (see clocks_v4.c)", - }, -}; - const struct clock_family clocks_family_v4 = { .chip_id = HISI_V4, .label = "Hisilicon V4 / Goke V300", @@ -117,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; }