Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/gpu/drm/display/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 131 kB image not shown  

Quelle  drm_dp_helper.c   Sprache: C

 
/*
 * Copyright © 2009 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */


#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/dynamic_debug.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/string_helpers.h>

#include <drm/display/drm_dp_helper.h>
#include <drm/display/drm_dp_mst_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_fixed.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include <drm/drm_panel.h>

#include "drm_dp_helper_internal.h"

DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, DD_CLASS_TYPE_DISJOINT_BITS, 0,
   "DRM_UT_CORE",
   "DRM_UT_DRIVER",
   "DRM_UT_KMS",
   "DRM_UT_PRIME",
   "DRM_UT_ATOMIC",
   "DRM_UT_VBL",
   "DRM_UT_STATE",
   "DRM_UT_LEASE",
   "DRM_UT_DP",
   "DRM_UT_DRMRES");

struct dp_aux_backlight {
 struct backlight_device *base;
 struct drm_dp_aux *aux;
 struct drm_edp_backlight_info info;
 bool enabled;
};

/**
 * DOC: dp helpers
 *
 * These functions contain some common logic and helpers at various abstraction
 * levels to deal with Display Port sink devices and related things like DP aux
 * channel transfers, EDID reading over DP aux channels, decoding certain DPCD
 * blocks, ...
 */


/* Helpers for DP link training */
static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r)
{
 return link_status[r - DP_LANE0_1_STATUS];
}

static u8 dp_get_lane_status(const u8 link_status[DP_LINK_STATUS_SIZE],
        int lane)
{
 int i = DP_LANE0_1_STATUS + (lane >> 1);
 int s = (lane & 1) * 4;
 u8 l = dp_link_status(link_status, i);

 return (l >> s) & 0xf;
}

bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
     int lane_count)
{
 u8 lane_align;
 u8 lane_status;
 int lane;

 lane_align = dp_link_status(link_status,
        DP_LANE_ALIGN_STATUS_UPDATED);
 if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0)
  return false;
 for (lane = 0; lane < lane_count; lane++) {
  lane_status = dp_get_lane_status(link_status, lane);
  if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS)
   return false;
 }
 return true;
}
EXPORT_SYMBOL(drm_dp_channel_eq_ok);

bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
         int lane_count)
{
 int lane;
 u8 lane_status;

 for (lane = 0; lane < lane_count; lane++) {
  lane_status = dp_get_lane_status(link_status, lane);
  if ((lane_status & DP_LANE_CR_DONE) == 0)
   return false;
 }
 return true;
}
EXPORT_SYMBOL(drm_dp_clock_recovery_ok);

u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE],
         int lane)
{
 int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
 int s = ((lane & 1) ?
   DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
   DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
 u8 l = dp_link_status(link_status, i);

 return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT;
}
EXPORT_SYMBOL(drm_dp_get_adjust_request_voltage);

u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE],
       int lane)
{
 int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
 int s = ((lane & 1) ?
   DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
   DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
 u8 l = dp_link_status(link_status, i);

 return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
}
EXPORT_SYMBOL(drm_dp_get_adjust_request_pre_emphasis);

/* DP 2.0 128b/132b */
u8 drm_dp_get_adjust_tx_ffe_preset(const u8 link_status[DP_LINK_STATUS_SIZE],
       int lane)
{
 int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
 int s = ((lane & 1) ?
   DP_ADJUST_TX_FFE_PRESET_LANE1_SHIFT :
   DP_ADJUST_TX_FFE_PRESET_LANE0_SHIFT);
 u8 l = dp_link_status(link_status, i);

 return (l >> s) & 0xf;
}
EXPORT_SYMBOL(drm_dp_get_adjust_tx_ffe_preset);

/* DP 2.0 errata for 128b/132b */
bool drm_dp_128b132b_lane_channel_eq_done(const u8 link_status[DP_LINK_STATUS_SIZE],
       int lane_count)
{
 u8 lane_align, lane_status;
 int lane;

 lane_align = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED);
 if (!(lane_align & DP_INTERLANE_ALIGN_DONE))
  return false;

 for (lane = 0; lane < lane_count; lane++) {
  lane_status = dp_get_lane_status(link_status, lane);
  if (!(lane_status & DP_LANE_CHANNEL_EQ_DONE))
   return false;
 }
 return true;
}
EXPORT_SYMBOL(drm_dp_128b132b_lane_channel_eq_done);

/* DP 2.0 errata for 128b/132b */
bool drm_dp_128b132b_lane_symbol_locked(const u8 link_status[DP_LINK_STATUS_SIZE],
     int lane_count)
{
 u8 lane_status;
 int lane;

 for (lane = 0; lane < lane_count; lane++) {
  lane_status = dp_get_lane_status(link_status, lane);
  if (!(lane_status & DP_LANE_SYMBOL_LOCKED))
   return false;
 }
 return true;
}
EXPORT_SYMBOL(drm_dp_128b132b_lane_symbol_locked);

/* DP 2.0 errata for 128b/132b */
bool drm_dp_128b132b_eq_interlane_align_done(const u8 link_status[DP_LINK_STATUS_SIZE])
{
 u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED);

 return status & DP_128B132B_DPRX_EQ_INTERLANE_ALIGN_DONE;
}
EXPORT_SYMBOL(drm_dp_128b132b_eq_interlane_align_done);

/* DP 2.0 errata for 128b/132b */
bool drm_dp_128b132b_cds_interlane_align_done(const u8 link_status[DP_LINK_STATUS_SIZE])
{
 u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED);

 return status & DP_128B132B_DPRX_CDS_INTERLANE_ALIGN_DONE;
}
EXPORT_SYMBOL(drm_dp_128b132b_cds_interlane_align_done);

/* DP 2.0 errata for 128b/132b */
bool drm_dp_128b132b_link_training_failed(const u8 link_status[DP_LINK_STATUS_SIZE])
{
 u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED);

 return status & DP_128B132B_LT_FAILED;
}
EXPORT_SYMBOL(drm_dp_128b132b_link_training_failed);

static int __8b10b_clock_recovery_delay_us(const struct drm_dp_aux *aux, u8 rd_interval)
{
 if (rd_interval > 4)
  drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n",
       aux->name, rd_interval);

 if (rd_interval == 0)
  return 100;

 return rd_interval * 4 * USEC_PER_MSEC;
}

static int __8b10b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval)
{
 if (rd_interval > 4)
  drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n",
       aux->name, rd_interval);

 if (rd_interval == 0)
  return 400;

 return rd_interval * 4 * USEC_PER_MSEC;
}

static int __128b132b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval)
{
 switch (rd_interval) {
 default:
  drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x\n",
       aux->name, rd_interval);
  fallthrough;
 case DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US:
  return 400;
 case DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS:
  return 4000;
 case DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS:
  return 8000;
 case DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS:
  return 12000;
 case DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS:
  return 16000;
 case DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS:
  return 32000;
 case DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS:
  return 64000;
 }
}

/*
 * The link training delays are different for:
 *
 *  - Clock recovery vs. channel equalization
 *  - DPRX vs. LTTPR
 *  - 128b/132b vs. 8b/10b
 *  - DPCD rev 1.3 vs. later
 *
 * Get the correct delay in us, reading DPCD if necessary.
 */

static int __read_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
   enum drm_dp_phy dp_phy, bool uhbr, bool cr)
{
 int (*parse)(const struct drm_dp_aux *aux, u8 rd_interval);
 unsigned int offset;
 u8 rd_interval, mask;

 if (dp_phy == DP_PHY_DPRX) {
  if (uhbr) {
   if (cr)
    return 100;

   offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL;
   mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK;
   parse = __128b132b_channel_eq_delay_us;
  } else {
   if (cr && dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
    return 100;

   offset = DP_TRAINING_AUX_RD_INTERVAL;
   mask = DP_TRAINING_AUX_RD_MASK;
   if (cr)
    parse = __8b10b_clock_recovery_delay_us;
   else
    parse = __8b10b_channel_eq_delay_us;
  }
 } else {
  if (uhbr) {
   offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy);
   mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK;
   parse = __128b132b_channel_eq_delay_us;
  } else {
   if (cr)
    return 100;

   offset = DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy);
   mask = DP_TRAINING_AUX_RD_MASK;
   parse = __8b10b_channel_eq_delay_us;
  }
 }

 if (offset < DP_RECEIVER_CAP_SIZE) {
  rd_interval = dpcd[offset];
 } else {
  if (drm_dp_dpcd_read_byte(aux, offset, &rd_interval) < 0) {
   drm_dbg_kms(aux->drm_dev, "%s: failed rd interval read\n",
        aux->name);
   /* arbitrary default delay */
   return 400;
  }
 }

 return parse(aux, rd_interval & mask);
}

int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
         enum drm_dp_phy dp_phy, bool uhbr)
{
 return __read_delay(aux, dpcd, dp_phy, uhbr, true);
}
EXPORT_SYMBOL(drm_dp_read_clock_recovery_delay);

