drm/bridge: dw-hdmi-qp: seamless handoff sets wrong tmds_char_rate, breaking audio on strict HDMI 2.1 sinks

Simon Wright Simon at symple.nz
Tue May 5 01:18:17 PDT 2026


[Resent in plain text — first attempt was rejected by the list's HTML filter.]

Hi,

I have tracked down a bug in dw-hdmi-qp that silences audio on strict
HDMI 2.1 sinks (tested: LG G3 OLED) when booting with HDMI seamless
handoff from U-Boot. HDMI 2.0 sinks are unaffected. The root cause is
a mismatch between the AVI InfoFrame colour depth and the ACR embedded
CTS value.

Hardware tested:
  Source:       ArmSoM Sige5 (Rockchip RK3576)
                Armbian-edge kernel 7.0.2 (Linux mainline v7.0 plus
                Armbian board-enable patches; not a BSP kernel)
  Failing sink: LG G3 OLED 4K (HDMI 2.1, strict embedded-CTS mode)
  Passing sink: Various HDMI 2.0 TVs (self-measuring CTS, unaffected)

The bug is structural and is expected to affect RK3588 under the same
boot sequence.


Root cause
----------

When the system boots with seamless handoff active, U-Boot has configured
the PHY for 8-bit 1080p60 (TMDS = 148,500,000 Hz). The DRM connector
state, negotiated from EDID, selects 10-bit deep colour (TMDS =
185,625,000 Hz). In dw_hdmi_qp_bridge_atomic_enable():

    hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate;

This stores 185,625,000. The PHY ops->init() is called but the PHY is
already running at 148,500,000 from U-Boot and is not reconfigured.

The result is an inconsistency between what the sink is told over HDMI
and what the wire actually carries:

  AVI InfoFrame:     declares 10-bit deep colour at the connector-state
                     TMDS rate (185.625 MHz)
  ACR embedded CTS:  driver uses hdmi->tmds_char_rate = 185,625,000 Hz
                     to compute CTS via dw_hdmi_qp_set_sample_rate(),
                     producing CTS = 185,625 (internally consistent
                     with the AVI declaration)
  Wire TMDS clock:   PHY untouched from U-Boot, still running at
                     148,500,000 Hz (8-bit)

The dmesg signature (full pr_err output, lower in this report) confirms
both AVI and ACR are computed from the nominal 185.625 MHz value:
"tmds=185625000 ... cts=185625".

A standard HDMI 2.0 sink does not cross-check AVI against the wire TMDS
in any way that matters for audio, so the mismatch is harmless. A strict
HDMI 2.1 sink (LG G3) measures the wire TMDS and verifies it matches the
AVI declaration; with declared 185.625 MHz vs measured 148.5 MHz, the
check fails and audio is muted, permanently, until the next mode set.

The N/CTS values themselves are correct CEA-861 ratios. At a wire TMDS
of 148.5 MHz with N=6144, CTS=148,500 yields f_audio = 48 kHz; at a
wire TMDS of 185.625 MHz with N=6144, CTS=185,625 also yields f_audio
= 48 kHz. The driver picks one TMDS, computes a self-consistent N/CTS
pair, and embeds it. The bug is solely that the TMDS the driver picks
(connector-state nominal) differs from the TMDS on the wire (PHY
actual).


Dmesg diagnostic signature
--------------------------

Failing (seamless handoff, DRM selects 10-bit 1080p from EDID):

    dwhdmiqp-rockchip 27da0000.hdmi: atomic_enable: \
        tmds_candidate=185625000 is_hdmi=1
    DW_HDMI_QP set_sample_rate: tmds=185625000 sr=48000 n=6144 cts=185625
    [audio silent on LG G3]

Working (8-bit mode forced, see workaround below):

    dwhdmiqp-rockchip 27da0000.hdmi: atomic_enable: \
        tmds_candidate=148500000 is_hdmi=1
    DW_HDMI_QP set_sample_rate: tmds=148500000 sr=48000 n=6144 cts=148500
    [audio working on LG G3]

The difference is entirely in which tmds_char_rate bridge_atomic_enable()
receives. If the PHY and DRM state agree (both 148.5 MHz), audio works
because what the AVI declares matches what the wire carries. If they
disagree (DRM: 185.625 MHz, PHY: 148.5 MHz), the AVI declares 185.625
but the wire carries 148.5, and any sink that cross-checks the wire
TMDS against the declared rate (e.g. LG G3 OLED) mutes audio.


Confirmed workaround
--------------------

In dw_hdmi_qp_bridge_tmds_char_rate_valid(), reject 185,625,000 Hz:

    if (rate == 185625000)
        return MODE_CLOCK_HIGH;

This forces DRM to select 8-bit mode (148,500,000 Hz). bridge_atomic_enable()
then stores the correct tmds_char_rate, the AVI InfoFrame declares 8-bit,
the ACR CTS is computed correctly, and the LG G3 produces audio.

Note: 185,625,000 Hz (1080p at 60 10-bit) is only selected via EDID
negotiation during seamless handoff. A genuine modeset at that rate would
correctly reconfigure the PHY and the AVI/ACR inconsistency would not
arise. Blocking this rate in tmds_char_rate_valid() therefore has no
correct-use-case cost on RK3576 with the current BSP. 4K at 60Hz (594 MHz)
is unaffected and continues to work correctly, including audio.


Proposed proper fix
-------------------

The fundamental issue is that bridge_atomic_enable() accepts
conn_state->hdmi.tmds_char_rate without verifying it against the PHY's
actual output frequency.

Option A (no display disruption):

  The HDPTX PHY PLL is already registered as a clock provider. On
  RK3576, rk3576.dtsi gives the hdptxphy node #clock-cells = <0>;
  VOP2 consumes it as "pll_hdmiphy0". If the bridge driver adds a
  new clk pointer (e.g. hdmi->tmds_clk) populated via
  devm_clk_get_optional() at probe and queries clk_get_rate()
  in bridge_atomic_enable(), it can detect the seamless-handoff
  mismatch and use the actual rate:

      /* hdmi->tmds_clk added in struct dw_hdmi_qp; populated at
       * probe via devm_clk_get_optional(dev, "tmds") or similar */
      actual_rate = hdmi->tmds_clk ? clk_get_rate(hdmi->tmds_clk) : 0;
      if (actual_rate && actual_rate != conn_state->hdmi.tmds_char_rate) {
          dev_warn(hdmi->dev,
              "seamless handoff: PHY at %lu Hz, DRM selected %llu Hz; "
              "using actual PHY rate for audio\n",
              actual_rate, conn_state->hdmi.tmds_char_rate);
          hdmi->tmds_char_rate = actual_rate;
      } else {
          hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate;
      }

  This corrects the ACR CTS and AVI InfoFrame without touching the
  PHY configuration or causing a display blank.

Option B (correct but causes brief blank):

  If the driver detects a mismatch, call phy.ops->init() to bring
  the PHY into agreement with the DRM-negotiated state. All
  parameters become consistent at the cost of a single mode change
  on first enable after seamless handoff. This is arguably the more
  correct approach if the intent is to honour what the EDID reports.

Option A is preferred for a seamless user experience. Option B is
preferable if the seamless-handoff-inherited U-Boot mode is not
otherwise suitable for production use.


Relation to Collabora's HDMI 2.0 patch series
----------------------------------------------

The "Add HDMI 2.0 support to DW HDMI QP TX" series (v3, January 2026)
addresses high-TMDS-clock-ratio and scrambling. It does not appear to
address the seamless-handoff tmds_char_rate mismatch described here.
If a revision of that series reads PHY state at probe time it may
incidentally close this gap, but that is not a stated goal. Reviewers
may wish to verify the seamless-handoff path is exercised in testing.


Summary
-------

  Bug:        bridge_atomic_enable() takes EDID-negotiated
              tmds_char_rate (185,625,000) while PHY runs at U-Boot
              rate (148,500,000); the AVI InfoFrame and the ACR CTS
              both reflect the connector-state nominal value, but
              the wire TMDS clock disagrees with what AVI declares
  Symptom:    audio silent on any HDMI 2.1 sink that cross-checks
              wire TMDS against the AVI declaration; HDMI 2.0 sinks
              unaffected (no such cross-check)
  Scope:      RK3576 confirmed; RK3588 structurally identical
  Not a bug:  N/CTS table, IEC60958 channel status, ARC, ALSA path
  Workaround: reject 185,625,000 Hz in tmds_char_rate_valid() so
              DRM picks 8-bit 1080p (148,500,000 Hz) which matches
              the actual PHY rate
  Fix:        compare conn_state->hdmi.tmds_char_rate against actual
              PHY clock at bridge_atomic_enable() and use the
              measured rate when they disagree (Option A) or
              reconfigure the PHY (Option B)

I am happy to test patches on Sige5 + LG G3.

Regards,
Simon
Symple Solutions, Dunedin, New Zealand



More information about the Linux-rockchip mailing list