linux/drivers/gpu/drm/i915/intel_lspcon.c
<<
>>
Prefs
   1/*
   2 * Copyright © 2016 Intel Corporation
   3 *
   4 * Permission is hereby granted, free of charge, to any person obtaining a
   5 * copy of this software and associated documentation files (the "Software"),
   6 * to deal in the Software without restriction, including without limitation
   7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   8 * and/or sell copies of the Software, and to permit persons to whom the
   9 * Software is furnished to do so, subject to the following conditions:
  10 *
  11 * The above copyright notice and this permission notice (including the next
  12 * paragraph) shall be included in all copies or substantial portions of the
  13 * Software.
  14 *
  15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
  18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  21 * DEALINGS IN THE SOFTWARE.
  22 *
  23 *
  24 */
  25#include <drm/drm_edid.h>
  26#include <drm/drm_atomic_helper.h>
  27#include <drm/drm_dp_dual_mode_helper.h>
  28#include "intel_drv.h"
  29
  30static struct intel_dp *lspcon_to_intel_dp(struct intel_lspcon *lspcon)
  31{
  32        struct intel_digital_port *dig_port =
  33                container_of(lspcon, struct intel_digital_port, lspcon);
  34
  35        return &dig_port->dp;
  36}
  37
  38static const char *lspcon_mode_name(enum drm_lspcon_mode mode)
  39{
  40        switch (mode) {
  41        case DRM_LSPCON_MODE_PCON:
  42                return "PCON";
  43        case DRM_LSPCON_MODE_LS:
  44                return "LS";
  45        case DRM_LSPCON_MODE_INVALID:
  46                return "INVALID";
  47        default:
  48                MISSING_CASE(mode);
  49                return "INVALID";
  50        }
  51}
  52
  53static enum drm_lspcon_mode lspcon_get_current_mode(struct intel_lspcon *lspcon)
  54{
  55        enum drm_lspcon_mode current_mode;
  56        struct i2c_adapter *adapter = &lspcon_to_intel_dp(lspcon)->aux.ddc;
  57
  58        if (drm_lspcon_get_mode(adapter, &current_mode)) {
  59                DRM_ERROR("Error reading LSPCON mode\n");
  60                return DRM_LSPCON_MODE_INVALID;
  61        }
  62        return current_mode;
  63}
  64
  65static enum drm_lspcon_mode lspcon_wait_mode(struct intel_lspcon *lspcon,
  66                                             enum drm_lspcon_mode mode)
  67{
  68        enum drm_lspcon_mode current_mode;
  69
  70        current_mode = lspcon_get_current_mode(lspcon);
  71        if (current_mode == mode || current_mode == DRM_LSPCON_MODE_INVALID)
  72                goto out;
  73
  74        DRM_DEBUG_KMS("Waiting for LSPCON mode %s to settle\n",
  75                      lspcon_mode_name(mode));
  76
  77        wait_for((current_mode = lspcon_get_current_mode(lspcon)) == mode ||
  78                 current_mode == DRM_LSPCON_MODE_INVALID, 100);
  79        if (current_mode != mode)
  80                DRM_DEBUG_KMS("LSPCON mode hasn't settled\n");
  81
  82out:
  83        DRM_DEBUG_KMS("Current LSPCON mode %s\n",
  84                      lspcon_mode_name(current_mode));
  85
  86        return current_mode;
  87}
  88
  89static int lspcon_change_mode(struct intel_lspcon *lspcon,
  90                              enum drm_lspcon_mode mode)
  91{
  92        int err;
  93        enum drm_lspcon_mode current_mode;
  94        struct i2c_adapter *adapter = &lspcon_to_intel_dp(lspcon)->aux.ddc;
  95
  96        err = drm_lspcon_get_mode(adapter, &current_mode);
  97        if (err) {
  98                DRM_ERROR("Error reading LSPCON mode\n");
  99                return err;
 100        }
 101
 102        if (current_mode == mode) {
 103                DRM_DEBUG_KMS("Current mode = desired LSPCON mode\n");
 104                return 0;
 105        }
 106
 107        err = drm_lspcon_set_mode(adapter, mode);
 108        if (err < 0) {
 109                DRM_ERROR("LSPCON mode change failed\n");
 110                return err;
 111        }
 112
 113        lspcon->mode = mode;
 114        DRM_DEBUG_KMS("LSPCON mode changed done\n");
 115        return 0;
 116}
 117
 118static bool lspcon_wake_native_aux_ch(struct intel_lspcon *lspcon)
 119{
 120        uint8_t rev;
 121
 122        if (drm_dp_dpcd_readb(&lspcon_to_intel_dp(lspcon)->aux, DP_DPCD_REV,
 123                              &rev) != 1) {
 124                DRM_DEBUG_KMS("Native AUX CH down\n");
 125                return false;
 126        }
 127
 128        DRM_DEBUG_KMS("Native AUX CH up, DPCD version: %d.%d\n",
 129                      rev >> 4, rev & 0xf);
 130
 131        return true;
 132}
 133
 134static bool lspcon_probe(struct intel_lspcon *lspcon)
 135{
 136        enum drm_dp_dual_mode_type adaptor_type;
 137        struct i2c_adapter *adapter = &lspcon_to_intel_dp(lspcon)->aux.ddc;
 138        enum drm_lspcon_mode expected_mode;
 139
 140        expected_mode = lspcon_wake_native_aux_ch(lspcon) ?
 141                        DRM_LSPCON_MODE_PCON : DRM_LSPCON_MODE_LS;
 142
 143        /* Lets probe the adaptor and check its type */
 144        adaptor_type = drm_dp_dual_mode_detect(adapter);
 145        if (adaptor_type != DRM_DP_DUAL_MODE_LSPCON) {
 146                DRM_DEBUG_KMS("No LSPCON detected, found %s\n",
 147                        drm_dp_get_dual_mode_type_name(adaptor_type));
 148                return false;
 149        }
 150
 151        /* Yay ... got a LSPCON device */
 152        DRM_DEBUG_KMS("LSPCON detected\n");
 153        lspcon->mode = lspcon_wait_mode(lspcon, expected_mode);
 154        lspcon->active = true;
 155        return true;
 156}
 157
 158static void lspcon_resume_in_pcon_wa(struct intel_lspcon *lspcon)
 159{
 160        struct intel_dp *intel_dp = lspcon_to_intel_dp(lspcon);
 161        struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp);
 162        struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev);
 163        unsigned long start = jiffies;
 164
 165        while (1) {
 166                if (intel_digital_port_connected(dev_priv, dig_port)) {
 167                        DRM_DEBUG_KMS("LSPCON recovering in PCON mode after %u ms\n",
 168                                      jiffies_to_msecs(jiffies - start));
 169                        return;
 170                }
 171
 172                if (time_after(jiffies, start + msecs_to_jiffies(1000)))
 173                        break;
 174
 175                usleep_range(10000, 15000);
 176        }
 177
 178        DRM_DEBUG_KMS("LSPCON DP descriptor mismatch after resume\n");
 179}
 180
 181void lspcon_resume(struct intel_lspcon *lspcon)
 182{
 183        enum drm_lspcon_mode expected_mode;
 184
 185        if (lspcon_wake_native_aux_ch(lspcon)) {
 186                expected_mode = DRM_LSPCON_MODE_PCON;
 187                lspcon_resume_in_pcon_wa(lspcon);
 188        } else {
 189                expected_mode = DRM_LSPCON_MODE_LS;
 190        }
 191
 192        if (lspcon_wait_mode(lspcon, expected_mode) == DRM_LSPCON_MODE_PCON)
 193                return;
 194
 195        if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON))
 196                DRM_ERROR("LSPCON resume failed\n");
 197        else
 198                DRM_DEBUG_KMS("LSPCON resume success\n");
 199}
 200
 201void lspcon_wait_pcon_mode(struct intel_lspcon *lspcon)
 202{
 203        lspcon_wait_mode(lspcon, DRM_LSPCON_MODE_PCON);
 204}
 205
 206bool lspcon_init(struct intel_digital_port *intel_dig_port)
 207{
 208        struct intel_dp *dp = &intel_dig_port->dp;
 209        struct intel_lspcon *lspcon = &intel_dig_port->lspcon;
 210        struct drm_device *dev = intel_dig_port->base.base.dev;
 211        struct drm_i915_private *dev_priv = to_i915(dev);
 212
 213        if (!HAS_LSPCON(dev_priv)) {
 214                DRM_ERROR("LSPCON is not supported on this platform\n");
 215                return false;
 216        }
 217
 218        lspcon->active = false;
 219        lspcon->mode = DRM_LSPCON_MODE_INVALID;
 220
 221        if (!lspcon_probe(lspcon)) {
 222                DRM_ERROR("Failed to probe lspcon\n");
 223                return false;
 224        }
 225
 226        /*
 227        * In the SW state machine, lets Put LSPCON in PCON mode only.
 228        * In this way, it will work with both HDMI 1.4 sinks as well as HDMI
 229        * 2.0 sinks.
 230        */
 231        if (lspcon->active && lspcon->mode != DRM_LSPCON_MODE_PCON) {
 232                if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON) < 0) {
 233                        DRM_ERROR("LSPCON mode change to PCON failed\n");
 234                        return false;
 235                }
 236        }
 237
 238        if (!intel_dp_read_dpcd(dp)) {
 239                DRM_ERROR("LSPCON DPCD read failed\n");
 240                return false;
 241        }
 242
 243        drm_dp_read_desc(&dp->aux, &dp->desc, drm_dp_is_branch(dp->dpcd));
 244
 245        DRM_DEBUG_KMS("Success: LSPCON init\n");
 246        return true;
 247}
 248