int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
     enum drm_dp_phy dp_phy, bool uhbr)
{
 return __read_delay(aux, dpcd, dp_phy, uhbr, false);
}
EXPORT_SYMBOL(drm_dp_read_channel_eq_delay);

/* Per DP 2.0 Errata */
int drm_dp_128b132b_read_aux_rd_interval(struct drm_dp_aux *aux)
{
 int unit;
 u8 val;

 if (drm_dp_dpcd_read_byte(aux, DP_128B132B_TRAINING_AUX_RD_INTERVAL, &val) < 0) {
  drm_err(aux->drm_dev, "%s: failed rd interval read\n",
   aux->name);
  /* default to max */
  val = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK;
 }

 unit = (val & DP_128B132B_TRAINING_AUX_RD_INTERVAL_1MS_UNIT) ? 1 : 2;
 val &= DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK;

 return (val + 1) * unit * 1000;
}
EXPORT_SYMBOL(drm_dp_128b132b_read_aux_rd_interval);

void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux,
         const u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
 u8 rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
  DP_TRAINING_AUX_RD_MASK;
 int delay_us;

 if (dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
  delay_us = 100;
 else
  delay_us = __8b10b_clock_recovery_delay_us(aux, rd_interval);

 usleep_range(delay_us, delay_us * 2);
}
EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay);

static void __drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux,
       u8 rd_interval)
{
 int delay_us = __8b10b_channel_eq_delay_us(aux, rd_interval);

 usleep_range(delay_us, delay_us * 2);
}

void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux,
     const u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
 __drm_dp_link_train_channel_eq_delay(aux,
          dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
          DP_TRAINING_AUX_RD_MASK);
}
EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay);

/**
 * drm_dp_phy_name() - Get the name of the given DP PHY
 * @dp_phy: The DP PHY identifier
 *
 * Given the @dp_phy, get a user friendly name of the DP PHY, either "DPRX" or
 * "LTTPR <N>", or "<INVALID DP PHY>" on errors. The returned string is always
 * non-NULL and valid.
 *
 * Returns: Name of the DP PHY.
 */

const char *drm_dp_phy_name(enum drm_dp_phy dp_phy)
{
 static const char * const phy_names[] = {
  [DP_PHY_DPRX] = "DPRX",
  [DP_PHY_LTTPR1] = "LTTPR 1",
  [DP_PHY_LTTPR2] = "LTTPR 2",
  [DP_PHY_LTTPR3] = "LTTPR 3",
  [DP_PHY_LTTPR4] = "LTTPR 4",
  [DP_PHY_LTTPR5] = "LTTPR 5",
  [DP_PHY_LTTPR6] = "LTTPR 6",
  [DP_PHY_LTTPR7] = "LTTPR 7",
  [DP_PHY_LTTPR8] = "LTTPR 8",
 };

 if (dp_phy < 0 || dp_phy >= ARRAY_SIZE(phy_names) ||
     WARN_ON(!phy_names[dp_phy]))
  return "";

 return phy_names[dp_phy];
}
EXPORT_SYMBOL(drm_dp_phy_name);

void drm_dp_lttpr_link_train_clock_recovery_delay(void)
{
 usleep_range(100, 200);
}
EXPORT_SYMBOL(drm_dp_lttpr_link_train_clock_recovery_delay);

static u8 dp_lttpr_phy_cap(const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE], int r)
{
 return phy_cap[r - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1];
}

void drm_dp_lttpr_link_train_channel_eq_delay(const struct drm_dp_aux *aux,
           const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE])
{
 u8 interval = dp_lttpr_phy_cap(phy_cap,
           DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1) &
        DP_TRAINING_AUX_RD_MASK;

 __drm_dp_link_train_channel_eq_delay(aux, interval);
}
EXPORT_SYMBOL(drm_dp_lttpr_link_train_channel_eq_delay);

/**
 * drm_dp_lttpr_wake_timeout_setup() - Grant extended time for sink to wake up
 * @aux: The DP AUX channel to use
 * @transparent_mode: This is true if lttpr is in transparent mode
 *
 * This function checks if the sink needs any extended wake time, if it does
 * it grants this request. Post this setup the source device can keep trying
 * the Aux transaction till the granted wake timeout.
 * If this function is not called all Aux transactions are expected to take
 * a default of 1ms before they throw an error.
 */

void drm_dp_lttpr_wake_timeout_setup(struct drm_dp_aux *aux, bool transparent_mode)
{
 u8 val = 1;
 int ret;

 if (transparent_mode) {
  static const u8 timeout_mapping[] = {
   [DP_DPRX_SLEEP_WAKE_TIMEOUT_PERIOD_1_MS] = 1,
   [DP_DPRX_SLEEP_WAKE_TIMEOUT_PERIOD_20_MS] = 20,
   [DP_DPRX_SLEEP_WAKE_TIMEOUT_PERIOD_40_MS] = 40,
   [DP_DPRX_SLEEP_WAKE_TIMEOUT_PERIOD_60_MS] = 60,
   [DP_DPRX_SLEEP_WAKE_TIMEOUT_PERIOD_80_MS] = 80,
   [DP_DPRX_SLEEP_WAKE_TIMEOUT_PERIOD_100_MS] = 100,
  };

  ret = drm_dp_dpcd_readb(aux, DP_EXTENDED_DPRX_SLEEP_WAKE_TIMEOUT_REQUEST, &val);
  if (ret != 1) {
   drm_dbg_kms(aux->drm_dev,
        "Failed to read Extended sleep wake timeout request\n");
   return;
  }

  val = (val < sizeof(timeout_mapping) && timeout_mapping[val]) ?
   timeout_mapping[val] : 1;

  if (val > 1)
   drm_dp_dpcd_writeb(aux,
        DP_EXTENDED_DPRX_SLEEP_WAKE_TIMEOUT_GRANT,
        DP_DPRX_SLEEP_WAKE_TIMEOUT_PERIOD_GRANTED);
 } else {
  ret = drm_dp_dpcd_readb(aux, DP_PHY_REPEATER_EXTENDED_WAIT_TIMEOUT, &val);
  if (ret != 1) {
   drm_dbg_kms(aux->drm_dev,
        "Failed to read Extended sleep wake timeout request\n");
   return;
  }

  val = (val & DP_EXTENDED_WAKE_TIMEOUT_REQUEST_MASK) ?
   (val & DP_EXTENDED_WAKE_TIMEOUT_REQUEST_MASK) * 10 : 1;

  if (val > 1)
   drm_dp_dpcd_writeb(aux, DP_PHY_REPEATER_EXTENDED_WAIT_TIMEOUT,
        DP_EXTENDED_WAKE_TIMEOUT_GRANT);
 }
}
EXPORT_SYMBOL(drm_dp_lttpr_wake_timeout_setup);

u8 drm_dp_link_rate_to_bw_code(int link_rate)
{
 switch (link_rate) {
 case 1000000:
  return DP_LINK_BW_10;
 case 1350000:
  return DP_LINK_BW_13_5;
 case 2000000:
  return DP_LINK_BW_20;
 default:
  /* Spec says link_bw = link_rate / 0.27Gbps */
  return link_rate / 27000;
 }
}
EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code);

int drm_dp_bw_code_to_link_rate(u8 link_bw)
{
 switch (link_bw) {
 case DP_LINK_BW_10:
  return 1000000;
 case DP_LINK_BW_13_5:
  return 1350000;
 case DP_LINK_BW_20:
  return 2000000;
 default:
  /* Spec says link_rate = link_bw * 0.27Gbps */
  return link_bw * 27000;
 }
}
EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate);

#define AUX_RETRY_INTERVAL 500 /* us */

static inline void
drm_dp_dump_access(const struct drm_dp_aux *aux,
     u8 request, uint offset, void *buffer, int ret)
{
 const char *arrow = request == DP_AUX_NATIVE_READ ? "->" : "<-";

 if (ret > 0)
  drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d) %*ph\n",
      aux->name, offset, arrow, ret, min(ret, 20), buffer);
 else
  drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d)\n",
      aux->name, offset, arrow, ret);
}

/**
 * DOC: dp helpers
 *
 * The DisplayPort AUX channel is an abstraction to allow generic, driver-
 * independent access to AUX functionality. Drivers can take advantage of
 * this by filling in the fields of the drm_dp_aux structure.
 *
 * Transactions are described using a hardware-independent drm_dp_aux_msg
 * structure, which is passed into a driver's .transfer() implementation.
 * Both native and I2C-over-AUX transactions are supported.
 */


