linux-stable/drivers/gpu/drm/i915/display/intel_combo_phy.c

335 lines
8.3 KiB
C
Raw Normal View History

// 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)
{
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 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;
}
val = I915_READ(ICL_PHY_MISC(port));
val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
I915_WRITE(ICL_PHY_MISC(port), val);
cnl_set_procmon_ref_values(dev_priv, port);
drm/i915/icl: Fix AUX-B HW not done issue w/o AUX-A Atm AUX-B transfers can fail with the following error if AUX-A is not enabled: [ 594.594108] [drm:intel_dp_aux_xfer [i915]] dp_aux_ch timeout status 0x7c2003ff [ 594.615854] [drm:intel_dp_aux_xfer [i915]] *ERROR* dp aux hw did not signal timeout! [ 594.632851] [drm:intel_dp_aux_xfer [i915]] *ERROR* dp aux hw did not signal timeout! [ 594.632915] [drm:intel_dp_aux_xfer [i915]] *ERROR* dp_aux_ch not done status 0xac2003ff [ 594.641786] ------------[ cut here ]------------ [ 594.641790] dp_aux_ch not started status 0xac2003ff [ 594.641874] WARNING: CPU: 4 PID: 1366 at drivers/gpu/drm/i915/intel_dp.c:1268 intel_dp_aux_xfer+0x232/0x890 [i915] Ville noticed this issue already earlier and managed to work around it by keeping AUX-A always powered whenever AUX-B was used. He also reported the issue to HW folks and they have now root caused the problem and updated BSpec with a fix (see internal BSpec/Index/21257, HSD/1607152412). I noticed the same error - even with the WA being applied - while doing AUX transfers with Chamelium being connected with a DP cable to the source but letting Chamelium imitate an unplug. This is probably some unstandard way on Chamelium's behalf of disconnecting itself from the AUX pins. For instance it could still pull on the AUX pins which would prevent the source from detecting AUX timeouts in the proper way, leading to the ERRORs or WARNs seen in the logs in the Reference: bug below. In case I disconnect the sink properly (the cable itself, not via the Chamelium unplug xmlrpc command) then the AUX timeout signaling works properly and so there won't be any ERRORs/WARNs emitted. Reference: https://bugs.freedesktop.org/show_bug.cgi?id=110718 Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Reported-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Imre Deak <imre.deak@intel.com> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20190524173532.6444-1-imre.deak@intel.com
2019-05-24 17:35:32 +00:00
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));
val = I915_READ(ICL_PHY_MISC(port));
val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
I915_WRITE(ICL_PHY_MISC(port), val);
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);
}