mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 14:53:58 -04:00
We need to make sure we return some port clock in case we have unsupported LT PHY modes or if we were not able to read the LT PHY state for whatever reason and the mode ends up being 0. Signed-off-by: Suraj Kandpal <suraj.kandpal@intel.com> Reviewed-by: Ankit Nautiyal <ankit.k.nautiyal@intel.com> Link: https://patch.msgid.link/20260105055937.136522-3-suraj.kandpal@intel.com
2322 lines
57 KiB
C
2322 lines
57 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2025 Intel Corporation
|
|
*/
|
|
|
|
#include <drm/drm_print.h>
|
|
|
|
#include "i915_reg.h"
|
|
#include "intel_cx0_phy.h"
|
|
#include "intel_cx0_phy_regs.h"
|
|
#include "intel_ddi.h"
|
|
#include "intel_ddi_buf_trans.h"
|
|
#include "intel_de.h"
|
|
#include "intel_display.h"
|
|
#include "intel_display_types.h"
|
|
#include "intel_display_utils.h"
|
|
#include "intel_dpll_mgr.h"
|
|
#include "intel_hdmi.h"
|
|
#include "intel_lt_phy.h"
|
|
#include "intel_lt_phy_regs.h"
|
|
#include "intel_panel.h"
|
|
#include "intel_psr.h"
|
|
#include "intel_tc.h"
|
|
|
|
#define for_each_lt_phy_lane_in_mask(__lane_mask, __lane) \
|
|
for ((__lane) = 0; (__lane) < 2; (__lane)++) \
|
|
for_each_if((__lane_mask) & BIT(__lane))
|
|
|
|
#define INTEL_LT_PHY_LANE0 BIT(0)
|
|
#define INTEL_LT_PHY_LANE1 BIT(1)
|
|
#define INTEL_LT_PHY_BOTH_LANES (INTEL_LT_PHY_LANE1 |\
|
|
INTEL_LT_PHY_LANE0)
|
|
#define MODE_DP 3
|
|
#define MODE_HDMI_20 4
|
|
#define Q32_TO_INT(x) ((x) >> 32)
|
|
#define Q32_TO_FRAC(x) ((x) & 0xFFFFFFFF)
|
|
#define DCO_MIN_FREQ_MHZ 11850
|
|
#define REF_CLK_KHZ 38400
|
|
#define TDC_RES_MULTIPLIER 10000000ULL
|
|
|
|
struct phy_param_t {
|
|
u32 val;
|
|
u32 addr;
|
|
};
|
|
|
|
struct lt_phy_params {
|
|
struct phy_param_t pll_reg4;
|
|
struct phy_param_t pll_reg3;
|
|
struct phy_param_t pll_reg5;
|
|
struct phy_param_t pll_reg57;
|
|
struct phy_param_t lf;
|
|
struct phy_param_t tdc;
|
|
struct phy_param_t ssc;
|
|
struct phy_param_t bias2;
|
|
struct phy_param_t bias_trim;
|
|
struct phy_param_t dco_med;
|
|
struct phy_param_t dco_fine;
|
|
struct phy_param_t ssc_inj;
|
|
struct phy_param_t surv_bonus;
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_dp_rbr = {
|
|
.clock = 162000,
|
|
.config = {
|
|
0x83,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x5, 0xa, 0x2a, 0x20 },
|
|
{ 0x80, 0x0, 0x0, 0x0 },
|
|
{ 0x4, 0x4, 0x82, 0x28 },
|
|
{ 0xfa, 0x16, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x5, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x4b, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0a },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_dp_hbr1 = {
|
|
.clock = 270000,
|
|
.config = {
|
|
0x8b,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x3, 0xca, 0x34, 0xa0 },
|
|
{ 0xe0, 0x0, 0x0, 0x0 },
|
|
{ 0x5, 0x4, 0x81, 0xad },
|
|
{ 0xfa, 0x11, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x7, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x43, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0d },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_dp_hbr2 = {
|
|
.clock = 540000,
|
|
.config = {
|
|
0x93,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x1, 0x4d, 0x34, 0xa0 },
|
|
{ 0xe0, 0x0, 0x0, 0x0 },
|
|
{ 0xa, 0x4, 0x81, 0xda },
|
|
{ 0xfa, 0x11, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x7, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x43, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0d },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_dp_hbr3 = {
|
|
.clock = 810000,
|
|
.config = {
|
|
0x9b,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x1, 0x4a, 0x34, 0xa0 },
|
|
{ 0xe0, 0x0, 0x0, 0x0 },
|
|
{ 0x5, 0x4, 0x80, 0xa8 },
|
|
{ 0xfa, 0x11, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x7, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x43, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0d },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_dp_uhbr10 = {
|
|
.clock = 1000000,
|
|
.config = {
|
|
0x43,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x85,
|
|
0x85,
|
|
0x85,
|
|
0x85,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x1, 0xa, 0x20, 0x80 },
|
|
{ 0x6a, 0xaa, 0xaa, 0xab },
|
|
{ 0x0, 0x3, 0x4, 0x94 },
|
|
{ 0xfa, 0x1c, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x4, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x45, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x14, 0x2a, 0x14 },
|
|
{ 0x0, 0x5b, 0xe0, 0x8 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_dp_uhbr13_5 = {
|
|
.clock = 1350000,
|
|
.config = {
|
|
0xcb,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x2, 0x9, 0x2b, 0xe0 },
|
|
{ 0x90, 0x0, 0x0, 0x0 },
|
|
{ 0x8, 0x4, 0x80, 0xe0 },
|
|
{ 0xfa, 0x15, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x6, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x49, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x57, 0xe0, 0x0c },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_dp_uhbr20 = {
|
|
.clock = 2000000,
|
|
.config = {
|
|
0x53,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x85,
|
|
0x85,
|
|
0x85,
|
|
0x85,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
0x86,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x1, 0xa, 0x20, 0x80 },
|
|
{ 0x6a, 0xaa, 0xaa, 0xab },
|
|
{ 0x0, 0x3, 0x4, 0x94 },
|
|
{ 0xfa, 0x1c, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x4, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x45, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x14, 0x2a, 0x14 },
|
|
{ 0x0, 0x5b, 0xe0, 0x8 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state * const xe3plpd_lt_dp_tables[] = {
|
|
&xe3plpd_lt_dp_rbr,
|
|
&xe3plpd_lt_dp_hbr1,
|
|
&xe3plpd_lt_dp_hbr2,
|
|
&xe3plpd_lt_dp_hbr3,
|
|
&xe3plpd_lt_dp_uhbr10,
|
|
&xe3plpd_lt_dp_uhbr13_5,
|
|
&xe3plpd_lt_dp_uhbr20,
|
|
NULL,
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_edp_2_16 = {
|
|
.clock = 216000,
|
|
.config = {
|
|
0xa3,
|
|
0x2d,
|
|
0x1,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x3, 0xca, 0x2a, 0x20 },
|
|
{ 0x80, 0x0, 0x0, 0x0 },
|
|
{ 0x6, 0x4, 0x81, 0xbc },
|
|
{ 0xfa, 0x16, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x5, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x4b, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0a },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_edp_2_43 = {
|
|
.clock = 243000,
|
|
.config = {
|
|
0xab,
|
|
0x2d,
|
|
0x1,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x3, 0xca, 0x2f, 0x60 },
|
|
{ 0xb0, 0x0, 0x0, 0x0 },
|
|
{ 0x6, 0x4, 0x81, 0xbc },
|
|
{ 0xfa, 0x13, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x6, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x47, 0x48, 0x0, 0x0 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0c },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_edp_3_24 = {
|
|
.clock = 324000,
|
|
.config = {
|
|
0xb3,
|
|
0x2d,
|
|
0x1,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x2, 0x8a, 0x2a, 0x20 },
|
|
{ 0x80, 0x0, 0x0, 0x0 },
|
|
{ 0x6, 0x4, 0x81, 0x28 },
|
|
{ 0xfa, 0x16, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x5, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x4b, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0a },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_edp_4_32 = {
|
|
.clock = 432000,
|
|
.config = {
|
|
0xbb,
|
|
0x2d,
|
|
0x1,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x1, 0x4d, 0x2a, 0x20 },
|
|
{ 0x80, 0x0, 0x0, 0x0 },
|
|
{ 0xc, 0x4, 0x81, 0xbc },
|
|
{ 0xfa, 0x16, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x5, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x4b, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x5b, 0xe0, 0x0a },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_edp_6_75 = {
|
|
.clock = 675000,
|
|
.config = {
|
|
0xdb,
|
|
0x2d,
|
|
0x1,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x1, 0x4a, 0x2b, 0xe0 },
|
|
{ 0x90, 0x0, 0x0, 0x0 },
|
|
{ 0x6, 0x4, 0x80, 0xa8 },
|
|
{ 0xfa, 0x15, 0x83, 0x11 },
|
|
{ 0x80, 0x0f, 0xf9, 0x53 },
|
|
{ 0x84, 0x26, 0x6, 0x4 },
|
|
{ 0x0, 0xe0, 0x1, 0x0 },
|
|
{ 0x49, 0x48, 0x0, 0x0 },
|
|
{ 0x27, 0x8, 0x0, 0x0 },
|
|
{ 0x5a, 0x13, 0x29, 0x13 },
|
|
{ 0x0, 0x57, 0xe0, 0x0c },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state * const xe3plpd_lt_edp_tables[] = {
|
|
&xe3plpd_lt_dp_rbr,
|
|
&xe3plpd_lt_edp_2_16,
|
|
&xe3plpd_lt_edp_2_43,
|
|
&xe3plpd_lt_dp_hbr1,
|
|
&xe3plpd_lt_edp_3_24,
|
|
&xe3plpd_lt_edp_4_32,
|
|
&xe3plpd_lt_dp_hbr2,
|
|
&xe3plpd_lt_edp_6_75,
|
|
&xe3plpd_lt_dp_hbr3,
|
|
NULL,
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_hdmi_252 = {
|
|
.clock = 25200,
|
|
.config = {
|
|
0x84,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x0c, 0x15, 0x27, 0x60 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
{ 0x8, 0x4, 0x98, 0x28 },
|
|
{ 0x42, 0x0, 0x84, 0x10 },
|
|
{ 0x80, 0x0f, 0xd9, 0xb5 },
|
|
{ 0x86, 0x0, 0x0, 0x0 },
|
|
{ 0x1, 0xa0, 0x1, 0x0 },
|
|
{ 0x4b, 0x0, 0x0, 0x0 },
|
|
{ 0x28, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x14, 0x2a, 0x14 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_hdmi_272 = {
|
|
.clock = 27200,
|
|
.config = {
|
|
0x84,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x0b, 0x15, 0x26, 0xa0 },
|
|
{ 0x60, 0x0, 0x0, 0x0 },
|
|
{ 0x8, 0x4, 0x96, 0x28 },
|
|
{ 0xfa, 0x0c, 0x84, 0x11 },
|
|
{ 0x80, 0x0f, 0xd9, 0x53 },
|
|
{ 0x86, 0x0, 0x0, 0x0 },
|
|
{ 0x1, 0xa0, 0x1, 0x0 },
|
|
{ 0x4b, 0x0, 0x0, 0x0 },
|
|
{ 0x28, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x14, 0x2a, 0x14 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_hdmi_742p5 = {
|
|
.clock = 74250,
|
|
.config = {
|
|
0x84,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x4, 0x15, 0x26, 0xa0 },
|
|
{ 0x60, 0x0, 0x0, 0x0 },
|
|
{ 0x8, 0x4, 0x88, 0x28 },
|
|
{ 0xfa, 0x0c, 0x84, 0x11 },
|
|
{ 0x80, 0x0f, 0xd9, 0x53 },
|
|
{ 0x86, 0x0, 0x0, 0x0 },
|
|
{ 0x1, 0xa0, 0x1, 0x0 },
|
|
{ 0x4b, 0x0, 0x0, 0x0 },
|
|
{ 0x28, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x14, 0x2a, 0x14 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_hdmi_1p485 = {
|
|
.clock = 148500,
|
|
.config = {
|
|
0x84,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x2, 0x15, 0x26, 0xa0 },
|
|
{ 0x60, 0x0, 0x0, 0x0 },
|
|
{ 0x8, 0x4, 0x84, 0x28 },
|
|
{ 0xfa, 0x0c, 0x84, 0x11 },
|
|
{ 0x80, 0x0f, 0xd9, 0x53 },
|
|
{ 0x86, 0x0, 0x0, 0x0 },
|
|
{ 0x1, 0xa0, 0x1, 0x0 },
|
|
{ 0x4b, 0x0, 0x0, 0x0 },
|
|
{ 0x28, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x14, 0x2a, 0x14 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state xe3plpd_lt_hdmi_5p94 = {
|
|
.clock = 594000,
|
|
.config = {
|
|
0x84,
|
|
0x2d,
|
|
0x0,
|
|
},
|
|
.addr_msb = {
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x87,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
0x88,
|
|
},
|
|
.addr_lsb = {
|
|
0x10,
|
|
0x0c,
|
|
0x14,
|
|
0xe4,
|
|
0x0c,
|
|
0x10,
|
|
0x14,
|
|
0x18,
|
|
0x48,
|
|
0x40,
|
|
0x4c,
|
|
0x24,
|
|
0x44,
|
|
},
|
|
.data = {
|
|
{ 0x0, 0x4c, 0x2, 0x0 },
|
|
{ 0x0, 0x95, 0x26, 0xa0 },
|
|
{ 0x60, 0x0, 0x0, 0x0 },
|
|
{ 0x8, 0x4, 0x81, 0x28 },
|
|
{ 0xfa, 0x0c, 0x84, 0x11 },
|
|
{ 0x80, 0x0f, 0xd9, 0x53 },
|
|
{ 0x86, 0x0, 0x0, 0x0 },
|
|
{ 0x1, 0xa0, 0x1, 0x0 },
|
|
{ 0x4b, 0x0, 0x0, 0x0 },
|
|
{ 0x28, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x14, 0x2a, 0x14 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
{ 0x0, 0x0, 0x0, 0x0 },
|
|
},
|
|
};
|
|
|
|
static const struct intel_lt_phy_pll_state * const xe3plpd_lt_hdmi_tables[] = {
|
|
&xe3plpd_lt_hdmi_252,
|
|
&xe3plpd_lt_hdmi_272,
|
|
&xe3plpd_lt_hdmi_742p5,
|
|
&xe3plpd_lt_hdmi_1p485,
|
|
&xe3plpd_lt_hdmi_5p94,
|
|
NULL,
|
|
};
|
|
|
|
static u8 intel_lt_phy_get_owned_lane_mask(struct intel_encoder *encoder)
|
|
{
|
|
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
|
|
|
|
if (!intel_tc_port_in_dp_alt_mode(dig_port))
|
|
return INTEL_LT_PHY_BOTH_LANES;
|
|
|
|
return intel_tc_port_max_lane_count(dig_port) > 2
|
|
? INTEL_LT_PHY_BOTH_LANES : INTEL_LT_PHY_LANE0;
|
|
}
|
|
|
|
static u8 intel_lt_phy_read(struct intel_encoder *encoder, u8 lane_mask, u16 addr)
|
|
{
|
|
return intel_cx0_read(encoder, lane_mask, addr);
|
|
}
|
|
|
|
static void intel_lt_phy_write(struct intel_encoder *encoder,
|
|
u8 lane_mask, u16 addr, u8 data, bool committed)
|
|
{
|
|
intel_cx0_write(encoder, lane_mask, addr, data, committed);
|
|
}
|
|
|
|
static void intel_lt_phy_rmw(struct intel_encoder *encoder,
|
|
u8 lane_mask, u16 addr, u8 clear, u8 set, bool committed)
|
|
{
|
|
intel_cx0_rmw(encoder, lane_mask, addr, clear, set, committed);
|
|
}
|
|
|
|
static void intel_lt_phy_clear_status_p2p(struct intel_encoder *encoder,
|
|
int lane)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
|
|
intel_de_rmw(display,
|
|
XE3PLPD_PORT_P2M_MSGBUS_STATUS_P2P(encoder->port, lane),
|
|
XELPDP_PORT_P2M_RESPONSE_READY, 0);
|
|
}
|
|
|
|
static void
|
|
assert_dc_off(struct intel_display *display)
|
|
{
|
|
bool enabled;
|
|
|
|
enabled = intel_display_power_is_enabled(display, POWER_DOMAIN_DC_OFF);
|
|
drm_WARN_ON(display->drm, !enabled);
|
|
}
|
|
|
|
static int __intel_lt_phy_p2p_write_once(struct intel_encoder *encoder,
|
|
int lane, u16 addr, u8 data,
|
|
i915_reg_t mac_reg_addr,
|
|
u8 expected_mac_val)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
enum port port = encoder->port;
|
|
enum phy phy = intel_encoder_to_phy(encoder);
|
|
int ack;
|
|
u32 val;
|
|
|
|
if (intel_de_wait_for_clear_ms(display, XELPDP_PORT_M2P_MSGBUS_CTL(display, port, lane),
|
|
XELPDP_PORT_P2P_TRANSACTION_PENDING,
|
|
XELPDP_MSGBUS_TIMEOUT_MS)) {
|
|
drm_dbg_kms(display->drm,
|
|
"PHY %c Timeout waiting for previous transaction to complete. Resetting bus.\n",
|
|
phy_name(phy));
|
|
intel_cx0_bus_reset(encoder, lane);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_P2M_MSGBUS_STATUS(display, port, lane), 0, 0);
|
|
|
|
intel_de_write(display, XELPDP_PORT_M2P_MSGBUS_CTL(display, port, lane),
|
|
XELPDP_PORT_P2P_TRANSACTION_PENDING |
|
|
XELPDP_PORT_M2P_COMMAND_WRITE_COMMITTED |
|
|
XELPDP_PORT_M2P_DATA(data) |
|
|
XELPDP_PORT_M2P_ADDRESS(addr));
|
|
|
|
ack = intel_cx0_wait_for_ack(encoder, XELPDP_PORT_P2M_COMMAND_WRITE_ACK, lane, &val);
|
|
if (ack < 0)
|
|
return ack;
|
|
|
|
if (val & XELPDP_PORT_P2M_ERROR_SET) {
|
|
drm_dbg_kms(display->drm,
|
|
"PHY %c Error occurred during P2P write command. Status: 0x%x\n",
|
|
phy_name(phy), val);
|
|
intel_lt_phy_clear_status_p2p(encoder, lane);
|
|
intel_cx0_bus_reset(encoder, lane);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* RE-VISIT:
|
|
* This needs to be added to give PHY time to set everything up this was a requirement
|
|
* to get the display up and running
|
|
* This is the time PHY takes to settle down after programming the PHY.
|
|
*/
|
|
udelay(150);
|
|
intel_clear_response_ready_flag(encoder, lane);
|
|
intel_lt_phy_clear_status_p2p(encoder, lane);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __intel_lt_phy_p2p_write(struct intel_encoder *encoder,
|
|
int lane, u16 addr, u8 data,
|
|
i915_reg_t mac_reg_addr,
|
|
u8 expected_mac_val)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
enum phy phy = intel_encoder_to_phy(encoder);
|
|
int i, status;
|
|
|
|
assert_dc_off(display);
|
|
|
|
/* 3 tries is assumed to be enough to write successfully */
|
|
for (i = 0; i < 3; i++) {
|
|
status = __intel_lt_phy_p2p_write_once(encoder, lane, addr, data, mac_reg_addr,
|
|
expected_mac_val);
|
|
|
|
if (status == 0)
|
|
return;
|
|
}
|
|
|
|
drm_err_once(display->drm,
|
|
"PHY %c P2P Write %04x failed after %d retries.\n", phy_name(phy), addr, i);
|
|
}
|
|
|
|
static void intel_lt_phy_p2p_write(struct intel_encoder *encoder,
|
|
u8 lane_mask, u16 addr, u8 data,
|
|
i915_reg_t mac_reg_addr,
|
|
u8 expected_mac_val)
|
|
{
|
|
int lane;
|
|
|
|
for_each_lt_phy_lane_in_mask(lane_mask, lane)
|
|
__intel_lt_phy_p2p_write(encoder, lane, addr, data, mac_reg_addr, expected_mac_val);
|
|
}
|
|
|
|
static void
|
|
intel_lt_phy_setup_powerdown(struct intel_encoder *encoder, u8 lane_count)
|
|
{
|
|
/*
|
|
* The new PORT_BUF_CTL6 stuff for dc5 entry and exit needs to be handled
|
|
* by dmc firmware not explicitly mentioned in Bspec. This leaves this
|
|
* function as a wrapper only but keeping it expecting future changes.
|
|
*/
|
|
intel_cx0_setup_powerdown(encoder);
|
|
}
|
|
|
|
static void
|
|
intel_lt_phy_powerdown_change_sequence(struct intel_encoder *encoder,
|
|
u8 lane_mask, u8 state)
|
|
{
|
|
intel_cx0_powerdown_change_sequence(encoder, lane_mask, state);
|
|
}
|
|
|
|
static void
|
|
intel_lt_phy_lane_reset(struct intel_encoder *encoder,
|
|
u8 lane_count)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
enum port port = encoder->port;
|
|
enum phy phy = intel_encoder_to_phy(encoder);
|
|
u8 owned_lane_mask = intel_lt_phy_get_owned_lane_mask(encoder);
|
|
u32 lane_pipe_reset = owned_lane_mask == INTEL_LT_PHY_BOTH_LANES
|
|
? XELPDP_LANE_PIPE_RESET(0) | XELPDP_LANE_PIPE_RESET(1)
|
|
: XELPDP_LANE_PIPE_RESET(0);
|
|
u32 lane_phy_current_status = owned_lane_mask == INTEL_LT_PHY_BOTH_LANES
|
|
? (XELPDP_LANE_PHY_CURRENT_STATUS(0) |
|
|
XELPDP_LANE_PHY_CURRENT_STATUS(1))
|
|
: XELPDP_LANE_PHY_CURRENT_STATUS(0);
|
|
u32 lane_phy_pulse_status = owned_lane_mask == INTEL_LT_PHY_BOTH_LANES
|
|
? (XE3PLPDP_LANE_PHY_PULSE_STATUS(0) |
|
|
XE3PLPDP_LANE_PHY_PULSE_STATUS(1))
|
|
: XE3PLPDP_LANE_PHY_PULSE_STATUS(0);
|
|
|
|
intel_de_rmw(display, XE3PLPD_PORT_BUF_CTL5(port),
|
|
XE3PLPD_MACCLK_RATE_MASK, XE3PLPD_MACCLK_RATE_DEF);
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL1(display, port),
|
|
XE3PLPDP_PHY_MODE_MASK, XE3PLPDP_PHY_MODE_DP);
|
|
|
|
intel_lt_phy_setup_powerdown(encoder, lane_count);
|
|
intel_lt_phy_powerdown_change_sequence(encoder, owned_lane_mask,
|
|
XELPDP_P2_STATE_RESET);
|
|
|
|
intel_de_rmw(display, XE3PLPD_PORT_BUF_CTL5(port),
|
|
XE3PLPD_MACCLK_RESET_0, 0);
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_REQUEST(0),
|
|
XELPDP_LANE_PCLK_PLL_REQUEST(0));
|
|
|
|
if (intel_de_wait_for_set_ms(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_ACK(0),
|
|
XE3PLPD_MACCLK_TURNON_LATENCY_MS))
|
|
drm_warn(display->drm, "PHY %c PLL MacCLK assertion ack not done\n",
|
|
phy_name(phy));
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_FORWARD_CLOCK_UNGATE,
|
|
XELPDP_FORWARD_CLOCK_UNGATE);
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_pipe_reset | lane_phy_pulse_status, 0);
|
|
|
|
if (intel_de_wait_for_clear_ms(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_current_status,
|
|
XE3PLPD_RESET_END_LATENCY_MS))
|
|
drm_warn(display->drm, "PHY %c failed to bring out of lane reset\n",
|
|
phy_name(phy));
|
|
|
|
if (intel_de_wait_for_set_ms(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_pulse_status,
|
|
XE3PLPD_RATE_CALIB_DONE_LATENCY_MS))
|
|
drm_warn(display->drm, "PHY %c PLL rate not changed\n",
|
|
phy_name(phy));
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL2(display, port), lane_phy_pulse_status, 0);
|
|
}
|
|
|
|
static void
|
|
intel_lt_phy_program_port_clock_ctl(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state,
|
|
bool lane_reversal)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
u32 val = 0;
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL1(display, encoder->port),
|
|
XELPDP_PORT_REVERSAL,
|
|
lane_reversal ? XELPDP_PORT_REVERSAL : 0);
|
|
|
|
val |= XELPDP_FORWARD_CLOCK_UNGATE;
|
|
|
|
/*
|
|
* We actually mean MACCLK here and not MAXPCLK when using LT Phy
|
|
* but since the register bits still remain the same we use
|
|
* the same definition
|
|
*/
|
|
if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) &&
|
|
intel_hdmi_is_frl(crtc_state->port_clock))
|
|
val |= XELPDP_DDI_CLOCK_SELECT_PREP(display, XELPDP_DDI_CLOCK_SELECT_DIV18CLK);
|
|
else
|
|
val |= XELPDP_DDI_CLOCK_SELECT_PREP(display, XELPDP_DDI_CLOCK_SELECT_MAXPCLK);
|
|
|
|
/* DP2.0 10G and 20G rates enable MPLLA*/
|
|
if (crtc_state->port_clock == 1000000 || crtc_state->port_clock == 2000000)
|
|
val |= XELPDP_SSC_ENABLE_PLLA;
|
|
else
|
|
val |= crtc_state->dpll_hw_state.ltpll.ssc_enabled ? XELPDP_SSC_ENABLE_PLLB : 0;
|
|
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, encoder->port),
|
|
XELPDP_LANE1_PHY_CLOCK_SELECT | XELPDP_FORWARD_CLOCK_UNGATE |
|
|
XELPDP_DDI_CLOCK_SELECT_MASK(display) | XELPDP_SSC_ENABLE_PLLA |
|
|
XELPDP_SSC_ENABLE_PLLB, val);
|
|
}
|
|
|
|
static u32 intel_lt_phy_get_dp_clock(u8 rate)
|
|
{
|
|
switch (rate) {
|
|
case 0:
|
|
return 162000;
|
|
case 1:
|
|
return 270000;
|
|
case 2:
|
|
return 540000;
|
|
case 3:
|
|
return 810000;
|
|
case 4:
|
|
return 216000;
|
|
case 5:
|
|
return 243000;
|
|
case 6:
|
|
return 324000;
|
|
case 7:
|
|
return 432000;
|
|
case 8:
|
|
return 1000000;
|
|
case 9:
|
|
return 1350000;
|
|
case 10:
|
|
return 2000000;
|
|
case 11:
|
|
return 675000;
|
|
default:
|
|
MISSING_CASE(rate);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
intel_lt_phy_config_changed(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state)
|
|
{
|
|
u8 val, rate;
|
|
u32 clock;
|
|
|
|
val = intel_lt_phy_read(encoder, INTEL_LT_PHY_LANE0,
|
|
LT_PHY_VDR_0_CONFIG);
|
|
rate = REG_FIELD_GET8(LT_PHY_VDR_RATE_ENCODING_MASK, val);
|
|
|
|
/*
|
|
* The only time we do not reconfigure the PLL is when we are
|
|
* using 1.62 Gbps clock since PHY PLL defaults to that
|
|
* otherwise we always need to reconfigure it.
|
|
*/
|
|
if (intel_crtc_has_dp_encoder(crtc_state)) {
|
|
clock = intel_lt_phy_get_dp_clock(rate);
|
|
if (crtc_state->port_clock == 1620000 && crtc_state->port_clock == clock)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct ref_tracker *intel_lt_phy_transaction_begin(struct intel_encoder *encoder)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
struct intel_dp *intel_dp = enc_to_intel_dp(encoder);
|
|
struct ref_tracker *wakeref;
|
|
|
|
intel_psr_pause(intel_dp);
|
|
wakeref = intel_display_power_get(display, POWER_DOMAIN_DC_OFF);
|
|
|
|
return wakeref;
|
|
}
|
|
|
|
static void intel_lt_phy_transaction_end(struct intel_encoder *encoder, struct ref_tracker *wakeref)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
struct intel_dp *intel_dp = enc_to_intel_dp(encoder);
|
|
|
|
intel_psr_resume(intel_dp);
|
|
intel_display_power_put(display, POWER_DOMAIN_DC_OFF, wakeref);
|
|
}
|
|
|
|
static const struct intel_lt_phy_pll_state * const *
|
|
intel_lt_phy_pll_tables_get(struct intel_crtc_state *crtc_state,
|
|
struct intel_encoder *encoder)
|
|
{
|
|
if (intel_crtc_has_dp_encoder(crtc_state)) {
|
|
if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP))
|
|
return xe3plpd_lt_edp_tables;
|
|
|
|
return xe3plpd_lt_dp_tables;
|
|
} else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) {
|
|
return xe3plpd_lt_hdmi_tables;
|
|
}
|
|
|
|
MISSING_CASE(encoder->type);
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
intel_lt_phy_pll_is_ssc_enabled(struct intel_crtc_state *crtc_state,
|
|
struct intel_encoder *encoder)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
|
|
if (intel_crtc_has_dp_encoder(crtc_state)) {
|
|
if (intel_panel_use_ssc(display)) {
|
|
struct intel_dp *intel_dp = enc_to_intel_dp(encoder);
|
|
|
|
return (intel_dp->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static u64 mul_q32_u32(u64 a_q32, u32 b)
|
|
{
|
|
u64 p0, p1, carry, result;
|
|
u64 x_hi = a_q32 >> 32;
|
|
u64 x_lo = a_q32 & 0xFFFFFFFFULL;
|
|
|
|
p0 = x_lo * (u64)b;
|
|
p1 = x_hi * (u64)b;
|
|
carry = p0 >> 32;
|
|
result = (p1 << 32) + (carry << 32) + (p0 & 0xFFFFFFFFULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
calculate_target_dco_and_loop_cnt(u32 frequency_khz, u64 *target_dco_mhz, u32 *loop_cnt)
|
|
{
|
|
u32 ppm_value = 1;
|
|
u32 dco_min_freq = DCO_MIN_FREQ_MHZ;
|
|
u32 dco_max_freq = 16200;
|
|
u32 dco_min_freq_low = 10000;
|
|
u32 dco_max_freq_low = 12000;
|
|
u64 val = 0;
|
|
u64 refclk_khz = REF_CLK_KHZ;
|
|
u64 m2div = 0;
|
|
u64 val_with_frac = 0;
|
|
u64 ppm = 0;
|
|
u64 temp0 = 0, temp1, scale;
|
|
int ppm_cnt, dco_count, y;
|
|
|
|
for (ppm_cnt = 0; ppm_cnt < 5; ppm_cnt++) {
|
|
ppm_value = ppm_cnt == 2 ? 2 : 1;
|
|
for (dco_count = 0; dco_count < 2; dco_count++) {
|
|
if (dco_count == 1) {
|
|
dco_min_freq = dco_min_freq_low;
|
|
dco_max_freq = dco_max_freq_low;
|
|
}
|
|
for (y = 2; y <= 255; y += 2) {
|
|
val = div64_u64((u64)y * frequency_khz, 200);
|
|
m2div = div64_u64(((u64)(val) << 32), refclk_khz);
|
|
m2div = mul_q32_u32(m2div, 500);
|
|
val_with_frac = mul_q32_u32(m2div, refclk_khz);
|
|
val_with_frac = div64_u64(val_with_frac, 500);
|
|
temp1 = Q32_TO_INT(val_with_frac);
|
|
temp0 = (temp1 > val) ? (temp1 - val) :
|
|
(val - temp1);
|
|
ppm = div64_u64(temp0, val);
|
|
if (temp1 >= dco_min_freq &&
|
|
temp1 <= dco_max_freq &&
|
|
ppm < ppm_value) {
|
|
/* Round to two places */
|
|
scale = (1ULL << 32) / 100;
|
|
temp0 = DIV_ROUND_UP_ULL(val_with_frac,
|
|
scale);
|
|
*target_dco_mhz = temp0 * scale;
|
|
*loop_cnt = y;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void set_phy_vdr_addresses(struct lt_phy_params *p, int pll_type)
|
|
{
|
|
p->pll_reg4.addr = PLL_REG_ADDR(PLL_REG4_ADDR, pll_type);
|
|
p->pll_reg3.addr = PLL_REG_ADDR(PLL_REG3_ADDR, pll_type);
|
|
p->pll_reg5.addr = PLL_REG_ADDR(PLL_REG5_ADDR, pll_type);
|
|
p->pll_reg57.addr = PLL_REG_ADDR(PLL_REG57_ADDR, pll_type);
|
|
p->lf.addr = PLL_REG_ADDR(PLL_LF_ADDR, pll_type);
|
|
p->tdc.addr = PLL_REG_ADDR(PLL_TDC_ADDR, pll_type);
|
|
p->ssc.addr = PLL_REG_ADDR(PLL_SSC_ADDR, pll_type);
|
|
p->bias2.addr = PLL_REG_ADDR(PLL_BIAS2_ADDR, pll_type);
|
|
p->bias_trim.addr = PLL_REG_ADDR(PLL_BIAS_TRIM_ADDR, pll_type);
|
|
p->dco_med.addr = PLL_REG_ADDR(PLL_DCO_MED_ADDR, pll_type);
|
|
p->dco_fine.addr = PLL_REG_ADDR(PLL_DCO_FINE_ADDR, pll_type);
|
|
p->ssc_inj.addr = PLL_REG_ADDR(PLL_SSC_INJ_ADDR, pll_type);
|
|
p->surv_bonus.addr = PLL_REG_ADDR(PLL_SURV_BONUS_ADDR, pll_type);
|
|
}
|
|
|
|
static void compute_ssc(struct lt_phy_params *p, u32 ana_cfg)
|
|
{
|
|
int ssc_stepsize = 0;
|
|
int ssc_steplen = 0;
|
|
int ssc_steplog = 0;
|
|
|
|
p->ssc.val = (1 << 31) | (ana_cfg << 24) | (ssc_steplog << 16) |
|
|
(ssc_stepsize << 8) | ssc_steplen;
|
|
}
|
|
|
|
static void compute_bias2(struct lt_phy_params *p)
|
|
{
|
|
u32 ssc_en_local = 0;
|
|
u64 dynctrl_ovrd_en = 0;
|
|
|
|
p->bias2.val = (dynctrl_ovrd_en << 31) | (ssc_en_local << 30) |
|
|
(1 << 23) | (1 << 24) | (32 << 16) | (1 << 8);
|
|
}
|
|
|
|
static void compute_tdc(struct lt_phy_params *p, u64 tdc_fine)
|
|
{
|
|
u32 settling_time = 15;
|
|
u32 bias_ovr_en = 1;
|
|
u32 coldstart = 1;
|
|
u32 true_lock = 2;
|
|
u32 early_lock = 1;
|
|
u32 lock_ovr_en = 1;
|
|
u32 lock_thr = tdc_fine ? 3 : 5;
|
|
u32 unlock_thr = tdc_fine ? 5 : 11;
|
|
|
|
p->tdc.val = (u32)((2 << 30) + (settling_time << 16) + (bias_ovr_en << 15) +
|
|
(lock_ovr_en << 14) + (coldstart << 12) + (true_lock << 10) +
|
|
(early_lock << 8) + (unlock_thr << 4) + lock_thr);
|
|
}
|
|
|
|
static void compute_dco_med(struct lt_phy_params *p)
|
|
{
|
|
u32 cselmed_en = 0;
|
|
u32 cselmed_dyn_adj = 0;
|
|
u32 cselmed_ratio = 39;
|
|
u32 cselmed_thr = 8;
|
|
|
|
p->dco_med.val = (cselmed_en << 31) + (cselmed_dyn_adj << 30) +
|
|
(cselmed_ratio << 24) + (cselmed_thr << 21);
|
|
}
|
|
|
|
static void compute_dco_fine(struct lt_phy_params *p, u32 dco_12g)
|
|
{
|
|
u32 dco_fine0_tune_2_0 = 0;
|
|
u32 dco_fine1_tune_2_0 = 0;
|
|
u32 dco_fine2_tune_2_0 = 0;
|
|
u32 dco_fine3_tune_2_0 = 0;
|
|
u32 dco_dith0_tune_2_0 = 0;
|
|
u32 dco_dith1_tune_2_0 = 0;
|
|
|
|
dco_fine0_tune_2_0 = dco_12g ? 4 : 3;
|
|
dco_fine1_tune_2_0 = 2;
|
|
dco_fine2_tune_2_0 = dco_12g ? 2 : 1;
|
|
dco_fine3_tune_2_0 = 5;
|
|
dco_dith0_tune_2_0 = dco_12g ? 4 : 3;
|
|
dco_dith1_tune_2_0 = 2;
|
|
|
|
p->dco_fine.val = (dco_dith1_tune_2_0 << 19) +
|
|
(dco_dith0_tune_2_0 << 16) +
|
|
(dco_fine3_tune_2_0 << 11) +
|
|
(dco_fine2_tune_2_0 << 8) +
|
|
(dco_fine1_tune_2_0 << 3) +
|
|
dco_fine0_tune_2_0;
|
|
}
|
|
|
|
int
|
|
intel_lt_phy_calculate_hdmi_state(struct intel_lt_phy_pll_state *lt_state,
|
|
u32 frequency_khz)
|
|
{
|
|
#define DATA_ASSIGN(i, pll_reg) \
|
|
do { \
|
|
lt_state->data[i][0] = (u8)((((pll_reg).val) & 0xFF000000) >> 24); \
|
|
lt_state->data[i][1] = (u8)((((pll_reg).val) & 0x00FF0000) >> 16); \
|
|
lt_state->data[i][2] = (u8)((((pll_reg).val) & 0x0000FF00) >> 8); \
|
|
lt_state->data[i][3] = (u8)((((pll_reg).val) & 0x000000FF)); \
|
|
} while (0)
|
|
#define ADDR_ASSIGN(i, pll_reg) \
|
|
do { \
|
|
lt_state->addr_msb[i] = ((pll_reg).addr >> 8) & 0xFF; \
|
|
lt_state->addr_lsb[i] = (pll_reg).addr & 0xFF; \
|
|
} while (0)
|
|
|
|
bool found = false;
|
|
struct lt_phy_params p;
|
|
u32 dco_fmin = DCO_MIN_FREQ_MHZ;
|
|
u64 refclk_khz = REF_CLK_KHZ;
|
|
u32 refclk_mhz_int = REF_CLK_KHZ / 1000;
|
|
u64 m2div = 0;
|
|
u64 target_dco_mhz = 0;
|
|
u64 tdc_fine, tdc_targetcnt;
|
|
u64 feedfwd_gain ,feedfwd_cal_en;
|
|
u64 tdc_res = 30;
|
|
u32 prop_coeff;
|
|
u32 int_coeff;
|
|
u32 ndiv = 1;
|
|
u32 m1div = 1, m2div_int, m2div_frac;
|
|
u32 frac_en;
|
|
u32 ana_cfg;
|
|
u32 loop_cnt = 0;
|
|
u32 gain_ctrl = 2;
|
|
u32 postdiv = 0;
|
|
u32 dco_12g = 0;
|
|
u32 pll_type = 0;
|
|
u32 d1 = 2, d3 = 5, d4 = 0, d5 = 0;
|
|
u32 d6 = 0, d6_new = 0;
|
|
u32 d7, d8 = 0;
|
|
u32 bonus_7_0 = 0;
|
|
u32 csel2fo = 11;
|
|
u32 csel2fo_ovrd_en = 1;
|
|
u64 temp0, temp1, temp2, temp3;
|
|
|
|
p.surv_bonus.val = (bonus_7_0 << 16);
|
|
p.pll_reg4.val = (refclk_mhz_int << 17) +
|
|
(ndiv << 9) + (1 << 4);
|
|
p.bias_trim.val = (csel2fo_ovrd_en << 30) + (csel2fo << 24);
|
|
p.ssc_inj.val = 0;
|
|
found = calculate_target_dco_and_loop_cnt(frequency_khz, &target_dco_mhz, &loop_cnt);
|
|
if (!found)
|
|
return -EINVAL;
|
|
|
|
m2div = div64_u64(target_dco_mhz, (refclk_khz * ndiv * m1div));
|
|
m2div = mul_q32_u32(m2div, 1000);
|
|
if (Q32_TO_INT(m2div) > 511)
|
|
return -EINVAL;
|
|
|
|
m2div_int = (u32)Q32_TO_INT(m2div);
|
|
m2div_frac = (u32)(Q32_TO_FRAC(m2div));
|
|
frac_en = (m2div_frac > 0) ? 1 : 0;
|
|
|
|
if (frac_en > 0)
|
|
tdc_res = 70;
|
|
else
|
|
tdc_res = 36;
|
|
tdc_fine = tdc_res > 50 ? 1 : 0;
|
|
temp0 = tdc_res * 40 * 11;
|
|
temp1 = div64_u64(((4 * TDC_RES_MULTIPLIER) + temp0) * 500, temp0 * refclk_khz);
|
|
temp2 = div64_u64(temp0 * refclk_khz, 1000);
|
|
temp3 = div64_u64(((8 * TDC_RES_MULTIPLIER) + temp2), temp2);
|
|
tdc_targetcnt = tdc_res < 50 ? (int)(temp1) : (int)(temp3);
|
|
tdc_targetcnt = (int)(tdc_targetcnt / 2);
|
|
temp0 = mul_q32_u32(target_dco_mhz, tdc_res);
|
|
temp0 >>= 32;
|
|
feedfwd_gain = (m2div_frac > 0) ? div64_u64(m1div * TDC_RES_MULTIPLIER, temp0) : 0;
|
|
feedfwd_cal_en = frac_en;
|
|
|
|
temp0 = (u32)Q32_TO_INT(target_dco_mhz);
|
|
prop_coeff = (temp0 >= dco_fmin) ? 3 : 4;
|
|
int_coeff = (temp0 >= dco_fmin) ? 7 : 8;
|
|
ana_cfg = (temp0 >= dco_fmin) ? 8 : 6;
|
|
dco_12g = (temp0 >= dco_fmin) ? 0 : 1;
|
|
|
|
if (temp0 > 12960)
|
|
d7 = 10;
|
|
else
|
|
d7 = 8;
|
|
|
|
d8 = loop_cnt / 2;
|
|
d4 = d8 * 2;
|
|
|
|
/* Compute pll_reg3,5,57 & lf */
|
|
p.pll_reg3.val = (u32)((d4 << 21) + (d3 << 18) + (d1 << 15) + (m2div_int << 5));
|
|
p.pll_reg5.val = m2div_frac;
|
|
postdiv = (d5 == 0) ? 9 : d5;
|
|
d6_new = (d6 == 0) ? 40 : d6;
|
|
p.pll_reg57.val = (d7 << 24) + (postdiv << 15) + (d8 << 7) + d6_new;
|
|
p.lf.val = (u32)((frac_en << 31) + (1 << 30) + (frac_en << 29) +
|
|
(feedfwd_cal_en << 28) + (tdc_fine << 27) +
|
|
(gain_ctrl << 24) + (feedfwd_gain << 16) +
|
|
(int_coeff << 12) + (prop_coeff << 8) + tdc_targetcnt);
|
|
|
|
compute_ssc(&p, ana_cfg);
|
|
compute_bias2(&p);
|
|
compute_tdc(&p, tdc_fine);
|
|
compute_dco_med(&p);
|
|
compute_dco_fine(&p, dco_12g);
|
|
|
|
pll_type = ((frequency_khz == 10000) || (frequency_khz == 20000) ||
|
|
(frequency_khz == 2500) || (dco_12g == 1)) ? 0 : 1;
|
|
set_phy_vdr_addresses(&p, pll_type);
|
|
|
|
lt_state->config[0] = 0x84;
|
|
lt_state->config[1] = 0x2d;
|
|
ADDR_ASSIGN(0, p.pll_reg4);
|
|
ADDR_ASSIGN(1, p.pll_reg3);
|
|
ADDR_ASSIGN(2, p.pll_reg5);
|
|
ADDR_ASSIGN(3, p.pll_reg57);
|
|
ADDR_ASSIGN(4, p.lf);
|
|
ADDR_ASSIGN(5, p.tdc);
|
|
ADDR_ASSIGN(6, p.ssc);
|
|
ADDR_ASSIGN(7, p.bias2);
|
|
ADDR_ASSIGN(8, p.bias_trim);
|
|
ADDR_ASSIGN(9, p.dco_med);
|
|
ADDR_ASSIGN(10, p.dco_fine);
|
|
ADDR_ASSIGN(11, p.ssc_inj);
|
|
ADDR_ASSIGN(12, p.surv_bonus);
|
|
DATA_ASSIGN(0, p.pll_reg4);
|
|
DATA_ASSIGN(1, p.pll_reg3);
|
|
DATA_ASSIGN(2, p.pll_reg5);
|
|
DATA_ASSIGN(3, p.pll_reg57);
|
|
DATA_ASSIGN(4, p.lf);
|
|
DATA_ASSIGN(5, p.tdc);
|
|
DATA_ASSIGN(6, p.ssc);
|
|
DATA_ASSIGN(7, p.bias2);
|
|
DATA_ASSIGN(8, p.bias_trim);
|
|
DATA_ASSIGN(9, p.dco_med);
|
|
DATA_ASSIGN(10, p.dco_fine);
|
|
DATA_ASSIGN(11, p.ssc_inj);
|
|
DATA_ASSIGN(12, p.surv_bonus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
intel_lt_phy_calc_hdmi_port_clock(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
#define REGVAL(i) ( \
|
|
(lt_state->data[i][3]) | \
|
|
(lt_state->data[i][2] << 8) | \
|
|
(lt_state->data[i][1] << 16) | \
|
|
(lt_state->data[i][0] << 24) \
|
|
)
|
|
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
const struct intel_lt_phy_pll_state *lt_state =
|
|
&crtc_state->dpll_hw_state.ltpll;
|
|
int clk = 0;
|
|
u32 d8, pll_reg_5, pll_reg_3, pll_reg_57, m2div_frac, m2div_int;
|
|
u64 temp0, temp1;
|
|
/*
|
|
* The algorithm uses '+' to combine bitfields when
|
|
* constructing PLL_reg3 and PLL_reg57:
|
|
* PLL_reg57 = (D7 << 24) + (postdiv << 15) + (D8 << 7) + D6_new;
|
|
* PLL_reg3 = (D4 << 21) + (D3 << 18) + (D1 << 15) + (m2div_int << 5);
|
|
*
|
|
* However, this is likely intended to be a bitwise OR operation,
|
|
* as each field occupies distinct, non-overlapping bits in the register.
|
|
*
|
|
* PLL_reg57 is composed of following fields packed into a 32-bit value:
|
|
* - D7: max value 10 -> fits in 4 bits -> placed at bits 24-27
|
|
* - postdiv: max value 9 -> fits in 4 bits -> placed at bits 15-18
|
|
* - D8: derived from loop_cnt / 2, max 127 -> fits in 7 bits
|
|
* (though 8 bits are given to it) -> placed at bits 7-14
|
|
* - D6_new: fits in lower 7 bits -> placed at bits 0-6
|
|
* PLL_reg57 = (D7 << 24) | (postdiv << 15) | (D8 << 7) | D6_new;
|
|
*
|
|
* Similarly, PLL_reg3 is packed as:
|
|
* - D4: max value 256 -> fits in 9 bits -> placed at bits 21-29
|
|
* - D3: max value 9 -> fits in 4 bits -> placed at bits 18-21
|
|
* - D1: max value 2 -> fits in 2 bits -> placed at bits 15-16
|
|
* - m2div_int: max value 511 -> fits in 9 bits (10 bits allocated)
|
|
* -> placed at bits 5-14
|
|
* PLL_reg3 = (D4 << 21) | (D3 << 18) | (D1 << 15) | (m2div_int << 5);
|
|
*/
|
|
pll_reg_5 = REGVAL(2);
|
|
pll_reg_3 = REGVAL(1);
|
|
pll_reg_57 = REGVAL(3);
|
|
m2div_frac = pll_reg_5;
|
|
|
|
/*
|
|
* From forward algorithm we know
|
|
* m2div = 2 * m2
|
|
* val = y * frequency * 5
|
|
* So now,
|
|
* frequency = (m2 * 2 * refclk_khz / (d8 * 10))
|
|
* frequency = (m2div * refclk_khz / (d8 * 10))
|
|
*/
|
|
d8 = (pll_reg_57 & REG_GENMASK(14, 7)) >> 7;
|
|
if (d8 == 0) {
|
|
drm_WARN_ON(display->drm,
|
|
"Invalid port clock using lowest HDMI portclock\n");
|
|
return xe3plpd_lt_hdmi_252.clock;
|
|
}
|
|
m2div_int = (pll_reg_3 & REG_GENMASK(14, 5)) >> 5;
|
|
temp0 = ((u64)m2div_frac * REF_CLK_KHZ) >> 32;
|
|
temp1 = (u64)m2div_int * REF_CLK_KHZ;
|
|
|
|
clk = div_u64((temp1 + temp0), d8 * 10);
|
|
|
|
return clk;
|
|
}
|
|
|
|
int
|
|
intel_lt_phy_calc_port_clock(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
int clk;
|
|
const struct intel_lt_phy_pll_state *lt_state =
|
|
&crtc_state->dpll_hw_state.ltpll;
|
|
u8 mode, rate;
|
|
|
|
mode = REG_FIELD_GET8(LT_PHY_VDR_MODE_ENCODING_MASK,
|
|
lt_state->config[0]);
|
|
/*
|
|
* For edp/dp read the clock value from the tables
|
|
* and return the clock as the algorithm used for
|
|
* calculating the port clock does not exactly matches
|
|
* with edp/dp clock.
|
|
*/
|
|
if (mode == MODE_DP) {
|
|
rate = REG_FIELD_GET8(LT_PHY_VDR_RATE_ENCODING_MASK,
|
|
lt_state->config[0]);
|
|
clk = intel_lt_phy_get_dp_clock(rate);
|
|
} else if (mode == MODE_HDMI_20) {
|
|
clk = intel_lt_phy_calc_hdmi_port_clock(crtc_state);
|
|
} else {
|
|
drm_WARN_ON(display->drm, "Unsupported LT PHY Mode!\n");
|
|
clk = xe3plpd_lt_hdmi_252.clock;
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
int
|
|
intel_lt_phy_pll_calc_state(struct intel_crtc_state *crtc_state,
|
|
struct intel_encoder *encoder)
|
|
{
|
|
const struct intel_lt_phy_pll_state * const *tables;
|
|
int i;
|
|
|
|
tables = intel_lt_phy_pll_tables_get(crtc_state, encoder);
|
|
if (!tables)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; tables[i]; i++) {
|
|
if (crtc_state->port_clock == tables[i]->clock) {
|
|
crtc_state->dpll_hw_state.ltpll = *tables[i];
|
|
if (intel_crtc_has_dp_encoder(crtc_state)) {
|
|
if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP))
|
|
crtc_state->dpll_hw_state.ltpll.config[2] = 1;
|
|
}
|
|
crtc_state->dpll_hw_state.ltpll.ssc_enabled =
|
|
intel_lt_phy_pll_is_ssc_enabled(crtc_state, encoder);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) {
|
|
return intel_lt_phy_calculate_hdmi_state(&crtc_state->dpll_hw_state.ltpll,
|
|
crtc_state->port_clock);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void
|
|
intel_lt_phy_program_pll(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state)
|
|
{
|
|
u8 owned_lane_mask = intel_lt_phy_get_owned_lane_mask(encoder);
|
|
int i, j, k;
|
|
|
|
intel_lt_phy_write(encoder, owned_lane_mask, LT_PHY_VDR_0_CONFIG,
|
|
crtc_state->dpll_hw_state.ltpll.config[0], MB_WRITE_COMMITTED);
|
|
intel_lt_phy_write(encoder, INTEL_LT_PHY_LANE0, LT_PHY_VDR_1_CONFIG,
|
|
crtc_state->dpll_hw_state.ltpll.config[1], MB_WRITE_COMMITTED);
|
|
intel_lt_phy_write(encoder, owned_lane_mask, LT_PHY_VDR_2_CONFIG,
|
|
crtc_state->dpll_hw_state.ltpll.config[2], MB_WRITE_COMMITTED);
|
|
|
|
for (i = 0; i <= 12; i++) {
|
|
intel_lt_phy_write(encoder, INTEL_LT_PHY_LANE0, LT_PHY_VDR_X_ADDR_MSB(i),
|
|
crtc_state->dpll_hw_state.ltpll.addr_msb[i],
|
|
MB_WRITE_COMMITTED);
|
|
intel_lt_phy_write(encoder, INTEL_LT_PHY_LANE0, LT_PHY_VDR_X_ADDR_LSB(i),
|
|
crtc_state->dpll_hw_state.ltpll.addr_lsb[i],
|
|
MB_WRITE_COMMITTED);
|
|
|
|
for (j = 3, k = 0; j >= 0; j--, k++)
|
|
intel_lt_phy_write(encoder, INTEL_LT_PHY_LANE0,
|
|
LT_PHY_VDR_X_DATAY(i, j),
|
|
crtc_state->dpll_hw_state.ltpll.data[i][k],
|
|
MB_WRITE_COMMITTED);
|
|
}
|
|
}
|
|
|
|
static void
|
|
intel_lt_phy_enable_disable_tx(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
|
|
bool lane_reversal = dig_port->lane_reversal;
|
|
u8 lane_count = crtc_state->lane_count;
|
|
bool is_dp_alt =
|
|
intel_tc_port_in_dp_alt_mode(dig_port);
|
|
enum intel_tc_pin_assignment tc_pin =
|
|
intel_tc_port_get_pin_assignment(dig_port);
|
|
u8 transmitter_mask = 0;
|
|
|
|
/*
|
|
* We have a two transmitters per lane and total of 2 PHY lanes so a total
|
|
* of 4 transmitters. We prepare a mask of the lanes that need to be activated
|
|
* and the transmitter which need to be activated for each lane. TX 0,1 correspond
|
|
* to LANE0 and TX 2, 3 correspond to LANE1.
|
|
*/
|
|
|
|
switch (lane_count) {
|
|
case 1:
|
|
transmitter_mask = lane_reversal ? REG_BIT8(3) : REG_BIT8(0);
|
|
if (is_dp_alt) {
|
|
if (tc_pin == INTEL_TC_PIN_ASSIGNMENT_D)
|
|
transmitter_mask = REG_BIT8(0);
|
|
else
|
|
transmitter_mask = REG_BIT8(1);
|
|
}
|
|
break;
|
|
case 2:
|
|
transmitter_mask = lane_reversal ? REG_GENMASK8(3, 2) : REG_GENMASK8(1, 0);
|
|
if (is_dp_alt)
|
|
transmitter_mask = REG_GENMASK8(1, 0);
|
|
break;
|
|
case 3:
|
|
transmitter_mask = lane_reversal ? REG_GENMASK8(3, 1) : REG_GENMASK8(2, 0);
|
|
if (is_dp_alt)
|
|
transmitter_mask = REG_GENMASK8(2, 0);
|
|
break;
|
|
case 4:
|
|
transmitter_mask = REG_GENMASK8(3, 0);
|
|
break;
|
|
default:
|
|
MISSING_CASE(lane_count);
|
|
transmitter_mask = REG_GENMASK8(3, 0);
|
|
break;
|
|
}
|
|
|
|
if (transmitter_mask & BIT(0)) {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE0, LT_PHY_TXY_CTL10(0),
|
|
LT_PHY_TX_LANE_ENABLE, LT_PHY_TXY_CTL10_MAC(0),
|
|
LT_PHY_TX_LANE_ENABLE);
|
|
} else {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE0, LT_PHY_TXY_CTL10(0),
|
|
0, LT_PHY_TXY_CTL10_MAC(0), 0);
|
|
}
|
|
|
|
if (transmitter_mask & BIT(1)) {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE0, LT_PHY_TXY_CTL10(1),
|
|
LT_PHY_TX_LANE_ENABLE, LT_PHY_TXY_CTL10_MAC(1),
|
|
LT_PHY_TX_LANE_ENABLE);
|
|
} else {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE0, LT_PHY_TXY_CTL10(1),
|
|
0, LT_PHY_TXY_CTL10_MAC(1), 0);
|
|
}
|
|
|
|
if (transmitter_mask & BIT(2)) {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE1, LT_PHY_TXY_CTL10(0),
|
|
LT_PHY_TX_LANE_ENABLE, LT_PHY_TXY_CTL10_MAC(0),
|
|
LT_PHY_TX_LANE_ENABLE);
|
|
} else {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE1, LT_PHY_TXY_CTL10(0),
|
|
0, LT_PHY_TXY_CTL10_MAC(0), 0);
|
|
}
|
|
|
|
if (transmitter_mask & BIT(3)) {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE1, LT_PHY_TXY_CTL10(1),
|
|
LT_PHY_TX_LANE_ENABLE, LT_PHY_TXY_CTL10_MAC(1),
|
|
LT_PHY_TX_LANE_ENABLE);
|
|
} else {
|
|
intel_lt_phy_p2p_write(encoder, INTEL_LT_PHY_LANE1, LT_PHY_TXY_CTL10(1),
|
|
0, LT_PHY_TXY_CTL10_MAC(1), 0);
|
|
}
|
|
}
|
|
|
|
void intel_lt_phy_pll_enable(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
|
|
bool lane_reversal = dig_port->lane_reversal;
|
|
u8 owned_lane_mask = intel_lt_phy_get_owned_lane_mask(encoder);
|
|
enum phy phy = intel_encoder_to_phy(encoder);
|
|
enum port port = encoder->port;
|
|
struct ref_tracker *wakeref = 0;
|
|
u32 lane_phy_pulse_status = owned_lane_mask == INTEL_LT_PHY_BOTH_LANES
|
|
? (XE3PLPDP_LANE_PHY_PULSE_STATUS(0) |
|
|
XE3PLPDP_LANE_PHY_PULSE_STATUS(1))
|
|
: XE3PLPDP_LANE_PHY_PULSE_STATUS(0);
|
|
u8 rate_update;
|
|
|
|
wakeref = intel_lt_phy_transaction_begin(encoder);
|
|
|
|
/* 1. Enable MacCLK at default 162 MHz frequency. */
|
|
intel_lt_phy_lane_reset(encoder, crtc_state->lane_count);
|
|
|
|
/* 2. Program PORT_CLOCK_CTL register to configure clock muxes, gating, and SSC. */
|
|
intel_lt_phy_program_port_clock_ctl(encoder, crtc_state, lane_reversal);
|
|
|
|
/* 3. Change owned PHY lanes power to Ready state. */
|
|
intel_lt_phy_powerdown_change_sequence(encoder, owned_lane_mask,
|
|
XELPDP_P2_STATE_READY);
|
|
|
|
/*
|
|
* 4. Read the PHY message bus VDR register PHY_VDR_0_Config check enabled PLL type,
|
|
* encoded rate and encoded mode.
|
|
*/
|
|
if (intel_lt_phy_config_changed(encoder, crtc_state)) {
|
|
/*
|
|
* 5. Program the PHY internal PLL registers over PHY message bus for the desired
|
|
* frequency and protocol type
|
|
*/
|
|
intel_lt_phy_program_pll(encoder, crtc_state);
|
|
|
|
/* 6. Use the P2P transaction flow */
|
|
/*
|
|
* 6.1. Set the PHY VDR register 0xCC4[Rate Control VDR Update] = 1 over PHY message
|
|
* bus for Owned PHY Lanes.
|
|
*/
|
|
/*
|
|
* 6.2. Poll for P2P Transaction Ready = "1" and read the MAC message bus VDR
|
|
* register at offset 0xC00 for Owned PHY Lanes*.
|
|
*/
|
|
/* 6.3. Clear P2P transaction Ready bit. */
|
|
intel_lt_phy_p2p_write(encoder, owned_lane_mask, LT_PHY_RATE_UPDATE,
|
|
LT_PHY_RATE_CONTROL_VDR_UPDATE, LT_PHY_MAC_VDR,
|
|
LT_PHY_PCLKIN_GATE);
|
|
|
|
/* 7. Program PORT_CLOCK_CTL[PCLK PLL Request LN0] = 0. */
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_REQUEST(0), 0);
|
|
|
|
/* 8. Poll for PORT_CLOCK_CTL[PCLK PLL Ack LN0]= 0. */
|
|
if (intel_de_wait_for_clear_us(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_ACK(0),
|
|
XE3PLPD_MACCLK_TURNOFF_LATENCY_US))
|
|
drm_warn(display->drm, "PHY %c PLL MacCLK ack deassertion timeout\n",
|
|
phy_name(phy));
|
|
|
|
/*
|
|
* 9. Follow the Display Voltage Frequency Switching - Sequence Before Frequency
|
|
* Change. We handle this step in bxt_set_cdclk().
|
|
*/
|
|
/* 10. Program DDI_CLK_VALFREQ to match intended DDI clock frequency. */
|
|
intel_de_write(display, DDI_CLK_VALFREQ(encoder->port),
|
|
crtc_state->port_clock);
|
|
|
|
/* 11. Program PORT_CLOCK_CTL[PCLK PLL Request LN0] = 1. */
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_REQUEST(0),
|
|
XELPDP_LANE_PCLK_PLL_REQUEST(0));
|
|
|
|
/* 12. Poll for PORT_CLOCK_CTL[PCLK PLL Ack LN0]= 1. */
|
|
if (intel_de_wait_for_set_ms(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_ACK(0),
|
|
XE3PLPD_MACCLK_TURNON_LATENCY_MS))
|
|
drm_warn(display->drm, "PHY %c PLL MacCLK ack assertion timeout\n",
|
|
phy_name(phy));
|
|
|
|
/*
|
|
* 13. Ungate the forward clock by setting
|
|
* PORT_CLOCK_CTL[Forward Clock Ungate] = 1.
|
|
*/
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_FORWARD_CLOCK_UNGATE,
|
|
XELPDP_FORWARD_CLOCK_UNGATE);
|
|
|
|
/* 14. SW clears PORT_BUF_CTL2 [PHY Pulse Status]. */
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_pulse_status,
|
|
lane_phy_pulse_status);
|
|
/*
|
|
* 15. Clear the PHY VDR register 0xCC4[Rate Control VDR Update] over
|
|
* PHY message bus for Owned PHY Lanes.
|
|
*/
|
|
rate_update = intel_lt_phy_read(encoder, INTEL_LT_PHY_LANE0, LT_PHY_RATE_UPDATE);
|
|
rate_update &= ~LT_PHY_RATE_CONTROL_VDR_UPDATE;
|
|
intel_lt_phy_write(encoder, owned_lane_mask, LT_PHY_RATE_UPDATE,
|
|
rate_update, MB_WRITE_COMMITTED);
|
|
|
|
/* 16. Poll for PORT_BUF_CTL2 register PHY Pulse Status = 1 for Owned PHY Lanes. */
|
|
if (intel_de_wait_for_set_ms(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_pulse_status,
|
|
XE3PLPD_RATE_CALIB_DONE_LATENCY_MS))
|
|
drm_warn(display->drm, "PHY %c PLL rate not changed\n",
|
|
phy_name(phy));
|
|
|
|
/* 17. SW clears PORT_BUF_CTL2 [PHY Pulse Status]. */
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_pulse_status,
|
|
lane_phy_pulse_status);
|
|
} else {
|
|
intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), crtc_state->port_clock);
|
|
}
|
|
|
|
/*
|
|
* 18. Follow the Display Voltage Frequency Switching - Sequence After Frequency Change.
|
|
* We handle this step in bxt_set_cdclk()
|
|
*/
|
|
/* 19. Move the PHY powerdown state to Active and program to enable/disable transmitters */
|
|
intel_lt_phy_powerdown_change_sequence(encoder, owned_lane_mask,
|
|
XELPDP_P0_STATE_ACTIVE);
|
|
|
|
intel_lt_phy_enable_disable_tx(encoder, crtc_state);
|
|
intel_lt_phy_transaction_end(encoder, wakeref);
|
|
}
|
|
|
|
void intel_lt_phy_pll_disable(struct intel_encoder *encoder)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
enum phy phy = intel_encoder_to_phy(encoder);
|
|
enum port port = encoder->port;
|
|
struct ref_tracker *wakeref;
|
|
u8 owned_lane_mask = intel_lt_phy_get_owned_lane_mask(encoder);
|
|
u32 lane_pipe_reset = owned_lane_mask == INTEL_LT_PHY_BOTH_LANES
|
|
? (XELPDP_LANE_PIPE_RESET(0) |
|
|
XELPDP_LANE_PIPE_RESET(1))
|
|
: XELPDP_LANE_PIPE_RESET(0);
|
|
u32 lane_phy_current_status = owned_lane_mask == INTEL_LT_PHY_BOTH_LANES
|
|
? (XELPDP_LANE_PHY_CURRENT_STATUS(0) |
|
|
XELPDP_LANE_PHY_CURRENT_STATUS(1))
|
|
: XELPDP_LANE_PHY_CURRENT_STATUS(0);
|
|
u32 lane_phy_pulse_status = owned_lane_mask == INTEL_LT_PHY_BOTH_LANES
|
|
? (XE3PLPDP_LANE_PHY_PULSE_STATUS(0) |
|
|
XE3PLPDP_LANE_PHY_PULSE_STATUS(1))
|
|
: XE3PLPDP_LANE_PHY_PULSE_STATUS(0);
|
|
|
|
wakeref = intel_lt_phy_transaction_begin(encoder);
|
|
|
|
/* 1. Clear PORT_BUF_CTL2 [PHY Pulse Status]. */
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_pulse_status,
|
|
lane_phy_pulse_status);
|
|
|
|
/* 2. Set PORT_BUF_CTL2<port> Lane<PHY Lanes Owned> Pipe Reset to 1. */
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL2(display, port), lane_pipe_reset,
|
|
lane_pipe_reset);
|
|
|
|
/* 3. Poll for PORT_BUF_CTL2<port> Lane<PHY Lanes Owned> PHY Current Status == 1. */
|
|
if (intel_de_wait_for_set_us(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_current_status,
|
|
XE3PLPD_RESET_START_LATENCY_US))
|
|
drm_warn(display->drm, "PHY %c failed to reset lane\n",
|
|
phy_name(phy));
|
|
|
|
/* 4. Clear for PHY pulse status on owned PHY lanes. */
|
|
intel_de_rmw(display, XELPDP_PORT_BUF_CTL2(display, port),
|
|
lane_phy_pulse_status,
|
|
lane_phy_pulse_status);
|
|
|
|
/*
|
|
* 5. Follow the Display Voltage Frequency Switching -
|
|
* Sequence Before Frequency Change. We handle this step in bxt_set_cdclk().
|
|
*/
|
|
/* 6. Program PORT_CLOCK_CTL[PCLK PLL Request LN0] = 0. */
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_REQUEST(0), 0);
|
|
|
|
/* 7. Program DDI_CLK_VALFREQ to 0. */
|
|
intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), 0);
|
|
|
|
/* 8. Poll for PORT_CLOCK_CTL[PCLK PLL Ack LN0]= 0. */
|
|
if (intel_de_wait_for_clear_us(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_LANE_PCLK_PLL_ACK(0),
|
|
XE3PLPD_MACCLK_TURNOFF_LATENCY_US))
|
|
drm_warn(display->drm, "PHY %c PLL MacCLK ack deassertion timeout\n",
|
|
phy_name(phy));
|
|
|
|
/*
|
|
* 9. Follow the Display Voltage Frequency Switching -
|
|
* Sequence After Frequency Change. We handle this step in bxt_set_cdclk().
|
|
*/
|
|
/* 10. Program PORT_CLOCK_CTL register to disable and gate clocks. */
|
|
intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port),
|
|
XELPDP_DDI_CLOCK_SELECT_MASK(display) | XELPDP_FORWARD_CLOCK_UNGATE, 0);
|
|
|
|
/* 11. Program PORT_BUF_CTL5[MacCLK Reset_0] = 1 to assert MacCLK reset. */
|
|
intel_de_rmw(display, XE3PLPD_PORT_BUF_CTL5(port),
|
|
XE3PLPD_MACCLK_RESET_0, XE3PLPD_MACCLK_RESET_0);
|
|
|
|
intel_lt_phy_transaction_end(encoder, wakeref);
|
|
}
|
|
|
|
void intel_lt_phy_set_signal_levels(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_display *display = to_intel_display(encoder);
|
|
const struct intel_ddi_buf_trans *trans;
|
|
u8 owned_lane_mask;
|
|
struct ref_tracker *wakeref;
|
|
int n_entries, ln;
|
|
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
|
|
|
|
if (intel_tc_port_in_tbt_alt_mode(dig_port))
|
|
return;
|
|
|
|
owned_lane_mask = intel_lt_phy_get_owned_lane_mask(encoder);
|
|
|
|
wakeref = intel_lt_phy_transaction_begin(encoder);
|
|
|
|
trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries);
|
|
if (drm_WARN_ON_ONCE(display->drm, !trans)) {
|
|
intel_lt_phy_transaction_end(encoder, wakeref);
|
|
return;
|
|
}
|
|
|
|
for (ln = 0; ln < crtc_state->lane_count; ln++) {
|
|
int level = intel_ddi_level(encoder, crtc_state, ln);
|
|
int lane = ln / 2;
|
|
int tx = ln % 2;
|
|
u8 lane_mask = lane == 0 ? INTEL_LT_PHY_LANE0 : INTEL_LT_PHY_LANE1;
|
|
|
|
if (!(lane_mask & owned_lane_mask))
|
|
continue;
|
|
|
|
intel_lt_phy_rmw(encoder, lane_mask, LT_PHY_TXY_CTL8(tx),
|
|
LT_PHY_TX_SWING_LEVEL_MASK | LT_PHY_TX_SWING_MASK,
|
|
LT_PHY_TX_SWING_LEVEL(trans->entries[level].lt.txswing_level) |
|
|
LT_PHY_TX_SWING(trans->entries[level].lt.txswing),
|
|
MB_WRITE_COMMITTED);
|
|
|
|
intel_lt_phy_rmw(encoder, lane_mask, LT_PHY_TXY_CTL2(tx),
|
|
LT_PHY_TX_CURSOR_MASK,
|
|
LT_PHY_TX_CURSOR(trans->entries[level].lt.pre_cursor),
|
|
MB_WRITE_COMMITTED);
|
|
intel_lt_phy_rmw(encoder, lane_mask, LT_PHY_TXY_CTL3(tx),
|
|
LT_PHY_TX_CURSOR_MASK,
|
|
LT_PHY_TX_CURSOR(trans->entries[level].lt.main_cursor),
|
|
MB_WRITE_COMMITTED);
|
|
intel_lt_phy_rmw(encoder, lane_mask, LT_PHY_TXY_CTL4(tx),
|
|
LT_PHY_TX_CURSOR_MASK,
|
|
LT_PHY_TX_CURSOR(trans->entries[level].lt.post_cursor),
|
|
MB_WRITE_COMMITTED);
|
|
}
|
|
|
|
intel_lt_phy_transaction_end(encoder, wakeref);
|
|
}
|
|
|
|
void intel_lt_phy_dump_hw_state(struct intel_display *display,
|
|
const struct intel_lt_phy_pll_state *hw_state)
|
|
{
|
|
int i, j;
|
|
|
|
drm_dbg_kms(display->drm, "lt_phy_pll_hw_state:\n");
|
|
for (i = 0; i < 3; i++) {
|
|
drm_dbg_kms(display->drm, "config[%d] = 0x%.4x,\n",
|
|
i, hw_state->config[i]);
|
|
}
|
|
|
|
for (i = 0; i <= 12; i++)
|
|
for (j = 3; j >= 0; j--)
|
|
drm_dbg_kms(display->drm, "vdr_data[%d][%d] = 0x%.4x,\n",
|
|
i, j, hw_state->data[i][j]);
|
|
}
|
|
|
|
bool
|
|
intel_lt_phy_pll_compare_hw_state(const struct intel_lt_phy_pll_state *a,
|
|
const struct intel_lt_phy_pll_state *b)
|
|
{
|
|
/*
|
|
* With LT PHY values other than VDR0_CONFIG and VDR2_CONFIG are
|
|
* unreliable. They cannot always be read back since internally
|
|
* after power gating values are not restored back to the
|
|
* shadow VDR registers. Thus we do not compare the whole state
|
|
* just the two VDR registers.
|
|
*/
|
|
if (a->config[0] == b->config[0] &&
|
|
a->config[2] == b->config[2])
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void intel_lt_phy_pll_readout_hw_state(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state,
|
|
struct intel_lt_phy_pll_state *pll_state)
|
|
{
|
|
u8 owned_lane_mask;
|
|
u8 lane;
|
|
struct ref_tracker *wakeref;
|
|
int i, j, k;
|
|
|
|
pll_state->tbt_mode = intel_tc_port_in_tbt_alt_mode(enc_to_dig_port(encoder));
|
|
if (pll_state->tbt_mode)
|
|
return;
|
|
|
|
owned_lane_mask = intel_lt_phy_get_owned_lane_mask(encoder);
|
|
lane = owned_lane_mask & INTEL_LT_PHY_LANE0 ? : INTEL_LT_PHY_LANE1;
|
|
wakeref = intel_lt_phy_transaction_begin(encoder);
|
|
|
|
pll_state->config[0] = intel_lt_phy_read(encoder, lane, LT_PHY_VDR_0_CONFIG);
|
|
pll_state->config[1] = intel_lt_phy_read(encoder, INTEL_LT_PHY_LANE0, LT_PHY_VDR_1_CONFIG);
|
|
pll_state->config[2] = intel_lt_phy_read(encoder, lane, LT_PHY_VDR_2_CONFIG);
|
|
|
|
for (i = 0; i <= 12; i++) {
|
|
for (j = 3, k = 0; j >= 0; j--, k++)
|
|
pll_state->data[i][k] =
|
|
intel_lt_phy_read(encoder, INTEL_LT_PHY_LANE0,
|
|
LT_PHY_VDR_X_DATAY(i, j));
|
|
}
|
|
|
|
pll_state->clock =
|
|
intel_lt_phy_calc_port_clock(encoder, crtc_state);
|
|
intel_lt_phy_transaction_end(encoder, wakeref);
|
|
}
|
|
|
|
void intel_lt_phy_pll_state_verify(struct intel_atomic_state *state,
|
|
struct intel_crtc *crtc)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_digital_port *dig_port;
|
|
const struct intel_crtc_state *new_crtc_state =
|
|
intel_atomic_get_new_crtc_state(state, crtc);
|
|
struct intel_encoder *encoder;
|
|
struct intel_lt_phy_pll_state pll_hw_state = {};
|
|
const struct intel_lt_phy_pll_state *pll_sw_state = &new_crtc_state->dpll_hw_state.ltpll;
|
|
|
|
if (DISPLAY_VER(display) < 35)
|
|
return;
|
|
|
|
if (!new_crtc_state->hw.active)
|
|
return;
|
|
|
|
/* intel_get_crtc_new_encoder() only works for modeset/fastset commits */
|
|
if (!intel_crtc_needs_modeset(new_crtc_state) &&
|
|
!intel_crtc_needs_fastset(new_crtc_state))
|
|
return;
|
|
|
|
encoder = intel_get_crtc_new_encoder(state, new_crtc_state);
|
|
intel_lt_phy_pll_readout_hw_state(encoder, new_crtc_state, &pll_hw_state);
|
|
|
|
dig_port = enc_to_dig_port(encoder);
|
|
if (intel_tc_port_in_tbt_alt_mode(dig_port))
|
|
return;
|
|
|
|
INTEL_DISPLAY_STATE_WARN(display, pll_hw_state.config[0] != pll_sw_state->config[0],
|
|
"[CRTC:%d:%s] mismatch in LT PHY PLL CONFIG 0: (expected 0x%04x, found 0x%04x)",
|
|
crtc->base.base.id, crtc->base.name,
|
|
pll_sw_state->config[0], pll_hw_state.config[0]);
|
|
INTEL_DISPLAY_STATE_WARN(display, pll_hw_state.config[2] != pll_sw_state->config[2],
|
|
"[CRTC:%d:%s] mismatch in LT PHY PLL CONFIG 2: (expected 0x%04x, found 0x%04x)",
|
|
crtc->base.base.id, crtc->base.name,
|
|
pll_sw_state->config[2], pll_hw_state.config[2]);
|
|
}
|
|
|
|
void intel_xe3plpd_pll_enable(struct intel_encoder *encoder,
|
|
const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
|
|
|
|
if (intel_tc_port_in_tbt_alt_mode(dig_port))
|
|
intel_mtl_tbt_pll_enable_clock(encoder, crtc_state->port_clock);
|
|
else
|
|
intel_lt_phy_pll_enable(encoder, crtc_state);
|
|
}
|
|
|
|
void intel_xe3plpd_pll_disable(struct intel_encoder *encoder)
|
|
{
|
|
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
|
|
|
|
if (intel_tc_port_in_tbt_alt_mode(dig_port))
|
|
intel_mtl_tbt_pll_disable_clock(encoder);
|
|
else
|
|
intel_lt_phy_pll_disable(encoder);
|
|
|
|
}
|