static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request,
         unsigned int offset, void *buffer, size_t size)
{
 struct drm_dp_aux_msg msg;
 unsigned int retry, native_reply;
 int err = 0, ret = 0;

 memset(&msg, 0, sizeof(msg));
 msg.address = offset;
 msg.request = request;
 msg.buffer = buffer;
 msg.size = size;

 mutex_lock(&aux->hw_mutex);

 /*
 * If the device attached to the aux bus is powered down then there's
 * no reason to attempt a transfer. Error out immediately.
 */

 if (aux->powered_down) {
  ret = -EBUSY;
  goto unlock;
 }

 /*
 * The specification doesn't give any recommendation on how often to
 * retry native transactions. We used to retry 7 times like for
 * aux i2c transactions but real world devices this wasn't
 * sufficient, bump to 32 which makes Dell 4k monitors happier.
 */

 for (retry = 0; retry < 32; retry++) {
  if (ret != 0 && ret != -ETIMEDOUT) {
   usleep_range(AUX_RETRY_INTERVAL,
         AUX_RETRY_INTERVAL + 100);
  }

  ret = aux->transfer(aux, &msg);
  if (ret >= 0) {
   native_reply = msg.reply & DP_AUX_NATIVE_REPLY_MASK;
   if (native_reply == DP_AUX_NATIVE_REPLY_ACK) {
    if (ret == size)
     goto unlock;

    ret = -EPROTO;
   } else
    ret = -EIO;
  }

  /*
 * We want the error we return to be the error we received on
 * the first transaction, since we may get a different error the
 * next time we retry
 */

  if (!err)
   err = ret;
 }

 drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up. First error: %d\n",
      aux->name, err);
 ret = err;

unlock:
 mutex_unlock(&aux->hw_mutex);
 return ret;
}

/**
 * drm_dp_dpcd_probe() - probe a given DPCD address with a 1-byte read access
 * @aux: DisplayPort AUX channel (SST)
 * @offset: address of the register to probe
 *
 * Probe the provided DPCD address by reading 1 byte from it. The function can
 * be used to trigger some side-effect the read access has, like waking up the
 * sink, without the need for the read-out value.
 *
 * Returns 0 if the read access suceeded, or a negative error code on failure.
 */

int drm_dp_dpcd_probe(struct drm_dp_aux *aux, unsigned int offset)
{
 u8 buffer;
 int ret;

 ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, &buffer, 1);
 WARN_ON(ret == 0);

 drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, &buffer, ret);

 return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL(drm_dp_dpcd_probe);

/**
 * drm_dp_dpcd_set_powered() - Set whether the DP device is powered
 * @aux: DisplayPort AUX channel; for convenience it's OK to pass NULL here
 *       and the function will be a no-op.
 * @powered: true if powered; false if not
 *
 * If the endpoint device on the DP AUX bus is known to be powered down
 * then this function can be called to make future transfers fail immediately
 * instead of needing to time out.
 *
 * If this function is never called then a device defaults to being powered.
 */

void drm_dp_dpcd_set_powered(struct drm_dp_aux *aux, bool powered)
{
 if (!aux)
  return;

 mutex_lock(&aux->hw_mutex);
 aux->powered_down = !powered;
 mutex_unlock(&aux->hw_mutex);
}
EXPORT_SYMBOL(drm_dp_dpcd_set_powered);

/**
 * drm_dp_dpcd_set_probe() - Set whether a probing before DPCD access is done
 * @aux: DisplayPort AUX channel
 * @enable: Enable the probing if required
 */

void drm_dp_dpcd_set_probe(struct drm_dp_aux *aux, bool enable)
{
 WRITE_ONCE(aux->dpcd_probe_disabled, !enable);
}
EXPORT_SYMBOL(drm_dp_dpcd_set_probe);

static bool dpcd_access_needs_probe(struct drm_dp_aux *aux)
{
 /*
 * HP ZR24w corrupts the first DPCD access after entering power save
 * mode. Eg. on a read, the entire buffer will be filled with the same
 * byte. Do a throw away read to avoid corrupting anything we care
 * about. Afterwards things will work correctly until the monitor
 * gets woken up and subsequently re-enters power save mode.
 *
 * The user pressing any button on the monitor is enough to wake it
 * up, so there is no particularly good place to do the workaround.
 * We just have to do it before any DPCD access and hope that the
 * monitor doesn't power down exactly after the throw away read.
 */

 return !aux->is_remote && !READ_ONCE(aux->dpcd_probe_disabled);
}

/**
 * drm_dp_dpcd_read() - read a series of bytes from the DPCD
 * @aux: DisplayPort AUX channel (SST or MST)
 * @offset: address of the (first) register to read
 * @buffer: buffer to store the register values
 * @size: number of bytes in @buffer
 *
 * Returns the number of bytes transferred on success, or a negative error
 * code on failure. -EIO is returned if the request was NAKed by the sink or
 * if the retry count was exceeded. If not all bytes were transferred, this
 * function returns -EPROTO. Errors from the underlying AUX channel transfer
 * function, with the exception of -EBUSY (which causes the transaction to
 * be retried), are propagated to the caller.
 *
 * In most of the cases you want to use drm_dp_dpcd_read_data() instead.
 */

ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset,
    void *buffer, size_t size)
{
 int ret;

 if (dpcd_access_needs_probe(aux)) {
  ret = drm_dp_dpcd_probe(aux, DP_TRAINING_PATTERN_SET);
  if (ret < 0)
   return ret;
 }

 if (aux->is_remote)
  ret = drm_dp_mst_dpcd_read(aux, offset, buffer, size);
 else
  ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset,
      buffer, size);

 drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, buffer, ret);
 return ret;
}
EXPORT_SYMBOL(drm_dp_dpcd_read);

/**
 * drm_dp_dpcd_write() - write a series of bytes to the DPCD
 * @aux: DisplayPort AUX channel (SST or MST)
 * @offset: address of the (first) register to write
 * @buffer: buffer containing the values to write
 * @size: number of bytes in @buffer
 *
 * Returns the number of bytes transferred on success, or a negative error
 * code on failure. -EIO is returned if the request was NAKed by the sink or
 * if the retry count was exceeded. If not all bytes were transferred, this
 * function returns -EPROTO. Errors from the underlying AUX channel transfer
 * function, with the exception of -EBUSY (which causes the transaction to
 * be retried), are propagated to the caller.
 *
 * In most of the cases you want to use drm_dp_dpcd_write_data() instead.
 */

ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset,
     void *buffer, size_t size)
{
 int ret;

 if (aux->is_remote)
  ret = drm_dp_mst_dpcd_write(aux, offset, buffer, size);
 else
  ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_WRITE, offset,
      buffer, size);

 drm_dp_dump_access(aux, DP_AUX_NATIVE_WRITE, offset, buffer, ret);
 return ret;
}
EXPORT_SYMBOL(drm_dp_dpcd_write);

/**
 * drm_dp_dpcd_read_link_status() - read DPCD link status (bytes 0x202-0x207)
 * @aux: DisplayPort AUX channel
 * @status: buffer to store the link status in (must be at least 6 bytes)
 *
 * Returns a negative error code on failure or 0 on success.
 */

int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
     u8 status[DP_LINK_STATUS_SIZE])
{
 return drm_dp_dpcd_read_data(aux, DP_LANE0_1_STATUS, status,
         DP_LINK_STATUS_SIZE);
}
EXPORT_SYMBOL(drm_dp_dpcd_read_link_status);

/**
 * drm_dp_dpcd_read_phy_link_status - get the link status information for a DP PHY
 * @aux: DisplayPort AUX channel
 * @dp_phy: the DP PHY to get the link status for
 * @link_status: buffer to return the status in
 *
 * Fetch the AUX DPCD registers for the DPRX or an LTTPR PHY link status. The
 * layout of the returned @link_status matches the DPCD register layout of the
 * DPRX PHY link status.
 *
 * Returns 0 if the information was read successfully or a negative error code
 * on failure.
 */

int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux,
         enum drm_dp_phy dp_phy,
         u8 link_status[DP_LINK_STATUS_SIZE])
{
 int ret;

 if (dp_phy == DP_PHY_DPRX)
  return drm_dp_dpcd_read_data(aux,
          DP_LANE0_1_STATUS,
          link_status,
          DP_LINK_STATUS_SIZE);

 ret = drm_dp_dpcd_read_data(aux,
        DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy),
        link_status,
        DP_LINK_STATUS_SIZE - 1);

 if (ret < 0)
  return ret;

 /* Convert the LTTPR to the sink PHY link status layout */
 memmove(&link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS + 1],
  &link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS],
  DP_LINK_STATUS_SIZE - (DP_SINK_STATUS - DP_LANE0_1_STATUS) - 1);
 link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS] = 0;

 return 0;
}
EXPORT_SYMBOL(drm_dp_dpcd_read_phy_link_status);

/**
 * drm_dp_link_power_up() - power up a DisplayPort link
 * @aux: DisplayPort AUX channel
 * @revision: DPCD revision supported on the link
 *
 * Returns 0 on success or a negative error code on failure.
 */

int drm_dp_link_power_up(struct drm_dp_aux *aux, unsigned char revision)
{
 u8 value;
 int err;

 /* DP_SET_POWER register is only available on DPCD v1.1 and later */
 if (revision < DP_DPCD_REV_11)
  return 0;

 err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
 if (err < 0)
  return err;

 value &= ~DP_SET_POWER_MASK;
 value |= DP_SET_POWER_D0;

 err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
 if (err < 0)
  return err;

 /*
 * According to the DP 1.1 specification, a "Sink Device must exit the
 * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
 * Control Field" (register 0x600).
 */

 usleep_range(1000, 2000);

 return 0;
}
EXPORT_SYMBOL(drm_dp_link_power_up);

