f80c9a9a04
Although EHL added a third combo PHY, no PHY_MISC register was added for PHY C. The bspec indicates that there's no need to program the "DE to IO Comp Pwr Down" setting for this PHY that we usually need to set in PHY_MISC. v2: - Add IS_ELKHARTLAKE() guards since future platforms that have a PHY C are likely to reinstate the PHY_MISC register. (Jose) - Use goto's to skip PHY_MISC programming & minimize code deltas. (Jose) Bspec: 33148 Cc: José Roberto de Souza <jose.souza@intel.com> Signed-off-by: Matt Roper <matthew.d.roper@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20190626000352.31926-4-matthew.d.roper@intel.com Reviewed-by: José Roberto de Souza <jose.souza@intel.com>
393 lines
10 KiB
C
393 lines
10 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2018 Intel Corporation
|
|
*/
|
|
|
|
#include "intel_combo_phy.h"
|
|
#include "intel_drv.h"
|
|
|
|
#define for_each_combo_port(__dev_priv, __port) \
|
|
for ((__port) = PORT_A; (__port) < I915_MAX_PORTS; (__port)++) \
|
|
for_each_if(intel_port_is_combophy(__dev_priv, __port))
|
|
|
|
#define for_each_combo_port_reverse(__dev_priv, __port) \
|
|
for ((__port) = I915_MAX_PORTS; (__port)-- > PORT_A;) \
|
|
for_each_if(intel_port_is_combophy(__dev_priv, __port))
|
|
|
|
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 ICL has two sets: one for port A and
|
|
* the other for port B. The CNL registers are equivalent to the ICL port 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 port port)
|
|
{
|
|
const struct cnl_procmon *procmon;
|
|
u32 val;
|
|
|
|
val = I915_READ(ICL_PORT_COMP_DW3(port));
|
|
switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) {
|
|
default:
|
|
MISSING_CASE(val);
|
|
/* fall through */
|
|
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 port port)
|
|
{
|
|
const struct cnl_procmon *procmon;
|
|
u32 val;
|
|
|
|
procmon = cnl_get_procmon_ref_values(dev_priv, port);
|
|
|
|
val = I915_READ(ICL_PORT_COMP_DW1(port));
|
|
val &= ~((0xff << 16) | 0xff);
|
|
val |= procmon->dw1;
|
|
I915_WRITE(ICL_PORT_COMP_DW1(port), val);
|
|
|
|
I915_WRITE(ICL_PORT_COMP_DW9(port), procmon->dw9);
|
|
I915_WRITE(ICL_PORT_COMP_DW10(port), procmon->dw10);
|
|
}
|
|
|
|
static bool check_phy_reg(struct drm_i915_private *dev_priv,
|
|
enum port port, i915_reg_t reg, u32 mask,
|
|
u32 expected_val)
|
|
{
|
|
u32 val = I915_READ(reg);
|
|
|
|
if ((val & mask) != expected_val) {
|
|
DRM_DEBUG_DRIVER("Port %c combo PHY reg %08x state mismatch: "
|
|
"current %08x mask %08x expected %08x\n",
|
|
port_name(port),
|
|
reg.reg, val, mask, expected_val);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool cnl_verify_procmon_ref_values(struct drm_i915_private *dev_priv,
|
|
enum port port)
|
|
{
|
|
const struct cnl_procmon *procmon;
|
|
bool ret;
|
|
|
|
procmon = cnl_get_procmon_ref_values(dev_priv, port);
|
|
|
|
ret = check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW1(port),
|
|
(0xff << 16) | 0xff, procmon->dw1);
|
|
ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW9(port),
|
|
-1U, procmon->dw9);
|
|
ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW10(port),
|
|
-1U, procmon->dw10);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool cnl_combo_phy_enabled(struct drm_i915_private *dev_priv)
|
|
{
|
|
return !(I915_READ(CHICKEN_MISC_2) & CNL_COMP_PWR_DOWN) &&
|
|
(I915_READ(CNL_PORT_COMP_DW0) & COMP_INIT);
|
|
}
|
|
|
|
static bool cnl_combo_phy_verify_state(struct drm_i915_private *dev_priv)
|
|
{
|
|
enum port port = PORT_A;
|
|
bool ret;
|
|
|
|
if (!cnl_combo_phy_enabled(dev_priv))
|
|
return false;
|
|
|
|
ret = cnl_verify_procmon_ref_values(dev_priv, port);
|
|
|
|
ret &= check_phy_reg(dev_priv, port, 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 = I915_READ(CHICKEN_MISC_2);
|
|
val &= ~CNL_COMP_PWR_DOWN;
|
|
I915_WRITE(CHICKEN_MISC_2, val);
|
|
|
|
/* Dummy PORT_A to get the correct CNL register from the ICL macro */
|
|
cnl_set_procmon_ref_values(dev_priv, PORT_A);
|
|
|
|
val = I915_READ(CNL_PORT_COMP_DW0);
|
|
val |= COMP_INIT;
|
|
I915_WRITE(CNL_PORT_COMP_DW0, val);
|
|
|
|
val = I915_READ(CNL_PORT_CL1CM_DW5);
|
|
val |= CL_POWER_DOWN_ENABLE;
|
|
I915_WRITE(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("Combo PHY HW state changed unexpectedly.\n");
|
|
|
|
val = I915_READ(CHICKEN_MISC_2);
|
|
val |= CNL_COMP_PWR_DOWN;
|
|
I915_WRITE(CHICKEN_MISC_2, val);
|
|
}
|
|
|
|
static bool icl_combo_phy_enabled(struct drm_i915_private *dev_priv,
|
|
enum port port)
|
|
{
|
|
/* The PHY C added by EHL has no PHY_MISC register */
|
|
if (IS_ELKHARTLAKE(dev_priv) && port == PORT_C)
|
|
return I915_READ(ICL_PORT_COMP_DW0(port)) & COMP_INIT;
|
|
else
|
|
return !(I915_READ(ICL_PHY_MISC(port)) &
|
|
ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) &&
|
|
(I915_READ(ICL_PORT_COMP_DW0(port)) & COMP_INIT);
|
|
}
|
|
|
|
static bool icl_combo_phy_verify_state(struct drm_i915_private *dev_priv,
|
|
enum port port)
|
|
{
|
|
bool ret;
|
|
|
|
if (!icl_combo_phy_enabled(dev_priv, port))
|
|
return false;
|
|
|
|
ret = cnl_verify_procmon_ref_values(dev_priv, port);
|
|
|
|
if (port == PORT_A)
|
|
ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW8(port),
|
|
IREFGEN, IREFGEN);
|
|
|
|
ret &= check_phy_reg(dev_priv, port, ICL_PORT_CL_DW5(port),
|
|
CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void intel_combo_phy_power_up_lanes(struct drm_i915_private *dev_priv,
|
|
enum port port, bool is_dsi,
|
|
int lane_count, bool lane_reversal)
|
|
{
|
|
u8 lane_mask;
|
|
u32 val;
|
|
|
|
if (is_dsi) {
|
|
WARN_ON(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);
|
|
/* fall-through */
|
|
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);
|
|
/* fall-through */
|
|
case 4:
|
|
lane_mask = PWR_UP_ALL_LANES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
val = I915_READ(ICL_PORT_CL_DW10(port));
|
|
val &= ~PWR_DOWN_LN_MASK;
|
|
val |= lane_mask << PWR_DOWN_LN_SHIFT;
|
|
I915_WRITE(ICL_PORT_CL_DW10(port), val);
|
|
}
|
|
|
|
static u32 ehl_combo_phy_a_mux(struct drm_i915_private *i915, u32 val)
|
|
{
|
|
bool ddi_a_present = i915->vbt.ddi_port_info[PORT_A].child != NULL;
|
|
bool ddi_d_present = i915->vbt.ddi_port_info[PORT_D].child != NULL;
|
|
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 val | ICL_PHY_MISC_MUX_DDID;
|
|
|
|
/*
|
|
* 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_ERROR("VBT claims to have both internal and external displays on PHY A. Configuring for internal.\n");
|
|
|
|
return val & ~ICL_PHY_MISC_MUX_DDID;
|
|
}
|
|
|
|
static void icl_combo_phys_init(struct drm_i915_private *dev_priv)
|
|
{
|
|
enum port port;
|
|
|
|
for_each_combo_port(dev_priv, port) {
|
|
u32 val;
|
|
|
|
if (icl_combo_phy_verify_state(dev_priv, port)) {
|
|
DRM_DEBUG_DRIVER("Port %c combo PHY already enabled, won't reprogram it.\n",
|
|
port_name(port));
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Although EHL adds a combo PHY C, there's no PHY_MISC
|
|
* register for it and no need to program the
|
|
* DE_IO_COMP_PWR_DOWN setting on PHY C.
|
|
*/
|
|
if (IS_ELKHARTLAKE(dev_priv) && port == PORT_C)
|
|
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 = I915_READ(ICL_PHY_MISC(port));
|
|
if (IS_ELKHARTLAKE(dev_priv) && port == PORT_A)
|
|
val = ehl_combo_phy_a_mux(dev_priv, val);
|
|
val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
|
|
I915_WRITE(ICL_PHY_MISC(port), val);
|
|
|
|
skip_phy_misc:
|
|
cnl_set_procmon_ref_values(dev_priv, port);
|
|
|
|
if (port == PORT_A) {
|
|
val = I915_READ(ICL_PORT_COMP_DW8(port));
|
|
val |= IREFGEN;
|
|
I915_WRITE(ICL_PORT_COMP_DW8(port), val);
|
|
}
|
|
|
|
val = I915_READ(ICL_PORT_COMP_DW0(port));
|
|
val |= COMP_INIT;
|
|
I915_WRITE(ICL_PORT_COMP_DW0(port), val);
|
|
|
|
val = I915_READ(ICL_PORT_CL_DW5(port));
|
|
val |= CL_POWER_DOWN_ENABLE;
|
|
I915_WRITE(ICL_PORT_CL_DW5(port), val);
|
|
}
|
|
}
|
|
|
|
static void icl_combo_phys_uninit(struct drm_i915_private *dev_priv)
|
|
{
|
|
enum port port;
|
|
|
|
for_each_combo_port_reverse(dev_priv, port) {
|
|
u32 val;
|
|
|
|
if (port == PORT_A &&
|
|
!icl_combo_phy_verify_state(dev_priv, port))
|
|
DRM_WARN("Port %c combo PHY HW state changed unexpectedly\n",
|
|
port_name(port));
|
|
|
|
/*
|
|
* Although EHL adds a combo PHY C, there's no PHY_MISC
|
|
* register for it and no need to program the
|
|
* DE_IO_COMP_PWR_DOWN setting on PHY C.
|
|
*/
|
|
if (IS_ELKHARTLAKE(dev_priv) && port == PORT_C)
|
|
goto skip_phy_misc;
|
|
|
|
val = I915_READ(ICL_PHY_MISC(port));
|
|
val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
|
|
I915_WRITE(ICL_PHY_MISC(port), val);
|
|
|
|
skip_phy_misc:
|
|
val = I915_READ(ICL_PORT_COMP_DW0(port));
|
|
val &= ~COMP_INIT;
|
|
I915_WRITE(ICL_PORT_COMP_DW0(port), 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);
|
|
}
|