> -----Original Message-----
> From: Golani, Mitulkumar Ajitkumar
> <[email protected]>
> Sent: 16 October 2026 02:08
> To: Kandpal, Suraj <[email protected]>; [email protected];
> [email protected]
> Cc: Nautiyal, Ankit K <[email protected]>; Murthy, Arun R
> <[email protected]>; Kandpal, Suraj <[email protected]>
> Subject: RE: [PATCH v4 07/10] drm/i915/display: Add bits for
> Wa_14021768792 for linkm/n ratio > 10
>
>
>
> > -----Original Message-----
> > From: Intel-gfx <[email protected]> On Behalf Of
> > Suraj Kandpal
> > Sent: 17 September 2026 10:37
> > To: [email protected]; [email protected]
> > Cc: Nautiyal, Ankit K <[email protected]>; Murthy, Arun R
> > <[email protected]>; Kandpal, Suraj <[email protected]>
> > Subject: [PATCH v4 07/10] drm/i915/display: Add bits for
> > Wa_14021768792 for linkm/n ratio > 10
> >
> > From: Ankit Nautiyal <[email protected]>
> >
> > To support a Link M/N ratio between 10.0 and 15.0, for some BMG
> > ultrajoiner cases we need Wa_14021768792.
> >
> > To bypass the hardware limitation within the Timing Generator DDA
> > (TGDDA), we need to program the LINKM and LINKN registers as defined in
> the WA.
> > Along with this we also need the relevant bits in HDMI_EMP_DATA and
> > CHICKEN_TRANS.
> >
> > Add the bits for the WA and a new member 'bypass_m_n_ratio_limit' to
> > track if we need to bypass the Link M/N ratio limit in the intel_link_m_n
> structure.
> >
> > Per Bspec the extended LINK_N field is two nibbles: bits 27:24 hold
> > INT(Link M / Link N) and bits 31:28 hold CEIL(Link M / Link N).
> >
> > CHICKEN_TRANS and HDMI_EMP_DATA are programmed before LINK_N,
> since
> > writing LINK_N arms the double buffered update of the M/N registers,
> > and all of the programming has to be done before the transcoder is
> enabled.
> > Both are also explicitly cleared when the mechanism is not in use, so
> > that state cannot leak from a previous modeset.
> >
> > Only bypass_m_n_ratio_limit is compared by the state checker, not the
> > ratio integer and fraction themselves. That is deliberate: both are
> > derived from Link M and Link N, which are compared already, so the
> > thing worth verifying is whether the workaround ended up being applied
> or not.
> >
> > Unlike the rest of the M/N registers, CHICKEN_TRANS and
> HDMI_EMP_DATA
> > are not double buffered, so the flag has to be compared outside the
> > fastset exemption that PIPE_CONF_CHECK_M_N() has for update_m_n.
> > Otherwise a seamless M/N update could switch the DDA out of the
> > workaround on a live transcoder, with the Link M/N values only
> > following at the next MSA.
> >
> > v2:
> > -Drop the drm_WARN_ON() on the extended LINK_N field in
> intel_get_m_n():
> > it is derived from Link M and Link N, so the state checker comparison
> > of those and of bypass_m_n_ratio_limit already covers it. (Sashiko)
> > -Compare bypass_m_n_ratio_limit unconditionally, so that a change to
> > it forces a full modeset instead of being applied during a seamless
> > M/N fastset. (Sashiko)
> >
> > Bspec: 49266
> > Signed-off-by: Ankit Nautiyal <[email protected]>
> > Signed-off-by: Suraj Kandpal <[email protected]>
> > ---
> > drivers/gpu/drm/i915/display/intel_display.c | 93
> > ++++++++++++++++++- drivers/gpu/drm/i915/display/intel_display.h | 2
> > + .../gpu/drm/i915/display/intel_display_regs.h | 10 ++
> > .../drm/i915/display/intel_display_types.h | 2 +
> > 4 files changed, 102 insertions(+), 5 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/i915/display/intel_display.c
> > b/drivers/gpu/drm/i915/display/intel_display.c
> > index 6d2025e10995..1e9d8943626a 100644
> > --- a/drivers/gpu/drm/i915/display/intel_display.c
> > +++ b/drivers/gpu/drm/i915/display/intel_display.c
> > @@ -2706,6 +2706,15 @@ void intel_zero_m_n(struct intel_link_m_n
> *m_n)
> > m_n->tu = 1;
> > }
> >
> > +static u8 bmg_get_link_n_ext(const struct intel_link_m_n *m_n) {
> > + if (!m_n->link_n)
> > + return 0;
> > +
> > + return PIPE_LINK_N1_EXT_INT(m_n->link_m / m_n->link_n) |
> > + PIPE_LINK_N1_EXT_CEIL(DIV_ROUND_UP(m_n->link_m,
> > m_n->link_n)); }
> > +
> > void intel_set_m_n(struct intel_display *display,
> > const struct intel_link_m_n *m_n,
> > intel_reg_t data_m_reg, intel_reg_t data_n_reg, @@ -
> > 2717,7 +2726,9 @@ void intel_set_m_n(struct intel_display *display,
> > intel_de_write(display, data_n_reg, m_n->data_n);
> > intel_de_write(display, link_m_reg, m_n->link_m);
> >
> > - if (DISPLAY_VER(display) >= 14)
> > + if (DISPLAY_VER(display) >= 14 && m_n->bypass_m_n_ratio_limit)
> > + link_n |=
> > PIPE_LINK_N1_EXTENDED(bmg_get_link_n_ext(m_n));
> > + else if (DISPLAY_VER(display) >= 14)
> > link_n &= ~PIPE_LINK_N1_EXTENDED_MASK;
> > else
> > link_n &= DATA_LINK_M_N_MASK;
> > @@ -2738,6 +2749,26 @@ bool intel_cpu_transcoder_has_m2_n2(struct
> > intel_display *display,
> > return IS_DISPLAY_VER(display, 5, 7) ||
> > display->platform.cherryview; }
> >
> > +static void bmg_enhanced_tgdda_write(struct intel_crtc *crtc,
> > + enum transcoder transcoder,
> > + const struct intel_link_m_n *m_n) {
> > + struct intel_display *display = to_intel_display(crtc);
> > + bool enable = m_n->bypass_m_n_ratio_limit && m_n->link_n;
> > + u32 link_m_frac = enable ? m_n->link_m % m_n->link_n : 0;
> > +
> > + if (!intel_display_wa(display, INTEL_DISPLAY_WA_14021768792))
> > + return;
> > +
> > + intel_de_rmw(display, HDMI_EMP_DATA(display, transcoder),
> > + HDMI_EMP_DATA_LINK_M_FRAC_MASK,
> > + REG_FIELD_PREP(HDMI_EMP_DATA_LINK_M_FRAC_MASK,
> > link_m_frac));
> > +
> > + intel_de_rmw(display, CHICKEN_TRANS(display, transcoder),
> > + DP_ENHANCED_TGDDA_ENABLE,
> > + enable ? DP_ENHANCED_TGDDA_ENABLE : 0); }
> > +
> > void intel_cpu_transcoder_set_m1_n1(struct intel_crtc *crtc,
> > enum transcoder transcoder,
> > const struct intel_link_m_n *m_n) @@ -
> > 2745,6 +2776,8 @@ void intel_cpu_transcoder_set_m1_n1(struct
> > intel_crtc *crtc,
> > struct intel_display *display = to_intel_display(crtc);
> > enum pipe pipe = crtc->pipe;
> >
> > + bmg_enhanced_tgdda_write(crtc, transcoder, m_n);
> > +
> > if (DISPLAY_VER(display) >= 5)
> > intel_set_m_n(display, m_n,
> > PIPE_DATA_M1(display, transcoder), @@ -3495,6
> > +3528,49 @@ void intel_get_m_n(struct intel_display *display,
> > m_n->tu = REG_FIELD_GET(TU_SIZE_MASK, intel_de_read(display,
> > data_m_reg)) + 1; }
> >
> > +bool intel_display_can_bypass_m_n_limit(struct intel_display *display,
> > + int m_n_ratio,
> > + enum pipe pipe)
> > +{
> > + if (!intel_display_wa(display, INTEL_DISPLAY_WA_14021768792))
> > + return false;
> > +
> > + if (m_n_ratio > 15)
> > + return false;
> > +
> > + if (pipe != PIPE_A)
> > + return false;
>
> Could we check whether this should use the transcoder (TRANSCODER_A)
> instead of crtc->pipe ?
Sure will move check t
> For ultrajoiner, could the secondary pipes report bypass=false even though
> their computed state, copied from the primary, has bypass=true ?
>
> > +
> > + return true;
> > +}
> > +
> > +static void bmg_enhanced_tgdda_read(struct intel_crtc *crtc,
> > + enum transcoder transcoder,
> > + struct intel_link_m_n *m_n)
> > +{
> > + struct intel_display *display = to_intel_display(crtc);
> > + u32 link_m_frac;
> > + int m_n_ratio;
> > +
> > + if (!m_n->link_n)
> > + return;
> > +
> > + m_n_ratio = DIV_ROUND_UP(m_n->link_m, m_n->link_n);
> > +
> > + if (!intel_display_can_bypass_m_n_limit(display, m_n_ratio, crtc-
> > >pipe))
> > + return;
> > +
> > + if (!(intel_de_read(display, CHICKEN_TRANS(display, transcoder)) &
> > + DP_ENHANCED_TGDDA_ENABLE))
> > + return;
> > +
> > + link_m_frac =
> > REG_FIELD_GET(HDMI_EMP_DATA_LINK_M_FRAC_MASK,
> > + intel_de_read(display,
> > HDMI_EMP_DATA(display, transcoder)));
> > +
> > + if (link_m_frac == m_n->link_m % m_n->link_n)
> > + m_n->bypass_m_n_ratio_limit = true; }
> > +
> > void intel_cpu_transcoder_get_m1_n1(struct intel_crtc *crtc,
> > enum transcoder transcoder,
> > struct intel_link_m_n *m_n)
> > @@ -3512,6 +3588,8 @@ void intel_cpu_transcoder_get_m1_n1(struct
> > intel_crtc *crtc,
> > intel_get_m_n(display, m_n,
> > PIPE_DATA_M_G4X(pipe),
> > PIPE_DATA_N_G4X(pipe),
> > PIPE_LINK_M_G4X(pipe),
> > PIPE_LINK_N_G4X(pipe));
> > +
> > + bmg_enhanced_tgdda_read(crtc, transcoder, m_n);
> > }
> >
> > void intel_cpu_transcoder_get_m2_n2(struct intel_crtc *crtc, @@
> > -5002,7
> > +5080,8 @@ intel_compare_link_m_n(const struct intel_link_m_n *m_n,
> > m_n->data_m == m2_n2->data_m &&
> > m_n->data_n == m2_n2->data_n &&
> > m_n->link_m == m2_n2->link_m &&
> > - m_n->link_n == m2_n2->link_n;
> > + m_n->link_n == m2_n2->link_n &&
> > + m_n->bypass_m_n_ratio_limit == m2_n2-
> > >bypass_m_n_ratio_limit;
> > }
> >
> > static bool
> > @@ -5277,18 +5356,20 @@ intel_pipe_config_compare(const struct
> > intel_crtc_state *current_config,
> > if (!intel_compare_link_m_n(¤t_config->name, \
> > &pipe_config->name)) { \
> > pipe_config_mismatch(&p, fastset, crtc, __stringify(name), \
> > - "(expected tu %i data %i/%i link %i/%i, " \
> > - "found tu %i, data %i/%i link %i/%i)", \
> > + "(expected tu %i data %i/%i link %i/%i
> > m_n_bypass %s, " \
> > + "found tu %i, data %i/%i link %i/%i
> > m_n_bypass %s)", \
> > current_config->name.tu, \
> > current_config->name.data_m, \
> > current_config->name.data_n, \
> > current_config->name.link_m, \
> > current_config->name.link_n, \
> > + str_yes_no(current_config-
> > >name.bypass_m_n_ratio_limit), \
> > pipe_config->name.tu, \
> > pipe_config->name.data_m, \
> > pipe_config->name.data_n, \
> > pipe_config->name.link_m, \
> > - pipe_config->name.link_n); \
> > + pipe_config->name.link_n, \
> > + str_yes_no(pipe_config-
> > >name.bypass_m_n_ratio_limit)); \
> > ret = false; \
> > } \
> > } while (0)
> > @@ -5436,6 +5517,8 @@ intel_pipe_config_compare(const struct
> > intel_crtc_state *current_config,
> > PIPE_CONF_CHECK_M_N(dp_m2_n2);
> > }
> >
> > + PIPE_CONF_CHECK_BOOL(dp_m_n.bypass_m_n_ratio_limit);
>
> The flag is compared twice. The unconditional BOOL check is enough.
Sure will do it
>
> Please also print it in intel_crtc_state_dump().
Will add it.
Regards,
Suraj Kandpal
>
> > +
> > PIPE_CONF_CHECK_X(output_types);
> >
> > PIPE_CONF_CHECK_I(framestart_delay);
> > diff --git a/drivers/gpu/drm/i915/display/intel_display.h
> > b/drivers/gpu/drm/i915/display/intel_display.h
> > index 404168f4213b..55e676655120 100644
> > --- a/drivers/gpu/drm/i915/display/intel_display.h
> > +++ b/drivers/gpu/drm/i915/display/intel_display.h
> > @@ -525,5 +525,7 @@ bool intel_scanout_needs_vtd_wa(struct
> > intel_display *display); int intel_crtc_num_joined_pipes(const struct
> > intel_crtc_state *crtc_state); void
> > intel_display_compute_link_m_n(u32
> > *link_m, u32 *link_n,
> > int pixel_clock, int link_symbol_clock);
> > +bool intel_display_can_bypass_m_n_limit(struct intel_display *display,
> > + int m_n_ratio, enum pipe pipe);
> >
> > #endif
> > diff --git a/drivers/gpu/drm/i915/display/intel_display_regs.h
> > b/drivers/gpu/drm/i915/display/intel_display_regs.h
> > index 57f388c958b4..81f9661357b6 100644
> > --- a/drivers/gpu/drm/i915/display/intel_display_regs.h
> > +++ b/drivers/gpu/drm/i915/display/intel_display_regs.h
> > @@ -550,6 +550,11 @@
> > #define _TRANS_MULT_B 0x6102c
> > #define TRANS_MULT(dev_priv, trans) _MMIO_TRANS2(dev_priv,
> (trans),
> > _TRANS_MULT_A)
> >
> > +#define _HDMI_EMP_DATA_A 0x600d8
> > +#define _HDMI_EMP_DATA_B 0x610d8
> > +#define HDMI_EMP_DATA_LINK_M_FRAC_MASK REG_GENMASK(23, 0)
> > +#define HDMI_EMP_DATA(display, trans) _MMIO_TRANS2((display),
> > (trans), _HDMI_EMP_DATA_A)
> > +
> > /* Hotplug control (945+ only) */
> > #define PORT_HOTPLUG_EN(dev_priv)
> > _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61110)
> > #define PORTB_HOTPLUG_INT_EN (1 << 29)
> > @@ -1192,6 +1197,10 @@
> > #define _PIPEB_LINK_N1 0x61044
> > #define PIPE_LINK_N1_EXTENDED_MASK REG_GENMASK(31, 24)
> > #define PIPE_LINK_N1_EXTENDED(val)
> > REG_FIELD_PREP(PIPE_LINK_N1_EXTENDED_MASK, (val))
> > +#define PIPE_LINK_N1_EXT_CEIL_MASK REG_GENMASK(7, 4)
> > +#define PIPE_LINK_N1_EXT_CEIL(val)
> > REG_FIELD_PREP(PIPE_LINK_N1_EXT_CEIL_MASK, (val))
> > +#define PIPE_LINK_N1_EXT_INT_MASK REG_GENMASK(3, 0)
> > +#define PIPE_LINK_N1_EXT_INT(val)
> > REG_FIELD_PREP(PIPE_LINK_N1_EXT_INT_MASK, (val)) #define
> > PIPE_LINK_N1(dev_priv, tran) _MMIO_TRANS2(dev_priv, tran,
> > _PIPEA_LINK_N1)
> >
> > #define _PIPEA_LINK_M2 0x60048
> > @@ -1710,6 +1719,7 @@
> > #define PSR2_ADD_VERTICAL_LINE_COUNT REG_BIT(15)
> > #define DP_FEC_BS_JITTER_WA REG_BIT(15)
> > #define PSR2_VSC_ENABLE_PROG_HEADER REG_BIT(12)
> > +#define DP_ENHANCED_TGDDA_ENABLE REG_BIT(11)
> > #define DP_DSC_INSERT_SF_AT_EOL_WA REG_BIT(4)
> > #define HDCP_LINE_REKEY_DISABLE REG_BIT(0)
> >
> > diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h
> > b/drivers/gpu/drm/i915/display/intel_display_types.h
> > index a5f18ac8a7d0..7a5286db977e 100644
> > --- a/drivers/gpu/drm/i915/display/intel_display_types.h
> > +++ b/drivers/gpu/drm/i915/display/intel_display_types.h
> > @@ -985,6 +985,8 @@ struct intel_link_m_n {
> > u32 data_n;
> > u32 link_m;
> > u32 link_n;
> > + /* Wa_14021768792 for linkm/n ratio > 10 */
> > + bool bypass_m_n_ratio_limit;
> > };
> >
> > struct intel_csc_matrix {
> > --
> > 2.34.1