/**
 * drm_dp_link_power_down() - power down a DisplayPort link
 * @aux: DisplayPort AUX channel
 * @revision: DPCD revision supported on the link
 *
 * Returns 0 on success or a negative error code on failure.
 */

int drm_dp_link_power_down(struct drm_dp_aux *aux, unsigned char revision)
{
 u8 value;
 int err;

 /* DP_SET_POWER register is only available on DPCD v1.1 and later */
 if (revision < DP_DPCD_REV_11)
  return 0;

 err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
 if (err < 0)
  return err;

 value &= ~DP_SET_POWER_MASK;
 value |= DP_SET_POWER_D3;

 err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
 if (err < 0)
  return err;

 return 0;
}
EXPORT_SYMBOL(drm_dp_link_power_down);

static int read_payload_update_status(struct drm_dp_aux *aux)
{
 int ret;
 u8 status;

 ret = drm_dp_dpcd_read_byte(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status);
 if (ret < 0)
  return ret;

 return status;
}

/**
 * drm_dp_dpcd_write_payload() - Write Virtual Channel information to payload table
 * @aux: DisplayPort AUX channel
 * @vcpid: Virtual Channel Payload ID
 * @start_time_slot: Starting time slot
 * @time_slot_count: Time slot count
 *
 * Write the Virtual Channel payload allocation table, checking the payload
 * update status and retrying as necessary.
 *
 * Returns:
 * 0 on success, negative error otherwise
 */

int drm_dp_dpcd_write_payload(struct drm_dp_aux *aux,
         int vcpid, u8 start_time_slot, u8 time_slot_count)
{
 u8 payload_alloc[3], status;
 int ret;
 int retries = 0;

 drm_dp_dpcd_write_byte(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS,
          DP_PAYLOAD_TABLE_UPDATED);

 payload_alloc[0] = vcpid;
 payload_alloc[1] = start_time_slot;
 payload_alloc[2] = time_slot_count;

 ret = drm_dp_dpcd_write_data(aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3);
 if (ret < 0) {
  drm_dbg_kms(aux->drm_dev, "failed to write payload allocation %d\n", ret);
  goto fail;
 }

retry:
 ret = drm_dp_dpcd_read_byte(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status);
 if (ret < 0) {
  drm_dbg_kms(aux->drm_dev, "failed to read payload table status %d\n", ret);
  goto fail;
 }

 if (!(status & DP_PAYLOAD_TABLE_UPDATED)) {
  retries++;
  if (retries < 20) {
   usleep_range(10000, 20000);
   goto retry;
  }
  drm_dbg_kms(aux->drm_dev, "status not set after read payload table status %d\n",
       status);
  ret = -EINVAL;
  goto fail;
 }
 ret = 0;
fail:
 return ret;
}
EXPORT_SYMBOL(drm_dp_dpcd_write_payload);

/**
 * drm_dp_dpcd_clear_payload() - Clear the entire VC Payload ID table
 * @aux: DisplayPort AUX channel
 *
 * Clear the entire VC Payload ID table.
 *
 * Returns: 0 on success, negative error code on errors.
 */

int drm_dp_dpcd_clear_payload(struct drm_dp_aux *aux)
{
 return drm_dp_dpcd_write_payload(aux, 0, 0, 0x3f);
}
EXPORT_SYMBOL(drm_dp_dpcd_clear_payload);

/**
 * drm_dp_dpcd_poll_act_handled() - Poll for ACT handled status
 * @aux: DisplayPort AUX channel
 * @timeout_ms: Timeout in ms
 *
 * Try waiting for the sink to finish updating its payload table by polling for
 * the ACT handled bit of DP_PAYLOAD_TABLE_UPDATE_STATUS for up to @timeout_ms
 * milliseconds, defaulting to 3000 ms if 0.
 *
 * Returns:
 * 0 if the ACT was handled in time, negative error code on failure.
 */

int drm_dp_dpcd_poll_act_handled(struct drm_dp_aux *aux, int timeout_ms)
{
 int ret, status;

 /* default to 3 seconds, this is arbitrary */
 timeout_ms = timeout_ms ?: 3000;

 ret = readx_poll_timeout(read_payload_update_status, aux, status,
     status & DP_PAYLOAD_ACT_HANDLED || status < 0,
     200, timeout_ms * USEC_PER_MSEC);
 if (ret < 0 && status >= 0) {
  drm_err(aux->drm_dev, "Failed to get ACT after %d ms, last status: %02x\n",
   timeout_ms, status);
  return -EINVAL;
 } else if (status < 0) {
  /*
 * Failure here isn't unexpected - the hub may have
 * just been unplugged
 */

  drm_dbg_kms(aux->drm_dev, "Failed to read payload table status: %d\n", status);
  return status;
 }

 return 0;
}
EXPORT_SYMBOL(drm_dp_dpcd_poll_act_handled);

static bool is_edid_digital_input_dp(const struct drm_edid *drm_edid)
{
 /* FIXME: get rid of drm_edid_raw() */
 const struct edid *edid = drm_edid_raw(drm_edid);

 return edid && edid->revision >= 4 &&
  edid->input & DRM_EDID_INPUT_DIGITAL &&
  (edid->input & DRM_EDID_DIGITAL_TYPE_MASK) == DRM_EDID_DIGITAL_TYPE_DP;
}

/**
 * drm_dp_downstream_is_type() - is the downstream facing port of certain type?
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 * @type: port type to be checked. Can be:
 *    %DP_DS_PORT_TYPE_DP, %DP_DS_PORT_TYPE_VGA, %DP_DS_PORT_TYPE_DVI,
 *    %DP_DS_PORT_TYPE_HDMI, %DP_DS_PORT_TYPE_NON_EDID,
 *   %DP_DS_PORT_TYPE_DP_DUALMODE or %DP_DS_PORT_TYPE_WIRELESS.
 *
 * Caveat: Only works with DPCD 1.1+ port caps.
 *
 * Returns: whether the downstream facing port matches the type.
 */

bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
          const u8 port_cap[4], u8 type)
{
 return drm_dp_is_branch(dpcd) &&
  dpcd[DP_DPCD_REV] >= 0x11 &&
  (port_cap[0] & DP_DS_PORT_TYPE_MASK) == type;
}
EXPORT_SYMBOL(drm_dp_downstream_is_type);

/**
 * drm_dp_downstream_is_tmds() - is the downstream facing port TMDS?
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 * @drm_edid: EDID
 *
 * Returns: whether the downstream facing port is TMDS (HDMI/DVI).
 */

bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
          const u8 port_cap[4],
          const struct drm_edid *drm_edid)
{
 if (dpcd[DP_DPCD_REV] < 0x11) {
  switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) {
  case DP_DWN_STRM_PORT_TYPE_TMDS:
   return true;
  default:
   return false;
  }
 }

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_DP_DUALMODE:
  if (is_edid_digital_input_dp(drm_edid))
   return false;
  fallthrough;
 case DP_DS_PORT_TYPE_DVI:
 case DP_DS_PORT_TYPE_HDMI:
  return true;
 default:
  return false;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_is_tmds);

/**
 * drm_dp_send_real_edid_checksum() - send back real edid checksum value
 * @aux: DisplayPort AUX channel
 * @real_edid_checksum: real edid checksum for the last block
 *
 * Returns:
 * True on success
 */

bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux,
        u8 real_edid_checksum)
{
 u8 link_edid_read = 0, auto_test_req = 0, test_resp = 0;

 if (drm_dp_dpcd_read_byte(aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
      &auto_test_req) < 0) {
  drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n",
   aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR);
  return false;
 }
 auto_test_req &= DP_AUTOMATED_TEST_REQUEST;

 if (drm_dp_dpcd_read_byte(aux, DP_TEST_REQUEST, &link_edid_read) < 0) {
  drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n",
   aux->name, DP_TEST_REQUEST);
  return false;
 }
 link_edid_read &= DP_TEST_LINK_EDID_READ;

 if (!auto_test_req || !link_edid_read) {
  drm_dbg_kms(aux->drm_dev, "%s: Source DUT does not support TEST_EDID_READ\n",
       aux->name);
  return false;
 }

 if (drm_dp_dpcd_write_byte(aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
       auto_test_req) < 0) {
  drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n",
   aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR);
  return false;
 }

 /* send back checksum for the last edid extension block data */
 if (drm_dp_dpcd_write_byte(aux, DP_TEST_EDID_CHECKSUM,
       real_edid_checksum) < 0) {
  drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n",
   aux->name, DP_TEST_EDID_CHECKSUM);
  return false;
 }

 test_resp |= DP_TEST_EDID_CHECKSUM_WRITE;
 if (drm_dp_dpcd_write_byte(aux, DP_TEST_RESPONSE, test_resp) < 0) {
  drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n",
   aux->name, DP_TEST_RESPONSE);
  return false;
 }

 return true;
}
EXPORT_SYMBOL(drm_dp_send_real_edid_checksum);

