"""Parse SN65DSI83 and DSIM register dumps into structured flags. DSIM PHY_TIMING bit-field layout is undocumented in the i.MX 8M Mini RM. We log raw hex AND decoded cycle counts so they can be cross-checked against kernel dmesg output that prints the cycle counts explicitly. """ from __future__ import annotations from typing import Optional from config import ( BYTE_CLK_HZ, SN65_ERR_SOT, SN65_ERR_SYNCH, SN65_ERR_UNC, SN65_FLICKER_MASK, ) def _to_int(v) -> Optional[int]: if v is None: return None if isinstance(v, int): return v s = str(v).strip().lower() try: if s.startswith("0x"): return int(s, 16) return int(s, 16) except ValueError: return None # --------------------------------------------------------------------------- # SN65DSI83 # --------------------------------------------------------------------------- def parse_sn65(reg_json: dict) -> dict: """Extract structured flicker flags from /sn65_registers response. Accepts either the server's pre-parsed shape (with explicit bool keys) or a raw {register: hex} mapping; falls back to bit-decoding in either case. """ irq_raw = _to_int(reg_json.get("irq_stat_raw")) if irq_raw is None: regs = reg_json.get("registers", {}) irq_raw = _to_int(regs.get("e5") or regs.get("E5") or regs.get("0xE5")) irq_raw = irq_raw or 0 pll_raw = _to_int(reg_json.get("registers", {}).get("0a")) if reg_json.get("registers") else None clk_raw = _to_int(reg_json.get("registers", {}).get("0b")) if reg_json.get("registers") else None pll_locked = reg_json.get("pll_locked") if pll_locked is None and pll_raw is not None: pll_locked = bool(pll_raw & 0x80) clk_detected = reg_json.get("clk_detected") if clk_detected is None and clk_raw is not None: clk_detected = bool(clk_raw & 0x01) sot_err = bool(irq_raw & SN65_ERR_SOT) synch_err = bool(irq_raw & SN65_ERR_SYNCH) unc_ecc_err = bool(irq_raw & SN65_ERR_UNC) flicker_detected = bool(irq_raw & SN65_FLICKER_MASK) return { "irq_stat_raw": f"0x{irq_raw:02X}", "irq_stat_int": irq_raw, "pll_locked": bool(pll_locked) if pll_locked is not None else None, "clk_detected": bool(clk_detected) if clk_detected is not None else None, "sot_err": sot_err, "synch_err": synch_err, "unc_ecc_err": unc_ecc_err, "flicker_detected": flicker_detected, "registers": reg_json.get("registers", {}), } # --------------------------------------------------------------------------- # DSIM PHY_TIMING / PHY_TIMING1 / PHY_TIMING2 # --------------------------------------------------------------------------- def _cycles_to_ns(cycles: int) -> float: return cycles / BYTE_CLK_HZ * 1e9 def parse_dsim(reg_json: dict) -> dict: pt = _to_int(reg_json.get("PHY_TIMING")) pt1 = _to_int(reg_json.get("PHY_TIMING1")) pt2 = _to_int(reg_json.get("PHY_TIMING2")) out: dict = { "PHY_TIMING_raw": f"0x{pt:08X}" if pt is not None else None, "PHY_TIMING1_raw": f"0x{pt1:08X}" if pt1 is not None else None, "PHY_TIMING2_raw": f"0x{pt2:08X}" if pt2 is not None else None, } if pt is not None: hs_exit = (pt >> 4) & 0xF lpx = pt & 0xF out["hs_exit_cycles"] = hs_exit out["hs_exit_ns"] = _cycles_to_ns(hs_exit) out["lpx_cycles"] = lpx out["lpx_ns"] = _cycles_to_ns(lpx) if pt1 is not None: clk_zero = (pt1 >> 24) & 0xFF clk_post = (pt1 >> 16) & 0xFF clk_trail = (pt1 >> 8) & 0xFF clk_prepare = pt1 & 0xFF out["clk_zero_cycles"] = clk_zero out["clk_zero_ns"] = _cycles_to_ns(clk_zero) out["clk_post_cycles"] = clk_post out["clk_post_ns"] = _cycles_to_ns(clk_post) out["clk_trail_cycles"] = clk_trail out["clk_trail_ns"] = _cycles_to_ns(clk_trail) out["clk_prepare_cycles"] = clk_prepare out["clk_prepare_ns"] = _cycles_to_ns(clk_prepare) if pt2 is not None: hs_prepare = (pt2 >> 16) & 0xFF hs_zero = (pt2 >> 8) & 0xFF hs_trail = pt2 & 0xFF out["hs_prepare_cycles"] = hs_prepare out["hs_prepare_ns"] = _cycles_to_ns(hs_prepare) out["hs_zero_cycles"] = hs_zero out["hs_zero_ns"] = _cycles_to_ns(hs_zero) out["hs_trail_cycles"] = hs_trail out["hs_trail_ns"] = _cycles_to_ns(hs_trail) return out