linux/drivers/gpu/drm/i915/display/intel_combo_phy.c
<<
>>
Prefs
   1// SPDX-License-Identifier: MIT
   2/*
   3 * Copyright © 2018 Intel Corporation
   4 */
   5
   6#include "intel_combo_phy.h"
   7#include "intel_display_types.h"
   8
   9#define for_each_combo_phy(__dev_priv, __phy) \
  10        for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++)       \
  11                for_each_if(intel_phy_is_combo(__dev_priv, __phy))
  12
  13#define for_each_combo_phy_reverse(__dev_priv, __phy) \
  14        for ((__phy) = I915_MAX_PHYS; (__phy)-- > PHY_A;) \
  15                for_each_if(intel_phy_is_combo(__dev_priv, __phy))
  16
  17enum {
  18        PROCMON_0_85V_DOT_0,
  19        PROCMON_0_95V_DOT_0,
  20        PROCMON_0_95V_DOT_1,
  21        PROCMON_1_05V_DOT_0,
  22        PROCMON_1_05V_DOT_1,
  23};
  24
  25static const struct cnl_procmon {
  26        u32 dw1, dw9, dw10;
  27} cnl_procmon_values[] = {
  28        [PROCMON_0_85V_DOT_0] =
  29                { .dw1 = 0x00000000, .dw9 = 0x62AB67BB, .dw10 = 0x51914F96, },
  30        [PROCMON_0_95V_DOT_0] =
  31                { .dw1 = 0x00000000, .dw9 = 0x86E172C7, .dw10 = 0x77CA5EAB, },
  32        [PROCMON_0_95V_DOT_1] =
  33                { .dw1 = 0x00000000, .dw9 = 0x93F87FE1, .dw10 = 0x8AE871C5, },
  34        [PROCMON_1_05V_DOT_0] =
  35                { .dw1 = 0x00000000, .dw9 = 0x98FA82DD, .dw10 = 0x89E46DC1, },
  36        [PROCMON_1_05V_DOT_1] =
  37                { .dw1 = 0x00440000, .dw9 = 0x9A00AB25, .dw10 = 0x8AE38FF1, },
  38};
  39
  40/*
  41 * CNL has just one set of registers, while gen11 has a set for each combo PHY.
  42 * The CNL registers are equivalent to the gen11 PHY A registers, that's why we
  43 * call the ICL macros even though the function has CNL on its name.
  44 */
  45static const struct cnl_procmon *
  46cnl_get_procmon_ref_values(struct drm_i915_private *dev_priv, enum phy phy)
  47{
  48        const struct cnl_procmon *procmon;
  49        u32 val;
  50
  51        val = I915_READ(ICL_PORT_COMP_DW3(phy));
  52        switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) {
  53        default:
  54                MISSING_CASE(val);
  55                /* fall through */
  56        case VOLTAGE_INFO_0_85V | PROCESS_INFO_DOT_0:
  57                procmon = &cnl_procmon_values[PROCMON_0_85V_DOT_0];
  58                break;
  59        case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_0:
  60                procmon = &cnl_procmon_values[PROCMON_0_95V_DOT_0];
  61                break;
  62        case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_1:
  63                procmon = &cnl_procmon_values[PROCMON_0_95V_DOT_1];
  64                break;
  65        case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_0:
  66                procmon = &cnl_procmon_values[PROCMON_1_05V_DOT_0];
  67                break;
  68        case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_1:
  69                procmon = &cnl_procmon_values[PROCMON_1_05V_DOT_1];
  70                break;
  71        }
  72
  73        return procmon;
  74}
  75
  76static void cnl_set_procmon_ref_values(struct drm_i915_private *dev_priv,
  77                                       enum phy phy)
  78{
  79        const struct cnl_procmon *procmon;
  80        u32 val;
  81
  82        procmon = cnl_get_procmon_ref_values(dev_priv, phy);
  83
  84        val = I915_READ(ICL_PORT_COMP_DW1(phy));
  85        val &= ~((0xff << 16) | 0xff);
  86        val |= procmon->dw1;
  87        I915_WRITE(ICL_PORT_COMP_DW1(phy), val);
  88
  89        I915_WRITE(ICL_PORT_COMP_DW9(phy), procmon->dw9);
  90        I915_WRITE(ICL_PORT_COMP_DW10(phy), procmon->dw10);
  91}
  92
  93static bool check_phy_reg(struct drm_i915_private *dev_priv,
  94                          enum phy phy, i915_reg_t reg, u32 mask,
  95                          u32 expected_val)
  96{
  97        u32 val = I915_READ(reg);
  98
  99        if ((val & mask) != expected_val) {
 100                DRM_DEBUG_DRIVER("Combo PHY %c reg %08x state mismatch: "
 101                                 "current %08x mask %08x expected %08x\n",
 102                                 phy_name(phy),
 103                                 reg.reg, val, mask, expected_val);
 104                return false;
 105        }
 106
 107        return true;
 108}
 109
 110static bool cnl_verify_procmon_ref_values(struct drm_i915_private *dev_priv,
 111                                          enum phy phy)
 112{
 113        const struct cnl_procmon *procmon;
 114        bool ret;
 115
 116        procmon = cnl_get_procmon_ref_values(dev_priv, phy);
 117
 118        ret = check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW1(phy),
 119                            (0xff << 16) | 0xff, procmon->dw1);
 120        ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW9(phy),
 121                             -1U, procmon->dw9);
 122        ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW10(phy),
 123                             -1U, procmon->dw10);
 124
 125        return ret;
 126}
 127
 128static bool cnl_combo_phy_enabled(struct drm_i915_private *dev_priv)
 129{
 130        return !(I915_READ(CHICKEN_MISC_2) & CNL_COMP_PWR_DOWN) &&
 131                (I915_READ(CNL_PORT_COMP_DW0) & COMP_INIT);
 132}
 133
 134static bool cnl_combo_phy_verify_state(struct drm_i915_private *dev_priv)
 135{
 136        enum phy phy = PHY_A;
 137        bool ret;
 138
 139        if (!cnl_combo_phy_enabled(dev_priv))
 140                return false;
 141
 142        ret = cnl_verify_procmon_ref_values(dev_priv, phy);
 143
 144        ret &= check_phy_reg(dev_priv, phy, CNL_PORT_CL1CM_DW5,
 145                             CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE);
 146
 147        return ret;
 148}
 149
 150static void cnl_combo_phys_init(struct drm_i915_private *dev_priv)
 151{
 152        u32 val;
 153
 154        val = I915_READ(CHICKEN_MISC_2);
 155        val &= ~CNL_COMP_PWR_DOWN;
 156        I915_WRITE(CHICKEN_MISC_2, val);
 157
 158        /* Dummy PORT_A to get the correct CNL register from the ICL macro */
 159        cnl_set_procmon_ref_values(dev_priv, PHY_A);
 160
 161        val = I915_READ(CNL_PORT_COMP_DW0);
 162        val |= COMP_INIT;
 163        I915_WRITE(CNL_PORT_COMP_DW0, val);
 164
 165        val = I915_READ(CNL_PORT_CL1CM_DW5);
 166        val |= CL_POWER_DOWN_ENABLE;
 167        I915_WRITE(CNL_PORT_CL1CM_DW5, val);
 168}
 169
 170static void cnl_combo_phys_uninit(struct drm_i915_private *dev_priv)
 171{
 172        u32 val;
 173
 174        if (!cnl_combo_phy_verify_state(dev_priv))
 175                DRM_WARN("Combo PHY HW state changed unexpectedly.\n");
 176
 177        val = I915_READ(CHICKEN_MISC_2);
 178        val |= CNL_COMP_PWR_DOWN;
 179        I915_WRITE(CHICKEN_MISC_2, val);
 180}
 181
 182static bool icl_combo_phy_enabled(struct drm_i915_private *dev_priv,
 183                                  enum phy phy)
 184{
 185        /* The PHY C added by EHL has no PHY_MISC register */
 186        if (IS_ELKHARTLAKE(dev_priv) && phy == PHY_C)
 187                return I915_READ(ICL_PORT_COMP_DW0(phy)) & COMP_INIT;
 188        else
 189                return !(I915_READ(ICL_PHY_MISC(phy)) &
 190                         ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) &&
 191                        (I915_READ(ICL_PORT_COMP_DW0(phy)) & COMP_INIT);
 192}
 193
 194static bool icl_combo_phy_verify_state(struct drm_i915_private *dev_priv,
 195                                       enum phy phy)
 196{
 197        bool ret;
 198
 199        if (!icl_combo_phy_enabled(dev_priv, phy))
 200                return false;
 201
 202        ret = cnl_verify_procmon_ref_values(dev_priv, phy);
 203
 204        if (phy == PHY_A)
 205                ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW8(phy),
 206                                     IREFGEN, IREFGEN);
 207
 208        ret &= check_phy_reg(dev_priv, phy, ICL_PORT_CL_DW5(phy),
 209                             CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE);
 210
 211        return ret;
 212}
 213
 214void intel_combo_phy_power_up_lanes(struct drm_i915_private *dev_priv,
 215                                    enum phy phy, bool is_dsi,
 216                                    int lane_count, bool lane_reversal)
 217{
 218        u8 lane_mask;
 219        u32 val;
 220
 221        if (is_dsi) {
 222                WARN_ON(lane_reversal);
 223
 224                switch (lane_count) {
 225                case 1:
 226                        lane_mask = PWR_DOWN_LN_3_1_0;
 227                        break;
 228                case 2:
 229                        lane_mask = PWR_DOWN_LN_3_1;
 230                        break;
 231                case 3:
 232                        lane_mask = PWR_DOWN_LN_3;
 233                        break;
 234                default:
 235                        MISSING_CASE(lane_count);
 236                        /* fall-through */
 237                case 4:
 238                        lane_mask = PWR_UP_ALL_LANES;
 239                        break;
 240                }
 241        } else {
 242                switch (lane_count) {
 243                case 1:
 244                        lane_mask = lane_reversal ? PWR_DOWN_LN_2_1_0 :
 245                                                    PWR_DOWN_LN_3_2_1;
 246                        break;
 247                case 2:
 248                        lane_mask = lane_reversal ? PWR_DOWN_LN_1_0 :
 249                                                    PWR_DOWN_LN_3_2;
 250                        break;
 251                default:
 252                        MISSING_CASE(lane_count);
 253                        /* fall-through */
 254                case 4:
 255                        lane_mask = PWR_UP_ALL_LANES;
 256                        break;
 257                }
 258        }
 259
 260        val = I915_READ(ICL_PORT_CL_DW10(phy));
 261        val &= ~PWR_DOWN_LN_MASK;
 262        val |= lane_mask << PWR_DOWN_LN_SHIFT;
 263        I915_WRITE(ICL_PORT_CL_DW10(phy), val);
 264}
 265
 266static u32 ehl_combo_phy_a_mux(struct drm_i915_private *i915, u32 val)
 267{
 268        bool ddi_a_present = i915->vbt.ddi_port_info[PORT_A].child != NULL;
 269        bool ddi_d_present = i915->vbt.ddi_port_info[PORT_D].child != NULL;
 270        bool dsi_present = intel_bios_is_dsi_present(i915, NULL);
 271
 272        /*
 273         * VBT's 'dvo port' field for child devices references the DDI, not
 274         * the PHY.  So if combo PHY A is wired up to drive an external
 275         * display, we should see a child device present on PORT_D and
 276         * nothing on PORT_A and no DSI.
 277         */
 278        if (ddi_d_present && !ddi_a_present && !dsi_present)
 279                return val | ICL_PHY_MISC_MUX_DDID;
 280
 281        /*
 282         * If we encounter a VBT that claims to have an external display on
 283         * DDI-D _and_ an internal display on DDI-A/DSI leave an error message
 284         * in the log and let the internal display win.
 285         */
 286        if (ddi_d_present)
 287                DRM_ERROR("VBT claims to have both internal and external displays on PHY A.  Configuring for internal.\n");
 288
 289        return val & ~ICL_PHY_MISC_MUX_DDID;
 290}
 291
 292static void icl_combo_phys_init(struct drm_i915_private *dev_priv)
 293{
 294        enum phy phy;
 295
 296        for_each_combo_phy(dev_priv, phy) {
 297                u32 val;
 298
 299                if (icl_combo_phy_verify_state(dev_priv, phy)) {
 300                        DRM_DEBUG_DRIVER("Combo PHY %c already enabled, won't reprogram it.\n",
 301                                         phy_name(phy));
 302                        continue;
 303                }
 304
 305                /*
 306                 * Although EHL adds a combo PHY C, there's no PHY_MISC
 307                 * register for it and no need to program the
 308                 * DE_IO_COMP_PWR_DOWN setting on PHY C.
 309                 */
 310                if (IS_ELKHARTLAKE(dev_priv) && phy == PHY_C)
 311                        goto skip_phy_misc;
 312
 313                /*
 314                 * EHL's combo PHY A can be hooked up to either an external
 315                 * display (via DDI-D) or an internal display (via DDI-A or
 316                 * the DSI DPHY).  This is a motherboard design decision that
 317                 * can't be changed on the fly, so initialize the PHY's mux
 318                 * based on whether our VBT indicates the presence of any
 319                 * "internal" child devices.
 320                 */
 321                val = I915_READ(ICL_PHY_MISC(phy));
 322                if (IS_ELKHARTLAKE(dev_priv) && phy == PHY_A)
 323                        val = ehl_combo_phy_a_mux(dev_priv, val);
 324                val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
 325                I915_WRITE(ICL_PHY_MISC(phy), val);
 326
 327skip_phy_misc:
 328                cnl_set_procmon_ref_values(dev_priv, phy);
 329
 330                if (phy == PHY_A) {
 331                        val = I915_READ(ICL_PORT_COMP_DW8(phy));
 332                        val |= IREFGEN;
 333                        I915_WRITE(ICL_PORT_COMP_DW8(phy), val);
 334                }
 335
 336                val = I915_READ(ICL_PORT_COMP_DW0(phy));
 337                val |= COMP_INIT;
 338                I915_WRITE(ICL_PORT_COMP_DW0(phy), val);
 339
 340                val = I915_READ(ICL_PORT_CL_DW5(phy));
 341                val |= CL_POWER_DOWN_ENABLE;
 342                I915_WRITE(ICL_PORT_CL_DW5(phy), val);
 343        }
 344}
 345
 346static void icl_combo_phys_uninit(struct drm_i915_private *dev_priv)
 347{
 348        enum phy phy;
 349
 350        for_each_combo_phy_reverse(dev_priv, phy) {
 351                u32 val;
 352
 353                if (phy == PHY_A &&
 354                    !icl_combo_phy_verify_state(dev_priv, phy))
 355                        DRM_WARN("Combo PHY %c HW state changed unexpectedly\n",
 356                                 phy_name(phy));
 357
 358                /*
 359                 * Although EHL adds a combo PHY C, there's no PHY_MISC
 360                 * register for it and no need to program the
 361                 * DE_IO_COMP_PWR_DOWN setting on PHY C.
 362                 */
 363                if (IS_ELKHARTLAKE(dev_priv) && phy == PHY_C)
 364                        goto skip_phy_misc;
 365
 366                val = I915_READ(ICL_PHY_MISC(phy));
 367                val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
 368                I915_WRITE(ICL_PHY_MISC(phy), val);
 369
 370skip_phy_misc:
 371                val = I915_READ(ICL_PORT_COMP_DW0(phy));
 372                val &= ~COMP_INIT;
 373                I915_WRITE(ICL_PORT_COMP_DW0(phy), val);
 374        }
 375}
 376
 377void intel_combo_phy_init(struct drm_i915_private *i915)
 378{
 379        if (INTEL_GEN(i915) >= 11)
 380                icl_combo_phys_init(i915);
 381        else if (IS_CANNONLAKE(i915))
 382                cnl_combo_phys_init(i915);
 383}
 384
 385void intel_combo_phy_uninit(struct drm_i915_private *i915)
 386{
 387        if (INTEL_GEN(i915) >= 11)
 388                icl_combo_phys_uninit(i915);
 389        else if (IS_CANNONLAKE(i915))
 390                cnl_combo_phys_uninit(i915);
 391}
 392