static u8 drm_dp_downstream_port_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
 u8 port_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & DP_PORT_COUNT_MASK;

 if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE && port_count > 4)
  port_count = 4;

 return port_count;
}

static int drm_dp_read_extended_dpcd_caps(struct drm_dp_aux *aux,
       u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
 u8 dpcd_ext[DP_RECEIVER_CAP_SIZE];
 int ret;

 /*
 * Prior to DP1.3 the bit represented by
 * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved.
 * If it is set DP_DPCD_REV at 0000h could be at a value less than
 * the true capability of the panel. The only way to check is to
 * then compare 0000h and 2200h.
 */

 if (!(dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
       DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT))
  return 0;

 ret = drm_dp_dpcd_read_data(aux, DP_DP13_DPCD_REV, &dpcd_ext,
        sizeof(dpcd_ext));
 if (ret < 0)
  return ret;

 if (dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) {
  drm_dbg_kms(aux->drm_dev,
       "%s: Extended DPCD rev less than base DPCD rev (%d > %d)\n",
       aux->name, dpcd[DP_DPCD_REV], dpcd_ext[DP_DPCD_REV]);
  return 0;
 }

 if (!memcmp(dpcd, dpcd_ext, sizeof(dpcd_ext)))
  return 0;

 drm_dbg_kms(aux->drm_dev, "%s: Base DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd);

 memcpy(dpcd, dpcd_ext, sizeof(dpcd_ext));

 return 0;
}

/**
 * drm_dp_read_dpcd_caps() - read DPCD caps and extended DPCD caps if
 * available
 * @aux: DisplayPort AUX channel
 * @dpcd: Buffer to store the resulting DPCD in
 *
 * Attempts to read the base DPCD caps for @aux. Additionally, this function
 * checks for and reads the extended DPRX caps (%DP_DP13_DPCD_REV) if
 * present.
 *
 * Returns: %0 if the DPCD was read successfully, negative error code
 * otherwise.
 */

int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux,
     u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
 int ret;

 ret = drm_dp_dpcd_read_data(aux, DP_DPCD_REV, dpcd, DP_RECEIVER_CAP_SIZE);
 if (ret < 0)
  return ret;
 if (dpcd[DP_DPCD_REV] == 0)
  return -EIO;

 ret = drm_dp_read_extended_dpcd_caps(aux, dpcd);
 if (ret < 0)
  return ret;

 drm_dbg_kms(aux->drm_dev, "%s: DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd);

 return ret;
}
EXPORT_SYMBOL(drm_dp_read_dpcd_caps);

/**
 * drm_dp_read_downstream_info() - read DPCD downstream port info if available
 * @aux: DisplayPort AUX channel
 * @dpcd: A cached copy of the port's DPCD
 * @downstream_ports: buffer to store the downstream port info in
 *
 * See also:
 * drm_dp_downstream_max_clock()
 * drm_dp_downstream_max_bpc()
 *
 * Returns: 0 if either the downstream port info was read successfully or
 * there was no downstream info to read, or a negative error code otherwise.
 */

int drm_dp_read_downstream_info(struct drm_dp_aux *aux,
    const u8 dpcd[DP_RECEIVER_CAP_SIZE],
    u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS])
{
 int ret;
 u8 len;

 memset(downstream_ports, 0, DP_MAX_DOWNSTREAM_PORTS);

 /* No downstream info to read */
 if (!drm_dp_is_branch(dpcd) || dpcd[DP_DPCD_REV] == DP_DPCD_REV_10)
  return 0;

 /* Some branches advertise having 0 downstream ports, despite also advertising they have a
 * downstream port present. The DP spec isn't clear on if this is allowed or not, but since
 * some branches do it we need to handle it regardless.
 */

 len = drm_dp_downstream_port_count(dpcd);
 if (!len)
  return 0;

 if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE)
  len *= 4;

 ret = drm_dp_dpcd_read_data(aux, DP_DOWNSTREAM_PORT_0, downstream_ports, len);
 if (ret < 0)
  return ret;

 drm_dbg_kms(aux->drm_dev, "%s: DPCD DFP: %*ph\n", aux->name, len, downstream_ports);

 return 0;
}
EXPORT_SYMBOL(drm_dp_read_downstream_info);

/**
 * drm_dp_downstream_max_dotclock() - extract downstream facing port max dot clock
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 *
 * Returns: Downstream facing port max dot clock in kHz on success,
 * or 0 if max clock not defined
 */

int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
       const u8 port_cap[4])
{
 if (!drm_dp_is_branch(dpcd))
  return 0;

 if (dpcd[DP_DPCD_REV] < 0x11)
  return 0;

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_VGA:
  if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0)
   return 0;
  return port_cap[1] * 8000;
 default:
  return 0;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_max_dotclock);

/**
 * drm_dp_downstream_max_tmds_clock() - extract downstream facing port max TMDS clock
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 * @drm_edid: EDID
 *
 * Returns: HDMI/DVI downstream facing port max TMDS clock in kHz on success,
 * or 0 if max TMDS clock not defined
 */

int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
         const u8 port_cap[4],
         const struct drm_edid *drm_edid)
{
 if (!drm_dp_is_branch(dpcd))
  return 0;

 if (dpcd[DP_DPCD_REV] < 0x11) {
  switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) {
  case DP_DWN_STRM_PORT_TYPE_TMDS:
   return 165000;
  default:
   return 0;
  }
 }

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_DP_DUALMODE:
  if (is_edid_digital_input_dp(drm_edid))
   return 0;
  /*
 * It's left up to the driver to check the
 * DP dual mode adapter's max TMDS clock.
 *
 * Unfortunately it looks like branch devices
 * may not fordward that the DP dual mode i2c
 * access so we just usually get i2c nak :(
 */

  fallthrough;
 case DP_DS_PORT_TYPE_HDMI:
   /*
  * We should perhaps assume 165 MHz when detailed cap
  * info is not available. But looks like many typical
  * branch devices fall into that category and so we'd
  * probably end up with users complaining that they can't
  * get high resolution modes with their favorite dongle.
  *
  * So let's limit to 300 MHz instead since DPCD 1.4
  * HDMI 2.0 DFPs are required to have the detailed cap
  * info. So it's more likely we're dealing with a HDMI 1.4
  * compatible* device here.
  */

  if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0)
   return 300000;
  return port_cap[1] * 2500;
 case DP_DS_PORT_TYPE_DVI:
  if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0)
   return 165000;
  /* FIXME what to do about DVI dual link? */
  return port_cap[1] * 2500;
 default:
  return 0;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_max_tmds_clock);

/**
 * drm_dp_downstream_min_tmds_clock() - extract downstream facing port min TMDS clock
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 * @drm_edid: EDID
 *
 * Returns: HDMI/DVI downstream facing port min TMDS clock in kHz on success,
 * or 0 if max TMDS clock not defined
 */

int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
         const u8 port_cap[4],
         const struct drm_edid *drm_edid)
{
 if (!drm_dp_is_branch(dpcd))
  return 0;

 if (dpcd[DP_DPCD_REV] < 0x11) {
  switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) {
  case DP_DWN_STRM_PORT_TYPE_TMDS:
   return 25000;
  default:
   return 0;
  }
 }

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_DP_DUALMODE:
  if (is_edid_digital_input_dp(drm_edid))
   return 0;
  fallthrough;
 case DP_DS_PORT_TYPE_DVI:
 case DP_DS_PORT_TYPE_HDMI:
  /*
 * Unclear whether the protocol converter could
 * utilize pixel replication. Assume it won't.
 */

  return 25000;
 default:
  return 0;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_min_tmds_clock);

/**
 * drm_dp_downstream_max_bpc() - extract downstream facing port max
 *                               bits per component
 * @dpcd: DisplayPort configuration data
 * @port_cap: downstream facing port capabilities
 * @drm_edid: EDID
 *
 * Returns: Max bpc on success or 0 if max bpc not defined
 */

int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
         const u8 port_cap[4],
         const struct drm_edid *drm_edid)
{
 if (!drm_dp_is_branch(dpcd))
  return 0;

 if (dpcd[DP_DPCD_REV] < 0x11) {
  switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) {
  case DP_DWN_STRM_PORT_TYPE_DP:
   return 0;
  default:
   return 8;
  }
 }

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_DP:
  return 0;
 case DP_DS_PORT_TYPE_DP_DUALMODE:
  if (is_edid_digital_input_dp(drm_edid))
   return 0;
  fallthrough;
 case DP_DS_PORT_TYPE_HDMI:
 case DP_DS_PORT_TYPE_DVI:
 case DP_DS_PORT_TYPE_VGA:
  if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0)
   return 8;

  switch (port_cap[2] & DP_DS_MAX_BPC_MASK) {
  case DP_DS_8BPC:
   return 8;
  case DP_DS_10BPC:
   return 10;
  case DP_DS_12BPC:
   return 12;
  case DP_DS_16BPC:
   return 16;
  default:
   return 8;
  }
  break;
 default:
  return 8;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_max_bpc);

