1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
// SPDX-License-Identifier: MIT
/*
 * Copyright (C) 2025 Intel Corporation
 */

#include <linux/string_choices.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.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);
		}
	}
}