summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/i915/display/intel_combo_phy.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/i915/display/intel_combo_phy.c')
-rw-r--r--drivers/gpu/drm/i915/display/intel_combo_phy.c195
1 files changed, 126 insertions, 69 deletions
diff --git a/drivers/gpu/drm/i915/display/intel_combo_phy.c b/drivers/gpu/drm/i915/display/intel_combo_phy.c
index 841708da5a56..44bbc7e74fc3 100644
--- a/drivers/gpu/drm/i915/display/intel_combo_phy.c
+++ b/drivers/gpu/drm/i915/display/intel_combo_phy.c
@@ -4,15 +4,15 @@
*/
#include "intel_combo_phy.h"
-#include "intel_drv.h"
+#include "intel_display_types.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_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_port_reverse(__dev_priv, __port) \
- for ((__port) = I915_MAX_PORTS; (__port)-- > PORT_A;) \
- for_each_if(intel_port_is_combophy(__dev_priv, __port))
+#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,
@@ -38,18 +38,17 @@ static const struct cnl_procmon {
};
/*
- * 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.
+ * 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 port port)
+cnl_get_procmon_ref_values(struct drm_i915_private *dev_priv, enum phy phy)
{
const struct cnl_procmon *procmon;
u32 val;
- val = I915_READ(ICL_PORT_COMP_DW3(port));
+ val = I915_READ(ICL_PORT_COMP_DW3(phy));
switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) {
default:
MISSING_CASE(val);
@@ -75,32 +74,32 @@ cnl_get_procmon_ref_values(struct drm_i915_private *dev_priv, enum port port)
}
static void cnl_set_procmon_ref_values(struct drm_i915_private *dev_priv,
- enum port port)
+ enum phy phy)
{
const struct cnl_procmon *procmon;
u32 val;
- procmon = cnl_get_procmon_ref_values(dev_priv, port);
+ procmon = cnl_get_procmon_ref_values(dev_priv, phy);
- val = I915_READ(ICL_PORT_COMP_DW1(port));
+ val = I915_READ(ICL_PORT_COMP_DW1(phy));
val &= ~((0xff << 16) | 0xff);
val |= procmon->dw1;
- I915_WRITE(ICL_PORT_COMP_DW1(port), val);
+ I915_WRITE(ICL_PORT_COMP_DW1(phy), val);
- I915_WRITE(ICL_PORT_COMP_DW9(port), procmon->dw9);
- I915_WRITE(ICL_PORT_COMP_DW10(port), procmon->dw10);
+ I915_WRITE(ICL_PORT_COMP_DW9(phy), procmon->dw9);
+ I915_WRITE(ICL_PORT_COMP_DW10(phy), procmon->dw10);
}
static bool check_phy_reg(struct drm_i915_private *dev_priv,
- enum port port, i915_reg_t reg, u32 mask,
+ enum phy phy, 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: "
+ DRM_DEBUG_DRIVER("Combo PHY %c reg %08x state mismatch: "
"current %08x mask %08x expected %08x\n",
- port_name(port),
+ phy_name(phy),
reg.reg, val, mask, expected_val);
return false;
}
@@ -109,18 +108,18 @@ static bool check_phy_reg(struct drm_i915_private *dev_priv,
}
static bool cnl_verify_procmon_ref_values(struct drm_i915_private *dev_priv,
- enum port port)
+ enum phy phy)
{
const struct cnl_procmon *procmon;
bool ret;
- procmon = cnl_get_procmon_ref_values(dev_priv, port);
+ procmon = cnl_get_procmon_ref_values(dev_priv, phy);
- ret = check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW1(port),
+ ret = check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW1(phy),
(0xff << 16) | 0xff, procmon->dw1);
- ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW9(port),
+ ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW9(phy),
-1U, procmon->dw9);
- ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW10(port),
+ ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW10(phy),
-1U, procmon->dw10);
return ret;
@@ -134,15 +133,15 @@ static bool cnl_combo_phy_enabled(struct drm_i915_private *dev_priv)
static bool cnl_combo_phy_verify_state(struct drm_i915_private *dev_priv)
{
- enum port port = PORT_A;
+ enum phy phy = PHY_A;
bool ret;
if (!cnl_combo_phy_enabled(dev_priv))
return false;
- ret = cnl_verify_procmon_ref_values(dev_priv, port);
+ ret = cnl_verify_procmon_ref_values(dev_priv, phy);
- ret &= check_phy_reg(dev_priv, port, CNL_PORT_CL1CM_DW5,
+ ret &= check_phy_reg(dev_priv, phy, CNL_PORT_CL1CM_DW5,
CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE);
return ret;
@@ -157,7 +156,7 @@ static void cnl_combo_phys_init(struct drm_i915_private *dev_priv)
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);
+ cnl_set_procmon_ref_values(dev_priv, PHY_A);
val = I915_READ(CNL_PORT_COMP_DW0);
val |= COMP_INIT;
@@ -181,35 +180,39 @@ static void cnl_combo_phys_uninit(struct drm_i915_private *dev_priv)
}
static bool icl_combo_phy_enabled(struct drm_i915_private *dev_priv,
- enum port port)
+ enum phy phy)
{
- return !(I915_READ(ICL_PHY_MISC(port)) &
- ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) &&
- (I915_READ(ICL_PORT_COMP_DW0(port)) & COMP_INIT);
+ /* The PHY C added by EHL has no PHY_MISC register */
+ if (IS_ELKHARTLAKE(dev_priv) && phy == PHY_C)
+ return I915_READ(ICL_PORT_COMP_DW0(phy)) & COMP_INIT;
+ else
+ return !(I915_READ(ICL_PHY_MISC(phy)) &
+ ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) &&
+ (I915_READ(ICL_PORT_COMP_DW0(phy)) & COMP_INIT);
}
static bool icl_combo_phy_verify_state(struct drm_i915_private *dev_priv,
- enum port port)
+ enum phy phy)
{
bool ret;
- if (!icl_combo_phy_enabled(dev_priv, port))
+ if (!icl_combo_phy_enabled(dev_priv, phy))
return false;
- ret = cnl_verify_procmon_ref_values(dev_priv, port);
+ ret = cnl_verify_procmon_ref_values(dev_priv, phy);
- if (port == PORT_A)
- ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW8(port),
+ if (phy == PHY_A)
+ ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW8(phy),
IREFGEN, IREFGEN);
- ret &= check_phy_reg(dev_priv, port, ICL_PORT_CL_DW5(port),
+ 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 port port, bool is_dsi,
+ enum phy phy, bool is_dsi,
int lane_count, bool lane_reversal)
{
u8 lane_mask;
@@ -254,66 +257,120 @@ void intel_combo_phy_power_up_lanes(struct drm_i915_private *dev_priv,
}
}
- val = I915_READ(ICL_PORT_CL_DW10(port));
+ val = I915_READ(ICL_PORT_CL_DW10(phy));
val &= ~PWR_DOWN_LN_MASK;
val |= lane_mask << PWR_DOWN_LN_SHIFT;
- I915_WRITE(ICL_PORT_CL_DW10(port), val);
+ I915_WRITE(ICL_PORT_CL_DW10(phy), 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;
+ enum phy phy;
- for_each_combo_port(dev_priv, port) {
+ for_each_combo_phy(dev_priv, phy) {
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));
+ if (icl_combo_phy_verify_state(dev_priv, phy)) {
+ DRM_DEBUG_DRIVER("Combo PHY %c already enabled, won't reprogram it.\n",
+ phy_name(phy));
continue;
}
- val = I915_READ(ICL_PHY_MISC(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) && phy == PHY_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(phy));
+ if (IS_ELKHARTLAKE(dev_priv) && phy == PHY_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);
+ I915_WRITE(ICL_PHY_MISC(phy), val);
- cnl_set_procmon_ref_values(dev_priv, port);
+skip_phy_misc:
+ cnl_set_procmon_ref_values(dev_priv, phy);
- if (port == PORT_A) {
- val = I915_READ(ICL_PORT_COMP_DW8(port));
+ if (phy == PHY_A) {
+ val = I915_READ(ICL_PORT_COMP_DW8(phy));
val |= IREFGEN;
- I915_WRITE(ICL_PORT_COMP_DW8(port), val);
+ I915_WRITE(ICL_PORT_COMP_DW8(phy), val);
}
- val = I915_READ(ICL_PORT_COMP_DW0(port));
+ val = I915_READ(ICL_PORT_COMP_DW0(phy));
val |= COMP_INIT;
- I915_WRITE(ICL_PORT_COMP_DW0(port), val);
+ I915_WRITE(ICL_PORT_COMP_DW0(phy), val);
- val = I915_READ(ICL_PORT_CL_DW5(port));
+ val = I915_READ(ICL_PORT_CL_DW5(phy));
val |= CL_POWER_DOWN_ENABLE;
- I915_WRITE(ICL_PORT_CL_DW5(port), val);
+ I915_WRITE(ICL_PORT_CL_DW5(phy), val);
}
}
static void icl_combo_phys_uninit(struct drm_i915_private *dev_priv)
{
- enum port port;
+ enum phy phy;
- for_each_combo_port_reverse(dev_priv, port) {
+ for_each_combo_phy_reverse(dev_priv, phy) {
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));
+ if (phy == PHY_A &&
+ !icl_combo_phy_verify_state(dev_priv, phy))
+ DRM_WARN("Combo PHY %c HW state changed unexpectedly\n",
+ phy_name(phy));
+
+ /*
+ * 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) && phy == PHY_C)
+ goto skip_phy_misc;
- val = I915_READ(ICL_PHY_MISC(port));
+ val = I915_READ(ICL_PHY_MISC(phy));
val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
- I915_WRITE(ICL_PHY_MISC(port), val);
+ I915_WRITE(ICL_PHY_MISC(phy), val);
- val = I915_READ(ICL_PORT_COMP_DW0(port));
+skip_phy_misc:
+ val = I915_READ(ICL_PORT_COMP_DW0(phy));
val &= ~COMP_INIT;
- I915_WRITE(ICL_PORT_COMP_DW0(port), val);
+ I915_WRITE(ICL_PORT_COMP_DW0(phy), val);
}
}