/**
 * drm_dp_downstream_420_passthrough() - determine downstream facing port
 *                                       YCbCr 4:2:0 pass-through capability
 * @dpcd: DisplayPort configuration data
 * @port_cap: downstream facing port capabilities
 *
 * Returns: whether the downstream facing port can pass through YCbCr 4:2:0
 */

bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
           const u8 port_cap[4])
{
 if (!drm_dp_is_branch(dpcd))
  return false;

 if (dpcd[DP_DPCD_REV] < 0x13)
  return false;

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_DP:
  return true;
 case DP_DS_PORT_TYPE_HDMI:
  if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0)
   return false;

  return port_cap[3] & DP_DS_HDMI_YCBCR420_PASS_THROUGH;
 default:
  return false;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_420_passthrough);

/**
 * drm_dp_downstream_444_to_420_conversion() - determine downstream facing port
 *                                             YCbCr 4:4:4->4:2:0 conversion capability
 * @dpcd: DisplayPort configuration data
 * @port_cap: downstream facing port capabilities
 *
 * Returns: whether the downstream facing port can convert YCbCr 4:4:4 to 4:2:0
 */

bool drm_dp_downstream_444_to_420_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
          const u8 port_cap[4])
{
 if (!drm_dp_is_branch(dpcd))
  return false;

 if (dpcd[DP_DPCD_REV] < 0x13)
  return false;

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_HDMI:
  if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0)
   return false;

  return port_cap[3] & DP_DS_HDMI_YCBCR444_TO_420_CONV;
 default:
  return false;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_444_to_420_conversion);

/**
 * drm_dp_downstream_rgb_to_ycbcr_conversion() - determine downstream facing port
 *                                               RGB->YCbCr conversion capability
 * @dpcd: DisplayPort configuration data
 * @port_cap: downstream facing port capabilities
 * @color_spc: Colorspace for which conversion cap is sought
 *
 * Returns: whether the downstream facing port can convert RGB->YCbCr for a given
 * colorspace.
 */

bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
            const u8 port_cap[4],
            u8 color_spc)
{
 if (!drm_dp_is_branch(dpcd))
  return false;

 if (dpcd[DP_DPCD_REV] < 0x13)
  return false;

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_HDMI:
  if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0)
   return false;

  return port_cap[3] & color_spc;
 default:
  return false;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_rgb_to_ycbcr_conversion);

/**
 * drm_dp_downstream_mode() - return a mode for downstream facing port
 * @dev: DRM device
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 *
 * Provides a suitable mode for downstream facing ports without EDID.
 *
 * Returns: A new drm_display_mode on success or NULL on failure
 */

struct drm_display_mode *
drm_dp_downstream_mode(struct drm_device *dev,
         const u8 dpcd[DP_RECEIVER_CAP_SIZE],
         const u8 port_cap[4])

{
 u8 vic;

 if (!drm_dp_is_branch(dpcd))
  return NULL;

 if (dpcd[DP_DPCD_REV] < 0x11)
  return NULL;

 switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) {
 case DP_DS_PORT_TYPE_NON_EDID:
  switch (port_cap[0] & DP_DS_NON_EDID_MASK) {
  case DP_DS_NON_EDID_720x480i_60:
   vic = 6;
   break;
  case DP_DS_NON_EDID_720x480i_50:
   vic = 21;
   break;
  case DP_DS_NON_EDID_1920x1080i_60:
   vic = 5;
   break;
  case DP_DS_NON_EDID_1920x1080i_50:
   vic = 20;
   break;
  case DP_DS_NON_EDID_1280x720_60:
   vic = 4;
   break;
  case DP_DS_NON_EDID_1280x720_50:
   vic = 19;
   break;
  default:
   return NULL;
  }
  return drm_display_mode_from_cea_vic(dev, vic);
 default:
  return NULL;
 }
}
EXPORT_SYMBOL(drm_dp_downstream_mode);

/**
 * drm_dp_downstream_id() - identify branch device
 * @aux: DisplayPort AUX channel
 * @id: DisplayPort branch device id
 *
 * Returns branch device id on success or NULL on failure
 */

int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6])
{
 return drm_dp_dpcd_read_data(aux, DP_BRANCH_ID, id, 6);
}
EXPORT_SYMBOL(drm_dp_downstream_id);

/**
 * drm_dp_downstream_debug() - debug DP branch devices
 * @m: pointer for debugfs file
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 * @drm_edid: EDID
 * @aux: DisplayPort AUX channel
 *
 */

void drm_dp_downstream_debug(struct seq_file *m,
        const u8 dpcd[DP_RECEIVER_CAP_SIZE],
        const u8 port_cap[4],
        const struct drm_edid *drm_edid,
        struct drm_dp_aux *aux)
{
 bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] &
     DP_DETAILED_CAP_INFO_AVAILABLE;
 int clk;
 int bpc;
 char id[7];
 int len;
 uint8_t rev[2];
 int type = port_cap[0] & DP_DS_PORT_TYPE_MASK;
 bool branch_device = drm_dp_is_branch(dpcd);

 seq_printf(m, "\tDP branch device present: %s\n",
     str_yes_no(branch_device));

 if (!branch_device)
  return;

 switch (type) {
 case DP_DS_PORT_TYPE_DP:
  seq_puts(m, "\t\tType: DisplayPort\n");
  break;
 case DP_DS_PORT_TYPE_VGA:
  seq_puts(m, "\t\tType: VGA\n");
  break;
 case DP_DS_PORT_TYPE_DVI:
  seq_puts(m, "\t\tType: DVI\n");
  break;
 case DP_DS_PORT_TYPE_HDMI:
  seq_puts(m, "\t\tType: HDMI\n");
  break;
 case DP_DS_PORT_TYPE_NON_EDID:
  seq_puts(m, "\t\tType: others without EDID support\n");
  break;
 case DP_DS_PORT_TYPE_DP_DUALMODE:
  seq_puts(m, "\t\tType: DP++\n");
  break;
 case DP_DS_PORT_TYPE_WIRELESS:
  seq_puts(m, "\t\tType: Wireless\n");
  break;
 default:
  seq_puts(m, "\t\tType: N/A\n");
 }

 memset(id, 0, sizeof(id));
 drm_dp_downstream_id(aux, id);
 seq_printf(m, "\t\tID: %s\n", id);

 len = drm_dp_dpcd_read_data(aux, DP_BRANCH_HW_REV, &rev[0], 1);
 if (!len)
  seq_printf(m, "\t\tHW: %d.%d\n",
      (rev[0] & 0xf0) >> 4, rev[0] & 0xf);

 len = drm_dp_dpcd_read_data(aux, DP_BRANCH_SW_REV, rev, 2);
 if (!len)
  seq_printf(m, "\t\tSW: %d.%d\n", rev[0], rev[1]);

 if (detailed_cap_info) {
  clk = drm_dp_downstream_max_dotclock(dpcd, port_cap);
  if (clk > 0)
   seq_printf(m, "\t\tMax dot clock: %d kHz\n", clk);

  clk = drm_dp_downstream_max_tmds_clock(dpcd, port_cap, drm_edid);
  if (clk > 0)
   seq_printf(m, "\t\tMax TMDS clock: %d kHz\n", clk);

  clk = drm_dp_downstream_min_tmds_clock(dpcd, port_cap, drm_edid);
  if (clk > 0)
   seq_printf(m, "\t\tMin TMDS clock: %d kHz\n", clk);

  bpc = drm_dp_downstream_max_bpc(dpcd, port_cap, drm_edid);

  if (bpc > 0)
   seq_printf(m, "\t\tMax bpc: %d\n", bpc);
 }
}
EXPORT_SYMBOL(drm_dp_downstream_debug);

/**
 * drm_dp_subconnector_type() - get DP branch device type
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 */

enum drm_mode_subconnector
drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
    const u8 port_cap[4])
{
 int type;
 if (!drm_dp_is_branch(dpcd))
  return DRM_MODE_SUBCONNECTOR_Native;
 /* DP 1.0 approach */
 if (dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) {
  type = dpcd[DP_DOWNSTREAMPORT_PRESENT] &
         DP_DWN_STRM_PORT_TYPE_MASK;

  switch (type) {
  case DP_DWN_STRM_PORT_TYPE_TMDS:
   /* Can be HDMI or DVI-D, DVI-D is a safer option */
   return DRM_MODE_SUBCONNECTOR_DVID;
  case DP_DWN_STRM_PORT_TYPE_ANALOG:
   /* Can be VGA or DVI-A, VGA is more popular */
   return DRM_MODE_SUBCONNECTOR_VGA;
  case DP_DWN_STRM_PORT_TYPE_DP:
   return DRM_MODE_SUBCONNECTOR_DisplayPort;
  case DP_DWN_STRM_PORT_TYPE_OTHER:
  default:
   return DRM_MODE_SUBCONNECTOR_Unknown;
  }
 }
 type = port_cap[0] & DP_DS_PORT_TYPE_MASK;

 switch (type) {
 case DP_DS_PORT_TYPE_DP:
 case DP_DS_PORT_TYPE_DP_DUALMODE:
  return DRM_MODE_SUBCONNECTOR_DisplayPort;
 case DP_DS_PORT_TYPE_VGA:
  return DRM_MODE_SUBCONNECTOR_VGA;
 case DP_DS_PORT_TYPE_DVI:
  return DRM_MODE_SUBCONNECTOR_DVID;
 case DP_DS_PORT_TYPE_HDMI:
  return DRM_MODE_SUBCONNECTOR_HDMIA;
 case DP_DS_PORT_TYPE_WIRELESS:
  return DRM_MODE_SUBCONNECTOR_Wireless;
 case DP_DS_PORT_TYPE_NON_EDID:
 default:
  return DRM_MODE_SUBCONNECTOR_Unknown;
 }
}
EXPORT_SYMBOL(drm_dp_subconnector_type);

