Recently we came across requirement to identify EHL and JSL platform to program them differently. Thus Split the basic platform definition, macros, and PCI IDs to differentiate between EHL and JSL platforms. Also, IS_ELKHARTLAKE is replaced with IS_JSL_EHL everywhere. Changes since V1 : - Rebased to avoid merge conflicts - Added missed check for jasperlake in intel_uc_fw.c Cc : Matt Roper <matthew.d.roper@intel.com> Cc : Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Tejas Upadhyay <tejaskumarx.surendrakumar.upadhyay@intel.com> Reviewed-by: Matt Roper <matthew.d.roper@intel.com> Signed-off-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20201013192948.63470-1-tejaskumarx.surendrakumar.upadhyay@intel.com
464 lines
12 KiB
C
464 lines
12 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2018 Intel Corporation
|
|
*/
|
|
|
|
#include "intel_combo_phy.h"
|
|
#include "intel_display_types.h"
|
|
|
|
#define for_each_combo_phy(__dev_priv, __phy) \
|
|
for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++) \
|
|
for_each_if(intel_phy_is_combo(__dev_priv, __phy))
|
|
|
|
#define for_each_combo_phy_reverse(__dev_priv, __phy) \
|
|
for ((__phy) = I915_MAX_PHYS; (__phy)-- > PHY_A;) \
|
|
for_each_if(intel_phy_is_combo(__dev_priv, __phy))
|
|
|
|
enum {
|
|
PROCMON_0_85V_DOT_0,
|
|
PROCMON_0_95V_DOT_0,
|
|
PROCMON_0_95V_DOT_1,
|
|
PROCMON_1_05V_DOT_0,
|
|
PROCMON_1_05V_DOT_1,
|
|
};
|
|
|
|
static const struct cnl_procmon {
|
|
u32 dw1, dw9, dw10;
|
|
} cnl_procmon_values[] = {
|
|
[PROCMON_0_85V_DOT_0] =
|
|
{ .dw1 = 0x00000000, .dw9 = 0x62AB67BB, .dw10 = 0x51914F96, },
|
|
[PROCMON_0_95V_DOT_0] =
|
|
{ .dw1 = 0x00000000, .dw9 = 0x86E172C7, .dw10 = 0x77CA5EAB, },
|
|
[PROCMON_0_95V_DOT_1] =
|
|
{ .dw1 = 0x00000000, .dw9 = 0x93F87FE1, .dw10 = 0x8AE871C5, },
|
|
[PROCMON_1_05V_DOT_0] =
|
|
{ .dw1 = 0x00000000, .dw9 = 0x98FA82DD, .dw10 = 0x89E46DC1, },
|
|
[PROCMON_1_05V_DOT_1] =
|
|
{ .dw1 = 0x00440000, .dw9 = 0x9A00AB25, .dw10 = 0x8AE38FF1, },
|
|
};
|
|
|
|
/*
|
|
* CNL has just one set of registers, while gen11 has a set for each combo PHY.
|
|
* The CNL registers are equivalent to the gen11 PHY A registers, that's why we
|
|
* call the ICL macros even though the function has CNL on its name.
|
|
*/
|
|
static const struct cnl_procmon *
|
|
cnl_get_procmon_ref_values(struct drm_i915_private *dev_priv, enum phy phy)
|
|
{
|
|
const struct cnl_procmon *procmon;
|
|
u32 val;
|
|
|
|
val = intel_de_read(dev_priv, ICL_PORT_COMP_DW3(phy));
|
|
switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) {
|
|
default:
|
|
MISSING_CASE(val);
|
|
fallthrough;
|
|
case VOLTAGE_INFO_0_85V | PROCESS_INFO_DOT_0:
|
|
procmon = &cnl_procmon_values[PROCMON_0_85V_DOT_0];
|
|
break;
|
|
case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_0:
|
|
procmon = &cnl_procmon_values[PROCMON_0_95V_DOT_0];
|
|
break;
|
|
case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_1:
|
|
procmon = &cnl_procmon_values[PROCMON_0_95V_DOT_1];
|
|
break;
|
|
case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_0:
|
|
procmon = &cnl_procmon_values[PROCMON_1_05V_DOT_0];
|
|
break;
|
|
case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_1:
|
|
procmon = &cnl_procmon_values[PROCMON_1_05V_DOT_1];
|
|
break;
|
|
}
|
|
|
|
return procmon;
|
|
}
|
|
|
|
static void cnl_set_procmon_ref_values(struct drm_i915_private *dev_priv,
|
|
enum phy phy)
|
|
{
|
|
const struct cnl_procmon *procmon;
|
|
u32 val;
|
|
|
|
procmon = cnl_get_procmon_ref_values(dev_priv, phy);
|
|
|
|
val = intel_de_read(dev_priv, ICL_PORT_COMP_DW1(phy));
|
|
val &= ~((0xff << 16) | 0xff);
|
|
val |= procmon->dw1;
|
|
intel_de_write(dev_priv, ICL_PORT_COMP_DW1(phy), val);
|
|
|
|
intel_de_write(dev_priv, ICL_PORT_COMP_DW9(phy), procmon->dw9);
|
|
intel_de_write(dev_priv, ICL_PORT_COMP_DW10(phy), procmon->dw10);
|
|
}
|
|
|
|
static bool check_phy_reg(struct drm_i915_private *dev_priv,
|
|
enum phy phy, i915_reg_t reg, u32 mask,
|
|
u32 expected_val)
|
|
{
|
|
u32 val = intel_de_read(dev_priv, reg);
|
|
|
|
if ((val & mask) != expected_val) {
|
|
drm_dbg(&dev_priv->drm,
|
|
"Combo PHY %c reg %08x state mismatch: "
|
|
"current %08x mask %08x expected %08x\n",
|
|
phy_name(phy),
|
|
reg.reg, val, mask, expected_val);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool cnl_verify_procmon_ref_values(struct drm_i915_private *dev_priv,
|
|
enum phy phy)
|
|
{
|
|
const struct cnl_procmon *procmon;
|
|
bool ret;
|
|
|
|
procmon = cnl_get_procmon_ref_values(dev_priv, phy);
|
|
|
|
ret = check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW1(phy),
|
|
(0xff << 16) | 0xff, procmon->dw1);
|
|
ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW9(phy),
|
|
-1U, procmon->dw9);
|
|
ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW10(phy),
|
|
-1U, procmon->dw10);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool cnl_combo_phy_enabled(struct drm_i915_private *dev_priv)
|
|
{
|
|
return !(intel_de_read(dev_priv, CHICKEN_MISC_2) & CNL_COMP_PWR_DOWN) &&
|
|
(intel_de_read(dev_priv, CNL_PORT_COMP_DW0) & COMP_INIT);
|
|
}
|
|
|
|
static bool cnl_combo_phy_verify_state(struct drm_i915_private *dev_priv)
|
|
{
|
|
enum phy phy = PHY_A;
|
|
bool ret;
|
|
|
|
if (!cnl_combo_phy_enabled(dev_priv))
|
|
return false;
|
|
|
|
ret = cnl_verify_procmon_ref_values(dev_priv, phy);
|
|
|
|
ret &= check_phy_reg(dev_priv, phy, CNL_PORT_CL1CM_DW5,
|
|
CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cnl_combo_phys_init(struct drm_i915_private *dev_priv)
|
|
{
|
|
u32 val;
|
|
|
|
val = intel_de_read(dev_priv, CHICKEN_MISC_2);
|
|
val &= ~CNL_COMP_PWR_DOWN;
|
|
intel_de_write(dev_priv, CHICKEN_MISC_2, val);
|
|
|
|
/* Dummy PORT_A to get the correct CNL register from the ICL macro */
|
|
cnl_set_procmon_ref_values(dev_priv, PHY_A);
|
|
|
|
val = intel_de_read(dev_priv, CNL_PORT_COMP_DW0);
|
|
val |= COMP_INIT;
|
|
intel_de_write(dev_priv, CNL_PORT_COMP_DW0, val);
|
|
|
|
val = intel_de_read(dev_priv, CNL_PORT_CL1CM_DW5);
|
|
val |= CL_POWER_DOWN_ENABLE;
|
|
intel_de_write(dev_priv, CNL_PORT_CL1CM_DW5, val);
|
|
}
|
|
|
|
static void cnl_combo_phys_uninit(struct drm_i915_private *dev_priv)
|
|
{
|
|
u32 val;
|
|
|
|
if (!cnl_combo_phy_verify_state(dev_priv))
|
|
drm_warn(&dev_priv->drm,
|
|
"Combo PHY HW state changed unexpectedly.\n");
|
|
|
|
val = intel_de_read(dev_priv, CHICKEN_MISC_2);
|
|
val |= CNL_COMP_PWR_DOWN;
|
|
intel_de_write(dev_priv, CHICKEN_MISC_2, val);
|
|
}
|
|
|
|
static bool has_phy_misc(struct drm_i915_private *i915, enum phy phy)
|
|
{
|
|
/*
|
|
* Some platforms only expect PHY_MISC to be programmed for PHY-A and
|
|
* PHY-B and may not even have instances of the register for the
|
|
* other combo PHY's.
|
|
*/
|
|
if (IS_JSL_EHL(i915) ||
|
|
IS_ROCKETLAKE(i915) ||
|
|
IS_DG1(i915))
|
|
return phy < PHY_C;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool icl_combo_phy_enabled(struct drm_i915_private *dev_priv,
|
|
enum phy phy)
|
|
{
|
|
/* The PHY C added by EHL has no PHY_MISC register */
|
|
if (!has_phy_misc(dev_priv, phy))
|
|
return intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)) & COMP_INIT;
|
|
else
|
|
return !(intel_de_read(dev_priv, ICL_PHY_MISC(phy)) &
|
|
ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) &&
|
|
(intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)) & COMP_INIT);
|
|
}
|
|
|
|
static bool ehl_vbt_ddi_d_present(struct drm_i915_private *i915)
|
|
{
|
|
bool ddi_a_present = intel_bios_is_port_present(i915, PORT_A);
|
|
bool ddi_d_present = intel_bios_is_port_present(i915, PORT_D);
|
|
bool dsi_present = intel_bios_is_dsi_present(i915, NULL);
|
|
|
|
/*
|
|
* VBT's 'dvo port' field for child devices references the DDI, not
|
|
* the PHY. So if combo PHY A is wired up to drive an external
|
|
* display, we should see a child device present on PORT_D and
|
|
* nothing on PORT_A and no DSI.
|
|
*/
|
|
if (ddi_d_present && !ddi_a_present && !dsi_present)
|
|
return true;
|
|
|
|
/*
|
|
* If we encounter a VBT that claims to have an external display on
|
|
* DDI-D _and_ an internal display on DDI-A/DSI leave an error message
|
|
* in the log and let the internal display win.
|
|
*/
|
|
if (ddi_d_present)
|
|
drm_err(&i915->drm,
|
|
"VBT claims to have both internal and external displays on PHY A. Configuring for internal.\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool phy_is_master(struct drm_i915_private *dev_priv, enum phy phy)
|
|
{
|
|
/*
|
|
* Certain PHYs are connected to compensation resistors and act
|
|
* as masters to other PHYs.
|
|
*
|
|
* ICL,TGL:
|
|
* A(master) -> B(slave), C(slave)
|
|
* RKL,DG1:
|
|
* A(master) -> B(slave)
|
|
* C(master) -> D(slave)
|
|
*
|
|
* We must set the IREFGEN bit for any PHY acting as a master
|
|
* to another PHY.
|
|
*/
|
|
if ((IS_DG1(dev_priv) || IS_ROCKETLAKE(dev_priv)) && phy == PHY_C)
|
|
return true;
|
|
|
|
return phy == PHY_A;
|
|
}
|
|
|
|
static bool icl_combo_phy_verify_state(struct drm_i915_private *dev_priv,
|
|
enum phy phy)
|
|
{
|
|
bool ret = true;
|
|
u32 expected_val = 0;
|
|
|
|
if (!icl_combo_phy_enabled(dev_priv, phy))
|
|
return false;
|
|
|
|
if (INTEL_GEN(dev_priv) >= 12) {
|
|
ret &= check_phy_reg(dev_priv, phy, ICL_PORT_TX_DW8_LN0(phy),
|
|
ICL_PORT_TX_DW8_ODCC_CLK_SEL |
|
|
ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK,
|
|
ICL_PORT_TX_DW8_ODCC_CLK_SEL |
|
|
ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2);
|
|
|
|
ret &= check_phy_reg(dev_priv, phy, ICL_PORT_PCS_DW1_LN0(phy),
|
|
DCC_MODE_SELECT_MASK,
|
|
DCC_MODE_SELECT_CONTINUOSLY);
|
|
}
|
|
|
|
ret &= cnl_verify_procmon_ref_values(dev_priv, phy);
|
|
|
|
if (phy_is_master(dev_priv, phy)) {
|
|
ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW8(phy),
|
|
IREFGEN, IREFGEN);
|
|
|
|
if (IS_JSL_EHL(dev_priv)) {
|
|
if (ehl_vbt_ddi_d_present(dev_priv))
|
|
expected_val = ICL_PHY_MISC_MUX_DDID;
|
|
|
|
ret &= check_phy_reg(dev_priv, phy, ICL_PHY_MISC(phy),
|
|
ICL_PHY_MISC_MUX_DDID,
|
|
expected_val);
|
|
}
|
|
}
|
|
|
|
ret &= check_phy_reg(dev_priv, phy, ICL_PORT_CL_DW5(phy),
|
|
CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void intel_combo_phy_power_up_lanes(struct drm_i915_private *dev_priv,
|
|
enum phy phy, bool is_dsi,
|
|
int lane_count, bool lane_reversal)
|
|
{
|
|
u8 lane_mask;
|
|
u32 val;
|
|
|
|
if (is_dsi) {
|
|
drm_WARN_ON(&dev_priv->drm, lane_reversal);
|
|
|
|
switch (lane_count) {
|
|
case 1:
|
|
lane_mask = PWR_DOWN_LN_3_1_0;
|
|
break;
|
|
case 2:
|
|
lane_mask = PWR_DOWN_LN_3_1;
|
|
break;
|
|
case 3:
|
|
lane_mask = PWR_DOWN_LN_3;
|
|
break;
|
|
default:
|
|
MISSING_CASE(lane_count);
|
|
fallthrough;
|
|
case 4:
|
|
lane_mask = PWR_UP_ALL_LANES;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (lane_count) {
|
|
case 1:
|
|
lane_mask = lane_reversal ? PWR_DOWN_LN_2_1_0 :
|
|
PWR_DOWN_LN_3_2_1;
|
|
break;
|
|
case 2:
|
|
lane_mask = lane_reversal ? PWR_DOWN_LN_1_0 :
|
|
PWR_DOWN_LN_3_2;
|
|
break;
|
|
default:
|
|
MISSING_CASE(lane_count);
|
|
fallthrough;
|
|
case 4:
|
|
lane_mask = PWR_UP_ALL_LANES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
val = intel_de_read(dev_priv, ICL_PORT_CL_DW10(phy));
|
|
val &= ~PWR_DOWN_LN_MASK;
|
|
val |= lane_mask << PWR_DOWN_LN_SHIFT;
|
|
intel_de_write(dev_priv, ICL_PORT_CL_DW10(phy), val);
|
|
}
|
|
|
|
static void icl_combo_phys_init(struct drm_i915_private *dev_priv)
|
|
{
|
|
enum phy phy;
|
|
|
|
for_each_combo_phy(dev_priv, phy) {
|
|
u32 val;
|
|
|
|
if (icl_combo_phy_verify_state(dev_priv, phy)) {
|
|
drm_dbg(&dev_priv->drm,
|
|
"Combo PHY %c already enabled, won't reprogram it.\n",
|
|
phy_name(phy));
|
|
continue;
|
|
}
|
|
|
|
if (!has_phy_misc(dev_priv, phy))
|
|
goto skip_phy_misc;
|
|
|
|
/*
|
|
* EHL's combo PHY A can be hooked up to either an external
|
|
* display (via DDI-D) or an internal display (via DDI-A or
|
|
* the DSI DPHY). This is a motherboard design decision that
|
|
* can't be changed on the fly, so initialize the PHY's mux
|
|
* based on whether our VBT indicates the presence of any
|
|
* "internal" child devices.
|
|
*/
|
|
val = intel_de_read(dev_priv, ICL_PHY_MISC(phy));
|
|
if (IS_JSL_EHL(dev_priv) && phy == PHY_A) {
|
|
val &= ~ICL_PHY_MISC_MUX_DDID;
|
|
|
|
if (ehl_vbt_ddi_d_present(dev_priv))
|
|
val |= ICL_PHY_MISC_MUX_DDID;
|
|
}
|
|
|
|
val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
|
|
intel_de_write(dev_priv, ICL_PHY_MISC(phy), val);
|
|
|
|
skip_phy_misc:
|
|
if (INTEL_GEN(dev_priv) >= 12) {
|
|
val = intel_de_read(dev_priv, ICL_PORT_TX_DW8_LN0(phy));
|
|
val &= ~ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK;
|
|
val |= ICL_PORT_TX_DW8_ODCC_CLK_SEL;
|
|
val |= ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2;
|
|
intel_de_write(dev_priv, ICL_PORT_TX_DW8_GRP(phy), val);
|
|
|
|
val = intel_de_read(dev_priv, ICL_PORT_PCS_DW1_LN0(phy));
|
|
val &= ~DCC_MODE_SELECT_MASK;
|
|
val |= DCC_MODE_SELECT_CONTINUOSLY;
|
|
intel_de_write(dev_priv, ICL_PORT_PCS_DW1_GRP(phy), val);
|
|
}
|
|
|
|
cnl_set_procmon_ref_values(dev_priv, phy);
|
|
|
|
if (phy_is_master(dev_priv, phy)) {
|
|
val = intel_de_read(dev_priv, ICL_PORT_COMP_DW8(phy));
|
|
val |= IREFGEN;
|
|
intel_de_write(dev_priv, ICL_PORT_COMP_DW8(phy), val);
|
|
}
|
|
|
|
val = intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy));
|
|
val |= COMP_INIT;
|
|
intel_de_write(dev_priv, ICL_PORT_COMP_DW0(phy), val);
|
|
|
|
val = intel_de_read(dev_priv, ICL_PORT_CL_DW5(phy));
|
|
val |= CL_POWER_DOWN_ENABLE;
|
|
intel_de_write(dev_priv, ICL_PORT_CL_DW5(phy), val);
|
|
}
|
|
}
|
|
|
|
static void icl_combo_phys_uninit(struct drm_i915_private *dev_priv)
|
|
{
|
|
enum phy phy;
|
|
|
|
for_each_combo_phy_reverse(dev_priv, phy) {
|
|
u32 val;
|
|
|
|
if (phy == PHY_A &&
|
|
!icl_combo_phy_verify_state(dev_priv, phy))
|
|
drm_warn(&dev_priv->drm,
|
|
"Combo PHY %c HW state changed unexpectedly\n",
|
|
phy_name(phy));
|
|
|
|
if (!has_phy_misc(dev_priv, phy))
|
|
goto skip_phy_misc;
|
|
|
|
val = intel_de_read(dev_priv, ICL_PHY_MISC(phy));
|
|
val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
|
|
intel_de_write(dev_priv, ICL_PHY_MISC(phy), val);
|
|
|
|
skip_phy_misc:
|
|
val = intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy));
|
|
val &= ~COMP_INIT;
|
|
intel_de_write(dev_priv, ICL_PORT_COMP_DW0(phy), val);
|
|
}
|
|
}
|
|
|
|
void intel_combo_phy_init(struct drm_i915_private *i915)
|
|
{
|
|
if (INTEL_GEN(i915) >= 11)
|
|
icl_combo_phys_init(i915);
|
|
else if (IS_CANNONLAKE(i915))
|
|
cnl_combo_phys_init(i915);
|
|
}
|
|
|
|
void intel_combo_phy_uninit(struct drm_i915_private *i915)
|
|
{
|
|
if (INTEL_GEN(i915) >= 11)
|
|
icl_combo_phys_uninit(i915);
|
|
else if (IS_CANNONLAKE(i915))
|
|
cnl_combo_phys_uninit(i915);
|
|
}
|