// SPDX-License-Identifier: MIT /* * Copyright (C) 2025 Intel Corporation */ #include #include #include #include "intel_cmtg.h" #include "intel_cmtg_regs.h" #include "intel_crtc.h" #include "intel_de.h" #include "intel_display.h" #include "intel_display_device.h" #include "intel_display_irq.h" #include "intel_display_power.h" #include "intel_display_regs.h" #include "intel_display_types.h" #include "intel_vrr.h" #include "intel_vrr_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 inline enum transcoder to_cmtg_transcoder(enum transcoder cpu_transcoder) { switch (cpu_transcoder) { case TRANSCODER_A: return TRANSCODER_CMTG0; case TRANSCODER_B: return TRANSCODER_CMTG1; default: return INVALID_TRANSCODER; } } static bool intel_cmtg_transcoder_is_secondary(struct intel_display *display, enum transcoder trans) { enum intel_display_power_domain power_domain; 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) 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(TRANSCODER_A)); cmtg_config->cmtg_a_enable = val & CMTG_ENABLE; if (intel_cmtg_has_cmtg_b(display)) { val = intel_de_read(display, TRANS_CMTG_CTL(TRANSCODER_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_all(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(TRANSCODER_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(TRANSCODER_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); } void intel_cmtg_disable(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); u32 clk_sel_clr = 0, interrupt_mask = 0; if (!crtc->cmtg.enabled) return; if (drm_WARN_ON(display->drm, cmtg_transcoder == INVALID_TRANSCODER)) return; crtc->cmtg.enabled = false; intel_de_rmw(display, TRANS_VRR_CTL(display, cmtg_transcoder), VRR_CTL_VRR_ENABLE | VRR_CTL_FLIP_LINE_EN, 0); /* * Use cpu_transcoder for: * 1. Exclusive CMTG registers that do not use the standard transcoder offset * (e.g., TRANS_CMTG_CTL, CMTG_CLK_SEL). * 2. Registers shared between the eDP and CMTG transcoders. * (e.g., TRANS_DDI_FUNC_CTL2). */ intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, cpu_transcoder), CMTG_SECONDARY_MODE, 0); intel_de_rmw(display, CMTG_SCANLINE_GB1(cpu_transcoder), CMTG_HW_GB_ENABLE, 0); intel_de_rmw(display, TRANS_CMTG_CTL(cpu_transcoder), CMTG_ENABLE, 0); if (intel_de_wait_for_clear_ms(display, TRANS_CMTG_CTL(cpu_transcoder), CMTG_STATE, 50)) { drm_WARN(display->drm, 1, "CMTG: %s disable timeout\n", transcoder_name(cpu_transcoder)); return; } clk_sel_clr = cpu_transcoder == TRANSCODER_A ? CMTG_CLK_SEL_A_MASK : CMTG_CLK_SEL_B_MASK; intel_de_rmw(display, CMTG_CLK_SEL, clk_sel_clr, 0); drm_dbg_kms(display->drm, "CMTG: %s disabled\n", transcoder_name(cpu_transcoder)); if (cpu_transcoder == TRANSCODER_A) interrupt_mask = CMTG_VBLANK_A; else if (cpu_transcoder == TRANSCODER_B) interrupt_mask = CMTG_VBLANK_B; intel_display_irq_port_interrupt_mask(display, interrupt_mask, true); } /* * 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_all(display, &cmtg_config); } bool intel_cmtg_is_allowed(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; if ((cpu_transcoder == TRANSCODER_A || cpu_transcoder == TRANSCODER_B) && DISPLAY_VER(display) == 35 && intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) return true; return false; } void intel_cmtg_set_clk_select(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; u32 clk_sel_clr = 0; u32 clk_sel_set = 0; if (!intel_cmtg_is_allowed(crtc_state)) return; if (cpu_transcoder == TRANSCODER_A) { clk_sel_clr = CMTG_CLK_SEL_A_MASK; clk_sel_set = CMTG_CLK_SELECT_PHYA_ENABLE; } else if (cpu_transcoder == TRANSCODER_B) { clk_sel_clr = CMTG_CLK_SEL_B_MASK; clk_sel_set = CMTG_CLK_SELECT_PHYB_ENABLE; } if (clk_sel_set) intel_de_rmw(display, CMTG_CLK_SEL, clk_sel_clr, clk_sel_set); } void intel_cmtg_set_timings(const struct intel_crtc_state *crtc_state, enum set_timing_type type) { enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); if (cmtg_transcoder == INVALID_TRANSCODER) return; if (!intel_cmtg_is_allowed(crtc_state)) return; if (type == LRR) intel_set_transcoder_timings_lrr(crtc_state, cmtg_transcoder); else intel_set_transcoder_timings(crtc_state, cmtg_transcoder); } void intel_cmtg_set_vrr_timings(const struct intel_crtc_state *crtc_state) { enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); if (!intel_cmtg_is_allowed(crtc_state)) return; intel_vrr_set_fixed_rr_timings(crtc_state, cmtg_transcoder); } void intel_cmtg_set_vrr_ctl(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); u32 vrr_ctl; if (!intel_cmtg_is_allowed(crtc_state)) return; vrr_ctl = VRR_CTL_VRR_ENABLE | VRR_CTL_FLIP_LINE_EN | XELPD_VRR_CTL_VRR_GUARDBAND(crtc_state->vrr.guardband); /* TODO: The code below may need to be revisited once CMRR is enabled */ if (crtc_state->vrr.cmrr.enable) vrr_ctl |= VRR_CTL_CMRR_ENABLE; intel_de_write(display, TRANS_VRR_CTL(display, cmtg_transcoder), vrr_ctl); } void intel_cmtg_set_m_n(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); const struct intel_link_m_n *m_n = &crtc_state->dp_m_n; if (!intel_cmtg_is_allowed(crtc_state)) return; intel_de_write(display, PIPE_LINK_M1(display, cmtg_transcoder), m_n->link_m); intel_de_write(display, PIPE_LINK_N1(display, cmtg_transcoder), m_n->link_n); } static void intel_cmtg_enable_sync(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; u32 cmtg_ctl; cmtg_ctl = CMTG_SYNC_TO_PORT | CMTG_ENABLE; intel_de_rmw(display, TRANS_CMTG_CTL(cpu_transcoder), 0, cmtg_ctl); if (intel_de_wait_for_clear_ms(display, TRANS_CMTG_CTL(cpu_transcoder), CMTG_SYNC_TO_PORT, 50)) { drm_WARN(display->drm, 1, "CMTG: %s enable timeout\n", transcoder_name(cpu_transcoder)); } } static void intel_cmtg_enable_ddi(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; u32 interrupt_mask = 0; intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, cpu_transcoder), 0, CMTG_SECONDARY_MODE); intel_de_rmw(display, CMTG_SCANLINE_GB1(cpu_transcoder), 0, CMTG_HW_GB_ENABLE); crtc->cmtg.enabled = true; drm_dbg_kms(display->drm, "CMTG: %s enabled\n", transcoder_name(cpu_transcoder)); /* * TODO: Currently cmtg is enabled along with eDP transcoder so cmtg * interrupt is not enabled through IER, need to do some fine * tuning in future. */ if (cpu_transcoder == TRANSCODER_A) interrupt_mask = CMTG_VBLANK_A; else if (cpu_transcoder == TRANSCODER_B) interrupt_mask = CMTG_VBLANK_B; intel_display_irq_port_interrupt_mask(display, interrupt_mask, false); } /* Bspec: 75253 */ #define DC3CO_ENTRY_LATENCY_US 55 #define DC3CO_EXIT_LATENCY_US 40 static void intel_cmtg_set_hwgb(const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; u32 breakeven_gb; u32 dc5_exit_latency; u32 line_time_us = 75; /* Max default initialization value */ u32 val; if (crtc_state->linetime) line_time_us = DIV_ROUND_UP(crtc_state->linetime, 8); /* Break Even Guardband - DC3co Entry Latency / linetime */ breakeven_gb = DIV_ROUND_UP(DC3CO_ENTRY_LATENCY_US, line_time_us); /* DC5 Exit Latency - DC3co Exit Latency / linetime */ dc5_exit_latency = DIV_ROUND_UP(DC3CO_EXIT_LATENCY_US, line_time_us); val = REG_FIELD_PREP(CMTG_HW_GB_BREAKEVEN_MASK, breakeven_gb) | REG_FIELD_PREP(CMTG_HW_GB_DC5_EXIT_LATENCY_MASK, dc5_exit_latency) | REG_FIELD_PREP(CMTG_HW_GB_UP_LW_BG_DIFF_MASK, 1); intel_de_write(display, CMTG_HW_GB(cpu_transcoder), val); } static void intel_cmtg_restore(const struct intel_crtc_state *crtc_state) { intel_cmtg_set_clk_select(crtc_state); intel_cmtg_set_timings(crtc_state, MODESET); intel_cmtg_set_vrr_timings(crtc_state); intel_cmtg_set_vrr_ctl(crtc_state); intel_cmtg_set_m_n(crtc_state); } void intel_cmtg_program(struct intel_atomic_state *state) { struct intel_display *display = to_intel_display(state); struct intel_crtc *crtc; struct intel_crtc_state *new_crtc_state; bool dc3co_to_dc6 = intel_display_power_get_and_reset_dc3co_to_dc6(display); for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state) { bool modeset = intel_crtc_needs_modeset(new_crtc_state); if (!intel_cmtg_is_allowed(new_crtc_state)) continue; if ((modeset || dc3co_to_dc6) && new_crtc_state->hw.active && !crtc->cmtg.enabled) { if (dc3co_to_dc6) intel_cmtg_restore(new_crtc_state); intel_cmtg_enable_sync(new_crtc_state); intel_cmtg_set_hwgb(new_crtc_state); intel_cmtg_enable_ddi(new_crtc_state); } } }