/**
 * drm_dp_set_subconnector_property - set subconnector for DP connector
 * @connector: connector to set property on
 * @status: connector status
 * @dpcd: DisplayPort configuration data
 * @port_cap: port capabilities
 *
 * Called by a driver on every detect event.
 */

void drm_dp_set_subconnector_property(struct drm_connector *connector,
          enum drm_connector_status status,
          const u8 *dpcd,
          const u8 port_cap[4])
{
 enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown;

 if (status == connector_status_connected)
  subconnector = drm_dp_subconnector_type(dpcd, port_cap);
 drm_object_property_set_value(&connector->base,
   connector->dev->mode_config.dp_subconnector_property,
   subconnector);
}
EXPORT_SYMBOL(drm_dp_set_subconnector_property);

/**
 * drm_dp_read_sink_count_cap() - Check whether a given connector has a valid sink
 * count
 * @connector: The DRM connector to check
 * @dpcd: A cached copy of the connector's DPCD RX capabilities
 * @desc: A cached copy of the connector's DP descriptor
 *
 * See also: drm_dp_read_sink_count()
 *
 * Returns: %True if the (e)DP connector has a valid sink count that should
 * be probed, %false otherwise.
 */

bool drm_dp_read_sink_count_cap(struct drm_connector *connector,
    const u8 dpcd[DP_RECEIVER_CAP_SIZE],
    const struct drm_dp_desc *desc)
{
 /* Some eDP panels don't set a valid value for the sink count */
 return connector->connector_type != DRM_MODE_CONNECTOR_eDP &&
  dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 &&
  dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT &&
  !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT);
}
EXPORT_SYMBOL(drm_dp_read_sink_count_cap);

/**
 * drm_dp_read_sink_count() - Retrieve the sink count for a given sink
 * @aux: The DP AUX channel to use
 *
 * See also: drm_dp_read_sink_count_cap()
 *
 * Returns: The current sink count reported by @aux, or a negative error code
 * otherwise.
 */

int drm_dp_read_sink_count(struct drm_dp_aux *aux)
{
 u8 count;
 int ret;

 ret = drm_dp_dpcd_read_byte(aux, DP_SINK_COUNT, &count);
 if (ret < 0)
  return ret;

 return DP_GET_SINK_COUNT(count);
}
EXPORT_SYMBOL(drm_dp_read_sink_count);

/*
 * I2C-over-AUX implementation
 */


static u32 drm_dp_i2c_functionality(struct i2c_adapter *adapter)
{
 return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
        I2C_FUNC_SMBUS_READ_BLOCK_DATA |
        I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
        I2C_FUNC_10BIT_ADDR;
}

static void drm_dp_i2c_msg_write_status_update(struct drm_dp_aux_msg *msg)
{
 /*
 * In case of i2c defer or short i2c ack reply to a write,
 * we need to switch to WRITE_STATUS_UPDATE to drain the
 * rest of the message
 */

 if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) {
  msg->request &= DP_AUX_I2C_MOT;
  msg->request |= DP_AUX_I2C_WRITE_STATUS_UPDATE;
 }
}

#define AUX_PRECHARGE_LEN 10 /* 10 to 16 */
#define AUX_SYNC_LEN (16 + 4) /* preamble + AUX_SYNC_END */
#define AUX_STOP_LEN 4
#define AUX_CMD_LEN 4
#define AUX_ADDRESS_LEN 20
#define AUX_REPLY_PAD_LEN 4
#define AUX_LENGTH_LEN 8

/*
 * Calculate the duration of the AUX request/reply in usec. Gives the
 * "best" case estimate, ie. successful while as short as possible.
 */

static int drm_dp_aux_req_duration(const struct drm_dp_aux_msg *msg)
{
 int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN +
  AUX_CMD_LEN + AUX_ADDRESS_LEN + AUX_LENGTH_LEN;

 if ((msg->request & DP_AUX_I2C_READ) == 0)
  len += msg->size * 8;

 return len;
}

static int drm_dp_aux_reply_duration(const struct drm_dp_aux_msg *msg)
{
 int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN +
  AUX_CMD_LEN + AUX_REPLY_PAD_LEN;

 /*
 * For read we expect what was asked. For writes there will
 * be 0 or 1 data bytes. Assume 0 for the "best" case.
 */

 if (msg->request & DP_AUX_I2C_READ)
  len += msg->size * 8;

 return len;
}

#define I2C_START_LEN 1
#define I2C_STOP_LEN 1
#define I2C_ADDR_LEN 9 /* ADDRESS + R/W + ACK/NACK */
#define I2C_DATA_LEN 9 /* DATA + ACK/NACK */

/*
 * Calculate the length of the i2c transfer in usec, assuming
 * the i2c bus speed is as specified. Gives the "worst"
 * case estimate, ie. successful while as long as possible.
 * Doesn't account the "MOT" bit, and instead assumes each
 * message includes a START, ADDRESS and STOP. Neither does it
 * account for additional random variables such as clock stretching.
 */

static int drm_dp_i2c_msg_duration(const struct drm_dp_aux_msg *msg,
       int i2c_speed_khz)
{
 /* AUX bitrate is 1MHz, i2c bitrate as specified */
 return DIV_ROUND_UP((I2C_START_LEN + I2C_ADDR_LEN +
        msg->size * I2C_DATA_LEN +
        I2C_STOP_LEN) * 1000, i2c_speed_khz);
}

/*
 * Determine how many retries should be attempted to successfully transfer
 * the specified message, based on the estimated durations of the
 * i2c and AUX transfers.
 */

static int drm_dp_i2c_retry_count(const struct drm_dp_aux_msg *msg,
         int i2c_speed_khz)
{
 int aux_time_us = drm_dp_aux_req_duration(msg) +
  drm_dp_aux_reply_duration(msg);
 int i2c_time_us = drm_dp_i2c_msg_duration(msg, i2c_speed_khz);

 return DIV_ROUND_UP(i2c_time_us, aux_time_us + AUX_RETRY_INTERVAL);
}

/*
 * FIXME currently assumes 10 kHz as some real world devices seem
 * to require it. We should query/set the speed via DPCD if supported.
 */

static int dp_aux_i2c_speed_khz __read_mostly = 10;
module_param_unsafe(dp_aux_i2c_speed_khz, int, 0644);
MODULE_PARM_DESC(dp_aux_i2c_speed_khz,
   "Assumed speed of the i2c bus in kHz, (1-400, default 10)");

/*
 * Transfer a single I2C-over-AUX message and handle various error conditions,
 * retrying the transaction as appropriate.  It is assumed that the
 * &drm_dp_aux.transfer function does not modify anything in the msg other than the
 * reply field.
 *
 * Returns bytes transferred on success, or a negative error code on failure.
 */

