// SPDX-License-Identifier: MIT
/*
* Copyright ( C ) 2025 Intel Corporation
*/
#include <linux/string_choices.h>
#include <linux/types.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include "intel_cmtg.h"
#include "intel_cmtg_regs.h"
#include "intel_crtc.h"
#include "intel_de.h"
#include "intel_display_device.h"
#include "intel_display_power.h"
#include "intel_display_regs.h"
/**
* DOC : Common Primary Timing Generator ( CMTG )
*
* The CMTG is a timing generator that runs in parallel to transcoders timing
* generators ( TG ) to provide a synchronization mechanism where CMTG acts as
* primary and transcoders TGs act as secondary to the CMTG . The CMTG outputs
* its TG start and frame sync signals to the transcoders that are configured
* as secondary , which use those signals to synchronize their own timing with
* the CMTG ' s .
*
* The CMTG can be used only with eDP or MIPI command mode and supports the
* following use cases :
*
* - Dual eDP : The CMTG can be used to keep two eDP TGs in sync when on a
* dual eDP configuration ( with or without PSR / PSR2 enabled ) .
*
* - Single eDP as secondary : It is also possible to use a single eDP
* configuration with the transcoder TG as secondary to the CMTG . That would
* allow a flow that would not require a modeset on the existing eDP when a
* new eDP is added for a dual eDP configuration with CMTG .
*
* - DC6v : In DC6v , the transcoder might be off but the CMTG keeps running to
* maintain frame timings . When exiting DC6v , the transcoder TG then is
* synced back the CMTG .
*
* Currently , the driver does not use the CMTG , but we need to make sure that
* we disable it in case we inherit a display configuration with it enabled .
*/
/*
* We describe here only the minimum data required to allow us to properly
* disable the CMTG if necessary .
*/
struct intel_cmtg_config {
bool cmtg_a_enable;
/*
* Xe2_LPD adds a second CMTG that can be used for dual eDP async mode .
*/
bool cmtg_b_enable;
bool trans_a_secondary;
bool trans_b_secondary;
};
static bool intel_cmtg_has_cmtg_b(struct intel_display *display)
{
return DISPLAY_VER(display) >= 20 ;
}
static bool intel_cmtg_has_clock_sel(struct intel_display *display)
{
return DISPLAY_VER(display) >= 14 ;
}
static void intel_cmtg_dump_config(struct intel_display *display,
struct intel_cmtg_config *cmtg_config)
{
drm_dbg_kms(display->drm,
"CMTG readout: CMTG A: %s, CMTG B: %s, Transcoder A secondary: %s, Transcoder B secondary: %s\n" ,
str_enabled_disabled(cmtg_config->cmtg_a_enable),
intel_cmtg_has_cmtg_b(display) ? str_enabled_disabled(cmtg_config->cmtg_b_enable) : "n/a" ,
str_yes_no(cmtg_config->trans_a_secondary),
str_yes_no(cmtg_config->trans_b_secondary));
}
static bool intel_cmtg_transcoder_is_secondary(struct intel_display *display,
enum transcoder trans)
{
enum intel_display_power_domain power_domain;
intel_wakeref_t wakeref;
u32 val = 0 ;
if (!HAS_TRANSCODER(display, trans))
return false ;
power_domain = POWER_DOMAIN_TRANSCODER(trans);
with_intel_display_power_if_enabled(display, power_domain, wakeref)
val = intel_de_read(display, TRANS_DDI_FUNC_CTL2(display, trans));
return val & CMTG_SECONDARY_MODE;
}
static void intel_cmtg_get_config(struct intel_display *display,
struct intel_cmtg_config *cmtg_config)
{
u32 val;
val = intel_de_read(display, TRANS_CMTG_CTL_A);
cmtg_config->cmtg_a_enable = val & CMTG_ENABLE;
if (intel_cmtg_has_cmtg_b(display)) {
val = intel_de_read(display, TRANS_CMTG_CTL_B);
cmtg_config->cmtg_b_enable = val & CMTG_ENABLE;
}
cmtg_config->trans_a_secondary = intel_cmtg_transcoder_is_secondary(display, TRANSCODER_A);
cmtg_config->trans_b_secondary = intel_cmtg_transcoder_is_secondary(display, TRANSCODER_B);
}
static bool intel_cmtg_disable_requires_modeset(struct intel_display *display,
struct intel_cmtg_config *cmtg_config)
{
if (DISPLAY_VER(display) >= 20 )
return false ;
return cmtg_config->trans_a_secondary || cmtg_config->trans_b_secondary;
}
static void intel_cmtg_disable(struct intel_display *display,
struct intel_cmtg_config *cmtg_config)
{
u32 clk_sel_clr = 0 ;
u32 clk_sel_set = 0 ;
if (cmtg_config->trans_a_secondary)
intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, TRANSCODER_A),
CMTG_SECONDARY_MODE, 0 );
if (cmtg_config->trans_b_secondary)
intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, TRANSCODER_B),
CMTG_SECONDARY_MODE, 0 );
if (cmtg_config->cmtg_a_enable) {
drm_dbg_kms(display->drm, "Disabling CMTG A\n" );
intel_de_rmw(display, TRANS_CMTG_CTL_A, CMTG_ENABLE, 0 );
clk_sel_clr |= CMTG_CLK_SEL_A_MASK;
clk_sel_set |= CMTG_CLK_SEL_A_DISABLED;
}
if (cmtg_config->cmtg_b_enable) {
drm_dbg_kms(display->drm, "Disabling CMTG B\n" );
intel_de_rmw(display, TRANS_CMTG_CTL_B, CMTG_ENABLE, 0 );
clk_sel_clr |= CMTG_CLK_SEL_B_MASK;
clk_sel_set |= CMTG_CLK_SEL_B_DISABLED;
}
if (intel_cmtg_has_clock_sel(display) && clk_sel_clr)
intel_de_rmw(display, CMTG_CLK_SEL, clk_sel_clr, clk_sel_set);
}
/*
* Read out CMTG configuration and , on platforms that allow disabling it without
* a modeset , do it .
*
* This function must be called before any port PLL is disabled in the general
* sanitization process , because we need whatever port PLL that is providing the
* clock for CMTG to be on before accessing CMTG registers .
*/
void intel_cmtg_sanitize(struct intel_display *display)
{
struct intel_cmtg_config cmtg_config = {};
if (!HAS_CMTG(display))
return ;
intel_cmtg_get_config(display, &cmtg_config);
intel_cmtg_dump_config(display, &cmtg_config);
/*
* FIXME : The driver is not prepared to handle cases where a modeset is
* required for disabling the CMTG : we need a proper way of tracking
* CMTG state and do the right syncronization with respect to triggering
* the modeset as part of the disable sequence .
*/
if (intel_cmtg_disable_requires_modeset(display, &cmtg_config))
return ;
intel_cmtg_disable(display, &cmtg_config);
}
Messung V0.5 in Prozent C=97 H=100 G=98
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-10-03)
¤
*© Formatika GbR, Deutschland