static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
{
 unsigned int retry, defer_i2c;
 int ret;
 /*
 * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device
 * is required to retry at least seven times upon receiving AUX_DEFER
 * before giving up the AUX transaction.
 *
 * We also try to account for the i2c bus speed.
 */

 int max_retries = max(7, drm_dp_i2c_retry_count(msg, dp_aux_i2c_speed_khz));

 for (retry = 0, defer_i2c = 0; retry < (max_retries + defer_i2c); retry++) {
  ret = aux->transfer(aux, msg);
  if (ret < 0) {
   if (ret == -EBUSY)
    continue;

   /*
 * While timeouts can be errors, they're usually normal
 * behavior (for instance, when a driver tries to
 * communicate with a non-existent DisplayPort device).
 * Avoid spamming the kernel log with timeout errors.
 */

   if (ret == -ETIMEDOUT)
    drm_dbg_kms_ratelimited(aux->drm_dev, "%s: transaction timed out\n",
       aux->name);
   else
    drm_dbg_kms(aux->drm_dev, "%s: transaction failed: %d\n",
         aux->name, ret);
   return ret;
  }


  switch (msg->reply & DP_AUX_NATIVE_REPLY_MASK) {
  case DP_AUX_NATIVE_REPLY_ACK:
   /*
 * For I2C-over-AUX transactions this isn't enough, we
 * need to check for the I2C ACK reply.
 */

   break;

  case DP_AUX_NATIVE_REPLY_NACK:
   drm_dbg_kms(aux->drm_dev, "%s: native nack (result=%d, size=%zu)\n",
        aux->name, ret, msg->size);
   return -EREMOTEIO;

  case DP_AUX_NATIVE_REPLY_DEFER:
   drm_dbg_kms(aux->drm_dev, "%s: native defer\n", aux->name);
   /*
 * We could check for I2C bit rate capabilities and if
 * available adjust this interval. We could also be
 * more careful with DP-to-legacy adapters where a
 * long legacy cable may force very low I2C bit rates.
 *
 * For now just defer for long enough to hopefully be
 * safe for all use-cases.
 */

   usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100);
   continue;

  default:
   drm_err(aux->drm_dev, "%s: invalid native reply %#04x\n",
    aux->name, msg->reply);
   return -EREMOTEIO;
  }

  switch (msg->reply & DP_AUX_I2C_REPLY_MASK) {
  case DP_AUX_I2C_REPLY_ACK:
   /*
 * Both native ACK and I2C ACK replies received. We
 * can assume the transfer was successful.
 */

   if (ret != msg->size)
    drm_dp_i2c_msg_write_status_update(msg);
   return ret;

  case DP_AUX_I2C_REPLY_NACK:
   drm_dbg_kms(aux->drm_dev, "%s: I2C nack (result=%d, size=%zu)\n",
        aux->name, ret, msg->size);
   aux->i2c_nack_count++;
   return -EREMOTEIO;

  case DP_AUX_I2C_REPLY_DEFER:
   drm_dbg_kms(aux->drm_dev, "%s: I2C defer\n", aux->name);
   /* DP Compliance Test 4.2.2.5 Requirement:
 * Must have at least 7 retries for I2C defers on the
 * transaction to pass this test
 */

   aux->i2c_defer_count++;
   if (defer_i2c < 7)
    defer_i2c++;
   usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100);
   drm_dp_i2c_msg_write_status_update(msg);

   continue;

  default:
   drm_err(aux->drm_dev, "%s: invalid I2C reply %#04x\n",
    aux->name, msg->reply);
   return -EREMOTEIO;
  }
 }

 drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up\n", aux->name);
 return -EREMOTEIO;
}

static void drm_dp_i2c_msg_set_request(struct drm_dp_aux_msg *msg,
           const struct i2c_msg *i2c_msg)
{
 msg->request = (i2c_msg->flags & I2C_M_RD) ?
  DP_AUX_I2C_READ : DP_AUX_I2C_WRITE;
 if (!(i2c_msg->flags & I2C_M_STOP))
  msg->request |= DP_AUX_I2C_MOT;
}

/*
 * Keep retrying drm_dp_i2c_do_msg until all data has been transferred.
 *
 * Returns an error code on failure, or a recommended transfer size on success.
 */

static int drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *orig_msg)
{
 int err, ret = orig_msg->size;
 struct drm_dp_aux_msg msg = *orig_msg;

 while (msg.size > 0) {
  err = drm_dp_i2c_do_msg(aux, &msg);
  if (err <= 0)
   return err == 0 ? -EPROTO : err;

  if (err < msg.size && err < ret) {
   drm_dbg_kms(aux->drm_dev,
        "%s: Partial I2C reply: requested %zu bytes got %d bytes\n",
        aux->name, msg.size, err);
   ret = err;
  }

  msg.size -= err;
  msg.buffer += err;
 }

 return ret;
}

/*
 * Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX
 * packets to be as large as possible. If not, the I2C transactions never
 * succeed. Hence the default is maximum.
 */

static int dp_aux_i2c_transfer_size __read_mostly = DP_AUX_MAX_PAYLOAD_BYTES;
module_param_unsafe(dp_aux_i2c_transfer_size, int, 0644);
MODULE_PARM_DESC(dp_aux_i2c_transfer_size,
   "Number of bytes to transfer in a single I2C over DP AUX CH message, (1-16, default 16)");

static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
      int num)
{
 struct drm_dp_aux *aux = adapter->algo_data;
 unsigned int i, j;
 unsigned transfer_size;
 struct drm_dp_aux_msg msg;
 int err = 0;

 if (aux->powered_down)
  return -EBUSY;

 dp_aux_i2c_transfer_size = clamp(dp_aux_i2c_transfer_size, 1, DP_AUX_MAX_PAYLOAD_BYTES);

 memset(&msg, 0, sizeof(msg));

 for (i = 0; i < num; i++) {
  msg.address = msgs[i].addr;

  if (!aux->no_zero_sized) {
   drm_dp_i2c_msg_set_request(&msg, &msgs[i]);
   /* Send a bare address packet to start the transaction.
 * Zero sized messages specify an address only (bare
 * address) transaction.
 */

   msg.buffer = NULL;
   msg.size = 0;
   err = drm_dp_i2c_do_msg(aux, &msg);
  }

  /*
 * Reset msg.request in case in case it got
 * changed into a WRITE_STATUS_UPDATE.
 */

  drm_dp_i2c_msg_set_request(&msg, &msgs[i]);

  if (err < 0)
   break;
  /* We want each transaction to be as large as possible, but
 * we'll go to smaller sizes if the hardware gives us a
 * short reply.
 */

  transfer_size = dp_aux_i2c_transfer_size;
  for (j = 0; j < msgs[i].len; j += msg.size) {
   msg.buffer = msgs[i].buf + j;
   msg.size = min(transfer_size, msgs[i].len - j);

   if (j + msg.size == msgs[i].len && aux->no_zero_sized)
    msg.request &= ~DP_AUX_I2C_MOT;
   err = drm_dp_i2c_drain_msg(aux, &msg);

   /*
 * Reset msg.request in case in case it got
 * changed into a WRITE_STATUS_UPDATE.
 */

   drm_dp_i2c_msg_set_request(&msg, &msgs[i]);

   if (err < 0)
    break;
   transfer_size = err;
  }
  if (err < 0)
   break;
 }
 if (err >= 0)
  err = num;

 if (!aux->no_zero_sized) {
  /* Send a bare address packet to close out the transaction.
 * Zero sized messages specify an address only (bare
 * address) transaction.
 */

  msg.request &= ~DP_AUX_I2C_MOT;
  msg.buffer = NULL;
  msg.size = 0;
  (void)drm_dp_i2c_do_msg(aux, &msg);
 }
 return err;
}

static const struct i2c_algorithm drm_dp_i2c_algo = {
 .functionality = drm_dp_i2c_functionality,
 .master_xfer = drm_dp_i2c_xfer,
};

static struct drm_dp_aux *i2c_to_aux(struct i2c_adapter *i2c)
{
 return container_of(i2c, struct drm_dp_aux, ddc);
}

static void lock_bus(struct i2c_adapter *i2c, unsigned int flags)
{
 mutex_lock(&i2c_to_aux(i2c)->hw_mutex);
}

static int trylock_bus(struct i2c_adapter *i2c, unsigned int flags)
{
 return mutex_trylock(&i2c_to_aux(i2c)->hw_mutex);
}

static void unlock_bus(struct i2c_adapter *i2c, unsigned int flags)
{
 mutex_unlock(&i2c_to_aux(i2c)->hw_mutex);
}

static const struct i2c_lock_operations drm_dp_i2c_lock_ops = {
 .lock_bus = lock_bus,
 .trylock_bus = trylock_bus,
 .unlock_bus = unlock_bus,
};

static int drm_dp_aux_get_crc(struct drm_dp_aux *aux, u8 *crc)
{
 u8 buf, count;
 int ret;

 ret = drm_dp_dpcd_read_byte(aux, DP_TEST_SINK, &buf);
 if (ret < 0)
  return ret;

 WARN_ON(!(buf & DP_TEST_SINK_START));

 ret = drm_dp_dpcd_read_byte(aux, DP_TEST_SINK_MISC, &buf);
 if (ret < 0)
  return ret;

 count = buf & DP_TEST_COUNT_MASK;
 if (count == aux->crc_count)
  return -EAGAIN; /* No CRC yet */

 aux->crc_count = count;

 /*
 * At DP_TEST_CRC_R_CR, there's 6 bytes containing CRC data, 2 bytes
 * per component (RGB or CrYCb).
 */

 return drm_dp_dpcd_read_data(aux, DP_TEST_CRC_R_CR, crc, 6);
}

static void drm_dp_aux_crc_work(struct work_struct *work)
{
 struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux,
           crc_work);
 struct drm_crtc *crtc;
 u8 crc_bytes[6];
 uint32_t crcs[3];
 int ret;

 if (WARN_ON(!aux->crtc))
  return;

 crtc = aux->crtc;
 while (crtc->crc.opened) {
  drm_crtc_wait_one_vblank(crtc);
  if (!crtc->crc.opened)
   break;

  ret = drm_dp_aux_get_crc(aux, crc_bytes);
  if (ret == -EAGAIN) {
   usleep_range(1000, 2000);
   ret = drm_dp_aux_get_crc(aux, crc_bytes);
  }

  if (ret == -EAGAIN) {
   drm_dbg_kms(aux->drm_dev, "%s: Get CRC failed after retrying: %d\n",
        aux->name, ret);
   continue;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=93 G=94

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.