OpenCloudOS-Kernel/drivers/gpu/drm/drm_dp_helper.c

2901 lines
79 KiB
C
Raw Normal View History

/*
* 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/delay.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include <drm/drm_dp_mst_helper.h>
#include "drm_crtc_helper_internal.h"
/**
* 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);
u8 drm_dp_get_adjust_request_post_cursor(const u8 link_status[DP_LINK_STATUS_SIZE],
unsigned int lane)
{
unsigned int offset = DP_ADJUST_REQUEST_POST_CURSOR2;
u8 value = dp_link_status(link_status, offset);
return (value >> (lane << 1)) & 0x3;
}
EXPORT_SYMBOL(drm_dp_get_adjust_request_post_cursor);
void drm_dp_link_train_clock_recovery_delay(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
unsigned long rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
DP_TRAINING_AUX_RD_MASK;
if (rd_interval > 4)
DRM_DEBUG_KMS("AUX interval %lu, out of range (max 4)\n",
rd_interval);
if (rd_interval == 0 || dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
rd_interval = 100;
else
rd_interval *= 4 * USEC_PER_MSEC;
usleep_range(rd_interval, rd_interval * 2);
}
EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay);
static void __drm_dp_link_train_channel_eq_delay(unsigned long rd_interval)
{
if (rd_interval > 4)
DRM_DEBUG_KMS("AUX interval %lu, out of range (max 4)\n",
rd_interval);
if (rd_interval == 0)
rd_interval = 400;
else
rd_interval *= 4 * USEC_PER_MSEC;
usleep_range(rd_interval, rd_interval * 2);
}
void drm_dp_link_train_channel_eq_delay(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
__drm_dp_link_train_channel_eq_delay(dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
DP_TRAINING_AUX_RD_MASK);
}
EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay);
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 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(interval);
}
EXPORT_SYMBOL(drm_dp_lttpr_link_train_channel_eq_delay);
u8 drm_dp_link_rate_to_bw_code(int link_rate)
{
/* 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)
{
/* 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_DEBUG_DP("%s: 0x%05x AUX %s (ret=%3d) %*ph\n",
aux->name, offset, arrow, ret, min(ret, 20), buffer);
else
DRM_DEBUG_DP("%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;
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
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);
/*
* 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++) {
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
if (ret != 0 && ret != -ETIMEDOUT) {
usleep_range(AUX_RETRY_INTERVAL,
AUX_RETRY_INTERVAL + 100);
}
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
ret = aux->transfer(aux, &msg);
if (ret >= 0) {
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
native_reply = msg.reply & DP_AUX_NATIVE_REPLY_MASK;
if (native_reply == DP_AUX_NATIVE_REPLY_ACK) {
if (ret == size)
goto unlock;
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
ret = -EPROTO;
} else
ret = -EIO;
}
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
/*
* 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_DEBUG_KMS("%s: Too many retries, giving up. First error: %d\n",
aux->name, err);
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
ret = err;
unlock:
mutex_unlock(&aux->hw_mutex);
drm/dp_helper: Retry aux transactions on all errors This is part of a patch series to migrate all of the workarounds for commonly seen behavior from bad sinks in intel_dp_dpcd_read_wake() to drm's DP helper. We cannot rely on sinks NACKing or deferring when they can't receive transactions, nor can we rely on any other sort of consistent error to know when we should stop retrying. As such, we need to just retry unconditionally on errors. We also make sure here to return the error we encountered during the first transaction, since it's possible that retrying the transaction might return a different error then we had originally. This, along with the previous patch, work around a weird bug with the ThinkPad T560's and it's dock. When resuming the laptop, it appears that there's a short period of time where we're unable to complete any aux transactions, as they all immediately timeout. The only machine I'm able to reproduce this on is the T560 as other production Skylake models seem to be fine. The period during which AUX transactions fail appears to be around 22ms long. AFAIK, the dock for the T560 never actually turns off, the only difference is that it's in SST mode at the start of the resume process, so it's unclear as to why it would need so much time to come back up. There's been a discussion on this issue going on for a while on the intel-gfx mailing list about this that has, in addition to including developers from Intel, also had the correspondence of one of the hardware engineers for Intel: http://www.spinics.net/lists/intel-gfx/msg88831.html http://www.spinics.net/lists/intel-gfx/msg88410.html We've already looked into a couple of possible explanations for the problem: - Calling intel_dp_mst_resume() before right fix. intel_runtime_pm_enable_interrupts(). This was the first fix I tried, and while it worked it definitely wasn't the right fix. This worked because DP aux transactions don't actually require interrupts to work: static uint32_t intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq) { struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); struct drm_device *dev = intel_dig_port->base.base.dev; struct drm_i915_private *dev_priv = dev->dev_private; i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg; uint32_t status; bool done; #define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) if (has_aux_irq) done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, msecs_to_jiffies_timeout(10)); else done = wait_for_atomic(C, 10) == 0; if (!done) DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n", has_aux_irq); #undef C return status; } When there's no interrupts enabled, we end up timing out on the wait_event_timeout() call, which causes us to check the DP status register once to see if the transaction was successful or not. Since this adds a 10ms delay to each aux transaction, it ends up adding a long enough delay to the resume process for aux transactions to become functional again. This gave us the illusion that enabling interrupts had something to do with making things work again, and put me on the wrong track for a while. - Interrupts occurring when we try to perform the aux transactions required to put the dock back into MST mode. This isn't the problem, as the only interrupts I've observed that come during this timeout period are from the snd_hda_intel driver, and disabling that driver doesn't appear to change the behavior at all. - Skylake's PSR block causing issues by performing aux transactions while we try to bring the dock out of MST mode. Disabling PSR through i915's command line options doesn't seem to change the behavior either, nor does preventing the DMC firmware from being loaded. Since this investigation went on for about 2 weeks, we decided it would be better for the time being to just workaround this issue by making sure AUX transactions wait a short period of time before retrying. Signed-off-by: Lyude <cpaul@redhat.com> Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1460559513-32280-3-git-send-email-cpaul@redhat.com
2016-04-13 22:58:31 +08:00
return ret;
}
/**
* 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.
*/
ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset,
void *buffer, size_t size)
{
int ret;
/*
* 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.
*/
if (!aux->is_remote) {
ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, DP_DPCD_REV,
buffer, 1);
if (ret != 1)
goto out;
}
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);
out:
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.
*/
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 the number of bytes transferred on success or a negative error
* code on failure.
*/
int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
u8 status[DP_LINK_STATUS_SIZE])
{
return drm_dp_dpcd_read(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) {
ret = drm_dp_dpcd_read(aux,
DP_LANE0_1_STATUS,
link_status,
DP_LINK_STATUS_SIZE);
if (ret < 0)
return ret;
WARN_ON(ret != DP_LINK_STATUS_SIZE);
return 0;
}
ret = drm_dp_dpcd_read(aux,
DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy),
link_status,
DP_LINK_STATUS_SIZE - 1);
if (ret < 0)
return ret;
WARN_ON(ret != DP_LINK_STATUS_SIZE - 1);
/* 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);
static bool is_edid_digital_input_dp(const struct edid *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
* @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 edid *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(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(aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
&auto_test_req, 1) < 1) {
DRM_ERROR("%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(aux, DP_TEST_REQUEST, &link_edid_read, 1) < 1) {
DRM_ERROR("%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_DEBUG_KMS("%s: Source DUT does not support TEST_EDID_READ\n",
aux->name);
return false;
}
if (drm_dp_dpcd_write(aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
&auto_test_req, 1) < 1) {
DRM_ERROR("%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(aux, DP_TEST_EDID_CHECKSUM,
&real_edid_checksum, 1) < 1) {
DRM_ERROR("%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(aux, DP_TEST_RESPONSE, &test_resp, 1) < 1) {
DRM_ERROR("%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);
drm/i915/dp: Extract drm_dp_read_downstream_info() We're going to be doing the same probing process in nouveau for determining downstream DP port capabilities, so let's deduplicate the work by moving i915's code for handling this into a shared helper: drm_dp_read_downstream_info(). Note that when we do this, we also do make some functional changes while we're at it: * We always clear the downstream port info before trying to read it, just to make things easier for the caller * We skip reading downstream port info if the DPCD indicates that we don't support downstream port info * We only read as many bytes as needed for the reported number of downstream ports, no sense in reading the whole thing every time v2: * Fixup logic for calculating the downstream port length to account for the fact that downstream port caps can be either 1 byte or 4 bytes long. We can actually skip fixing the max_clock/max_bpc helpers here since they all check for DP_DETAILED_CAP_INFO_AVAILABLE anyway. * Fix ret code check for drm_dp_dpcd_read v5: * Change name from drm_dp_downstream_read_info() to drm_dp_read_downstream_info() * Also, add "See Also" sections for the various downstream info functions (drm_dp_read_downstream_info(), drm_dp_downstream_max_clock(), drm_dp_downstream_max_bpc()) Reviewed-by: Sean Paul <sean@poorly.run> Signed-off-by: Lyude Paul <lyude@redhat.com> Reviewed-by: Jani Nikula <jani.nikula@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200826182456.322681-14-lyude@redhat.com
2020-08-27 02:24:49 +08:00
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[6];
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(aux, DP_DP13_DPCD_REV, &dpcd_ext,
sizeof(dpcd_ext));
if (ret < 0)
return ret;
if (ret != sizeof(dpcd_ext))
return -EIO;
if (dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) {
DRM_DEBUG_KMS("%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_DEBUG_KMS("%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(aux, DP_DPCD_REV, dpcd, DP_RECEIVER_CAP_SIZE);
if (ret < 0)
return ret;
if (ret != DP_RECEIVER_CAP_SIZE || dpcd[DP_DPCD_REV] == 0)
return -EIO;
ret = drm_dp_read_extended_dpcd_caps(aux, dpcd);
if (ret < 0)
return ret;
DRM_DEBUG_KMS("%s: DPCD: %*ph\n",
aux->name, DP_RECEIVER_CAP_SIZE, dpcd);
return ret;
}
EXPORT_SYMBOL(drm_dp_read_dpcd_caps);
drm/i915/dp: Extract drm_dp_read_downstream_info() We're going to be doing the same probing process in nouveau for determining downstream DP port capabilities, so let's deduplicate the work by moving i915's code for handling this into a shared helper: drm_dp_read_downstream_info(). Note that when we do this, we also do make some functional changes while we're at it: * We always clear the downstream port info before trying to read it, just to make things easier for the caller * We skip reading downstream port info if the DPCD indicates that we don't support downstream port info * We only read as many bytes as needed for the reported number of downstream ports, no sense in reading the whole thing every time v2: * Fixup logic for calculating the downstream port length to account for the fact that downstream port caps can be either 1 byte or 4 bytes long. We can actually skip fixing the max_clock/max_bpc helpers here since they all check for DP_DETAILED_CAP_INFO_AVAILABLE anyway. * Fix ret code check for drm_dp_dpcd_read v5: * Change name from drm_dp_downstream_read_info() to drm_dp_read_downstream_info() * Also, add "See Also" sections for the various downstream info functions (drm_dp_read_downstream_info(), drm_dp_downstream_max_clock(), drm_dp_downstream_max_bpc()) Reviewed-by: Sean Paul <sean@poorly.run> Signed-off-by: Lyude Paul <lyude@redhat.com> Reviewed-by: Jani Nikula <jani.nikula@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200826182456.322681-14-lyude@redhat.com
2020-08-27 02:24:49 +08:00
/**
* 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 ||
!(dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT))
return 0;
len = drm_dp_downstream_port_count(dpcd);
if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE)
len *= 4;
ret = drm_dp_dpcd_read(aux, DP_DOWNSTREAM_PORT_0, downstream_ports, len);
if (ret < 0)
return ret;
if (ret != len)
return -EIO;
DRM_DEBUG_KMS("%s: DPCD DFP: %*ph\n",
aux->name, len, downstream_ports);
drm/i915/dp: Extract drm_dp_read_downstream_info() We're going to be doing the same probing process in nouveau for determining downstream DP port capabilities, so let's deduplicate the work by moving i915's code for handling this into a shared helper: drm_dp_read_downstream_info(). Note that when we do this, we also do make some functional changes while we're at it: * We always clear the downstream port info before trying to read it, just to make things easier for the caller * We skip reading downstream port info if the DPCD indicates that we don't support downstream port info * We only read as many bytes as needed for the reported number of downstream ports, no sense in reading the whole thing every time v2: * Fixup logic for calculating the downstream port length to account for the fact that downstream port caps can be either 1 byte or 4 bytes long. We can actually skip fixing the max_clock/max_bpc helpers here since they all check for DP_DETAILED_CAP_INFO_AVAILABLE anyway. * Fix ret code check for drm_dp_dpcd_read v5: * Change name from drm_dp_downstream_read_info() to drm_dp_read_downstream_info() * Also, add "See Also" sections for the various downstream info functions (drm_dp_read_downstream_info(), drm_dp_downstream_max_clock(), drm_dp_downstream_max_bpc()) Reviewed-by: Sean Paul <sean@poorly.run> Signed-off-by: Lyude Paul <lyude@redhat.com> Reviewed-by: Jani Nikula <jani.nikula@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200826182456.322681-14-lyude@redhat.com
2020-08-27 02:24:49 +08:00
return 0;
drm/i915/dp: Extract drm_dp_read_downstream_info() We're going to be doing the same probing process in nouveau for determining downstream DP port capabilities, so let's deduplicate the work by moving i915's code for handling this into a shared helper: drm_dp_read_downstream_info(). Note that when we do this, we also do make some functional changes while we're at it: * We always clear the downstream port info before trying to read it, just to make things easier for the caller * We skip reading downstream port info if the DPCD indicates that we don't support downstream port info * We only read as many bytes as needed for the reported number of downstream ports, no sense in reading the whole thing every time v2: * Fixup logic for calculating the downstream port length to account for the fact that downstream port caps can be either 1 byte or 4 bytes long. We can actually skip fixing the max_clock/max_bpc helpers here since they all check for DP_DETAILED_CAP_INFO_AVAILABLE anyway. * Fix ret code check for drm_dp_dpcd_read v5: * Change name from drm_dp_downstream_read_info() to drm_dp_read_downstream_info() * Also, add "See Also" sections for the various downstream info functions (drm_dp_read_downstream_info(), drm_dp_downstream_max_clock(), drm_dp_downstream_max_bpc()) Reviewed-by: Sean Paul <sean@poorly.run> Signed-off-by: Lyude Paul <lyude@redhat.com> Reviewed-by: Jani Nikula <jani.nikula@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200826182456.322681-14-lyude@redhat.com
2020-08-27 02:24:49 +08:00
}
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
* @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 edid *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(edid))
return 0;
/*
* It's left up to the driver to check the
* DP dual mode adapter's max TMDS clock.
*
* Unfortunatley 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
* @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 edid *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(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
* @edid: EDID
drm/i915/dp: Extract drm_dp_read_downstream_info() We're going to be doing the same probing process in nouveau for determining downstream DP port capabilities, so let's deduplicate the work by moving i915's code for handling this into a shared helper: drm_dp_read_downstream_info(). Note that when we do this, we also do make some functional changes while we're at it: * We always clear the downstream port info before trying to read it, just to make things easier for the caller * We skip reading downstream port info if the DPCD indicates that we don't support downstream port info * We only read as many bytes as needed for the reported number of downstream ports, no sense in reading the whole thing every time v2: * Fixup logic for calculating the downstream port length to account for the fact that downstream port caps can be either 1 byte or 4 bytes long. We can actually skip fixing the max_clock/max_bpc helpers here since they all check for DP_DETAILED_CAP_INFO_AVAILABLE anyway. * Fix ret code check for drm_dp_dpcd_read v5: * Change name from drm_dp_downstream_read_info() to drm_dp_read_downstream_info() * Also, add "See Also" sections for the various downstream info functions (drm_dp_read_downstream_info(), drm_dp_downstream_max_clock(), drm_dp_downstream_max_bpc()) Reviewed-by: Sean Paul <sean@poorly.run> Signed-off-by: Lyude Paul <lyude@redhat.com> Reviewed-by: Jani Nikula <jani.nikula@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200826182456.322681-14-lyude@redhat.com
2020-08-27 02:24:49 +08:00
*
* 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 edid *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(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_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(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
* @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 edid *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",
branch_device ? "yes" : "no");
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(aux, DP_BRANCH_HW_REV, &rev[0], 1);
if (len > 0)
seq_printf(m, "\t\tHW: %d.%d\n",
(rev[0] & 0xf0) >> 4, rev[0] & 0xf);
len = drm_dp_dpcd_read(aux, DP_BRANCH_SW_REV, rev, 2);
if (len > 0)
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, 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, edid);
if (clk > 0)
seq_printf(m, "\t\tMin TMDS clock: %d kHz\n", clk);
bpc = drm_dp_downstream_max_bpc(dpcd, port_cap, edid);
if (bpc > 0)
seq_printf(m, "\t\tMax bpc: %d\n", bpc);
}
}
EXPORT_SYMBOL(drm_dp_downstream_debug);
drm: report dp downstream port type as a subconnector property Currently, downstream port type is only reported in debugfs. This information should be considered important since it reflects the actual physical connector type. Some userspace (e.g. window compositors) may want to show this info to a user. The 'subconnector' property is already utilized for DVI-I and TV-out for reporting connector subtype. The initial motivation for this feature came from i2c test [1]. It is supposed to be skipped on VGA connectors, but it cannot detect VGA over DP and fails instead. v2: - Ville: utilized drm_dp_is_branch() - Ville: implement DP 1.0 downstream type info - Replaced create_dp_properties with add_dp_subconnector_property - Added dp_set_subconnector_property helper v4: - Ville: add DP1.0 best assumption about subconnector - Ville: assume DVI is DVI-D - Ville: reuse Writeback enum value for Virtual subconnector - Renamed #defines: HDMI -> HDMIA, DP -> DisplayPort v5: rebase v6: - Jani Nikula: renamed a function name - Jani Nikula: addressed the issues with documentation [1]: https://bugs.freedesktop.org/show_bug.cgi?id=104097 Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: intel-gfx@lists.freedesktop.org Signed-off-by: Jeevan B <jeevan.b@intel.com> Signed-off-by: Oleg Vasilev <oleg.vasilev@intel.com> Reviewed-by: Emil Velikov <emil.velikov@collabora.com> Signed-off-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/1587732655-17544-1-git-send-email-jeevan.b@intel.com
2020-04-24 20:50:51 +08:00
/**
* drm_dp_subconnector_type() - get DP branch device type
* @dpcd: DisplayPort configuration data
* @port_cap: port capabilities
drm: report dp downstream port type as a subconnector property Currently, downstream port type is only reported in debugfs. This information should be considered important since it reflects the actual physical connector type. Some userspace (e.g. window compositors) may want to show this info to a user. The 'subconnector' property is already utilized for DVI-I and TV-out for reporting connector subtype. The initial motivation for this feature came from i2c test [1]. It is supposed to be skipped on VGA connectors, but it cannot detect VGA over DP and fails instead. v2: - Ville: utilized drm_dp_is_branch() - Ville: implement DP 1.0 downstream type info - Replaced create_dp_properties with add_dp_subconnector_property - Added dp_set_subconnector_property helper v4: - Ville: add DP1.0 best assumption about subconnector - Ville: assume DVI is DVI-D - Ville: reuse Writeback enum value for Virtual subconnector - Renamed #defines: HDMI -> HDMIA, DP -> DisplayPort v5: rebase v6: - Jani Nikula: renamed a function name - Jani Nikula: addressed the issues with documentation [1]: https://bugs.freedesktop.org/show_bug.cgi?id=104097 Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: intel-gfx@lists.freedesktop.org Signed-off-by: Jeevan B <jeevan.b@intel.com> Signed-off-by: Oleg Vasilev <oleg.vasilev@intel.com> Reviewed-by: Emil Velikov <emil.velikov@collabora.com> Signed-off-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/1587732655-17544-1-git-send-email-jeevan.b@intel.com
2020-04-24 20:50:51 +08:00
*/
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_mode_set_dp_subconnector_property - set subconnector for DP connector
* @connector: connector to set property on
* @status: connector status
* @dpcd: DisplayPort configuration data
* @port_cap: port capabilities
drm: report dp downstream port type as a subconnector property Currently, downstream port type is only reported in debugfs. This information should be considered important since it reflects the actual physical connector type. Some userspace (e.g. window compositors) may want to show this info to a user. The 'subconnector' property is already utilized for DVI-I and TV-out for reporting connector subtype. The initial motivation for this feature came from i2c test [1]. It is supposed to be skipped on VGA connectors, but it cannot detect VGA over DP and fails instead. v2: - Ville: utilized drm_dp_is_branch() - Ville: implement DP 1.0 downstream type info - Replaced create_dp_properties with add_dp_subconnector_property - Added dp_set_subconnector_property helper v4: - Ville: add DP1.0 best assumption about subconnector - Ville: assume DVI is DVI-D - Ville: reuse Writeback enum value for Virtual subconnector - Renamed #defines: HDMI -> HDMIA, DP -> DisplayPort v5: rebase v6: - Jani Nikula: renamed a function name - Jani Nikula: addressed the issues with documentation [1]: https://bugs.freedesktop.org/show_bug.cgi?id=104097 Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: intel-gfx@lists.freedesktop.org Signed-off-by: Jeevan B <jeevan.b@intel.com> Signed-off-by: Oleg Vasilev <oleg.vasilev@intel.com> Reviewed-by: Emil Velikov <emil.velikov@collabora.com> Signed-off-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/1587732655-17544-1-git-send-email-jeevan.b@intel.com
2020-04-24 20:50:51 +08:00
*
* 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, 0, 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_readb(aux, DP_SINK_COUNT, &count);
if (ret < 0)
return ret;
if (ret != 1)
return -EIO;
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;
}
}
drm/dp: Adjust i2c-over-aux retry count based on message size and i2c bus speed Calculate the number of retries we should do for each i2c-over-aux message based on the time it takes to perform the i2c transfer vs. the aux transfer. We assume the shortest possible length for the aux transfer, and the longest possible (exluding clock stretching) for the i2c transfer. The DP spec has some examples on how to calculate this, but we don't calculate things quite the same way. The spec doesn't account for the retry interval (assumes immediate retry on defer), and doesn't assume the best/worst case behaviour as we do. Note that currently we assume 10 kHz speed for the i2c bus. Some real world devices (eg. some Apple DP->VGA dongle) fails with less than 16 retries. and that would correspond to something close to 15 kHz (with our method of calculating things) But let's just go for 10 kHz to be on the safe side. Ideally we should query/set the i2c bus speed via DPCD but for now this should at leaast remove the regression from the 1->16 byte trasnfer size change. And of course if the sink completes the transfer quicker this shouldn't slow things down since we don't change the interval between retries. I did a few experiments with a DP->DVI dongle I have that allows you to change the i2c bus speed. Here are the results of me changing the actual bus speed and the assumed bus speed and seeing when we start to fail the operation: actual i2c khz assumed i2c khz max retries 1 1 ok -> 2 fail 211 ok -> 106 fail 5 8 ok -> 9 fail 27 ok -> 24 fail 10 17 ok -> 18 fail 13 ok -> 12 fail 100 210 ok -> 211 fail 2 ok -> 1 fail So based on that we have a fairly decent safety margin baked into the formula to calculate the max number of retries. Fixes a regression with some DP dongles from: commit 1d002fa720738bcd0bddb9178e9ea0773288e1dd Author: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Date: Tue Feb 10 18:38:08 2015 +0000 drm/dp: Use large transactions for I2C over AUX v2: Use best case for AUX and worst case for i2c (Simon Farnsworth) Add a define our AUX retry interval and account for it v3: Make everything usecs to avoid confusion about units (Daniel) Add a comment reminding people about the AUX bitrate (Daniel) Use DIV_ROUND_UP() since we're after the "worst" case for i2c Cc: Simon Farnsworth <simon.farnsworth@onelan.com> Cc: moosotc@gmail.com Tested-by: moosotc@gmail.com Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=91451 Reviewed-by: Simon Farnsworth <simon.farnsworth@onelan.com> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Jani Nikula <jani.nikula@intel.com>
2015-09-02 01:12:54 +08:00
#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 the "worst"
* case estimate, ie. successful while as long as possible.
* Doesn't account the 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);
}
/*
* Deterine 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.
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
*
* 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)
{
drm: Fix for DP CTS test 4.2.2.5 - I2C DEFER handling For test 4.2.2.5 to pass per the Link CTS Core 1.2 rev1.1 spec, the source device must attempt at least 7 times to read the EDID when it receives an I2C defer. The normal DRM code makes only 7 retries, regardless of whether or not the response is a native defer or an I2C defer. Test 4.2.2.5 fails since there are native defers interspersed with the I2C defers which results in less than 7 EDID read attempts. The solution is to add the numer of defers to the retry counter when an I2C DEFER is returned such that another read attempt will be made. This situation should normally only occur in compliance testing, however, as a worse case real-world scenario, it would result in 13 attempts ( 6 native defers, 7 I2C defers) for a single transaction to complete. The net result is a slightly slower response to an EDID read that shouldn't significantly impact overall performance. V2: - Added a check on the number of I2C Defers to limit the number of times that the retries variable will be decremented. This is to address review feedback regarding possible infinite loops from misbehaving sink devices. V3: - Fixed the limit value to 7 instead of 8 to get the correct retry count. - Combined the increment of the defer count into the if-statement V4: - Removed i915 tag from subject as the patch is not i915-specific V5: - Updated the for-loop to add the number of i2c defers to the retry counter such that the correct number of retry attempts will be made Signed-off-by: Todd Previte <tprevite@gmail.com> Cc: dri-devel@lists.freedesktop.org Reviewed-by: Paulo Zanoni <paulo.r.zanoni@intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-04-18 15:04:18 +08:00
unsigned int retry, defer_i2c;
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
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.
drm/dp: Adjust i2c-over-aux retry count based on message size and i2c bus speed Calculate the number of retries we should do for each i2c-over-aux message based on the time it takes to perform the i2c transfer vs. the aux transfer. We assume the shortest possible length for the aux transfer, and the longest possible (exluding clock stretching) for the i2c transfer. The DP spec has some examples on how to calculate this, but we don't calculate things quite the same way. The spec doesn't account for the retry interval (assumes immediate retry on defer), and doesn't assume the best/worst case behaviour as we do. Note that currently we assume 10 kHz speed for the i2c bus. Some real world devices (eg. some Apple DP->VGA dongle) fails with less than 16 retries. and that would correspond to something close to 15 kHz (with our method of calculating things) But let's just go for 10 kHz to be on the safe side. Ideally we should query/set the i2c bus speed via DPCD but for now this should at leaast remove the regression from the 1->16 byte trasnfer size change. And of course if the sink completes the transfer quicker this shouldn't slow things down since we don't change the interval between retries. I did a few experiments with a DP->DVI dongle I have that allows you to change the i2c bus speed. Here are the results of me changing the actual bus speed and the assumed bus speed and seeing when we start to fail the operation: actual i2c khz assumed i2c khz max retries 1 1 ok -> 2 fail 211 ok -> 106 fail 5 8 ok -> 9 fail 27 ok -> 24 fail 10 17 ok -> 18 fail 13 ok -> 12 fail 100 210 ok -> 211 fail 2 ok -> 1 fail So based on that we have a fairly decent safety margin baked into the formula to calculate the max number of retries. Fixes a regression with some DP dongles from: commit 1d002fa720738bcd0bddb9178e9ea0773288e1dd Author: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Date: Tue Feb 10 18:38:08 2015 +0000 drm/dp: Use large transactions for I2C over AUX v2: Use best case for AUX and worst case for i2c (Simon Farnsworth) Add a define our AUX retry interval and account for it v3: Make everything usecs to avoid confusion about units (Daniel) Add a comment reminding people about the AUX bitrate (Daniel) Use DIV_ROUND_UP() since we're after the "worst" case for i2c Cc: Simon Farnsworth <simon.farnsworth@onelan.com> Cc: moosotc@gmail.com Tested-by: moosotc@gmail.com Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=91451 Reviewed-by: Simon Farnsworth <simon.farnsworth@onelan.com> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Jani Nikula <jani.nikula@intel.com>
2015-09-02 01:12:54 +08:00
*
* 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));
drm/dp: Adjust i2c-over-aux retry count based on message size and i2c bus speed Calculate the number of retries we should do for each i2c-over-aux message based on the time it takes to perform the i2c transfer vs. the aux transfer. We assume the shortest possible length for the aux transfer, and the longest possible (exluding clock stretching) for the i2c transfer. The DP spec has some examples on how to calculate this, but we don't calculate things quite the same way. The spec doesn't account for the retry interval (assumes immediate retry on defer), and doesn't assume the best/worst case behaviour as we do. Note that currently we assume 10 kHz speed for the i2c bus. Some real world devices (eg. some Apple DP->VGA dongle) fails with less than 16 retries. and that would correspond to something close to 15 kHz (with our method of calculating things) But let's just go for 10 kHz to be on the safe side. Ideally we should query/set the i2c bus speed via DPCD but for now this should at leaast remove the regression from the 1->16 byte trasnfer size change. And of course if the sink completes the transfer quicker this shouldn't slow things down since we don't change the interval between retries. I did a few experiments with a DP->DVI dongle I have that allows you to change the i2c bus speed. Here are the results of me changing the actual bus speed and the assumed bus speed and seeing when we start to fail the operation: actual i2c khz assumed i2c khz max retries 1 1 ok -> 2 fail 211 ok -> 106 fail 5 8 ok -> 9 fail 27 ok -> 24 fail 10 17 ok -> 18 fail 13 ok -> 12 fail 100 210 ok -> 211 fail 2 ok -> 1 fail So based on that we have a fairly decent safety margin baked into the formula to calculate the max number of retries. Fixes a regression with some DP dongles from: commit 1d002fa720738bcd0bddb9178e9ea0773288e1dd Author: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Date: Tue Feb 10 18:38:08 2015 +0000 drm/dp: Use large transactions for I2C over AUX v2: Use best case for AUX and worst case for i2c (Simon Farnsworth) Add a define our AUX retry interval and account for it v3: Make everything usecs to avoid confusion about units (Daniel) Add a comment reminding people about the AUX bitrate (Daniel) Use DIV_ROUND_UP() since we're after the "worst" case for i2c Cc: Simon Farnsworth <simon.farnsworth@onelan.com> Cc: moosotc@gmail.com Tested-by: moosotc@gmail.com Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=91451 Reviewed-by: Simon Farnsworth <simon.farnsworth@onelan.com> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Jani Nikula <jani.nikula@intel.com>
2015-09-02 01:12:54 +08:00
for (retry = 0, defer_i2c = 0; retry < (max_retries + defer_i2c); retry++) {
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
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-existant DisplayPort device).
* Avoid spamming the kernel log with timeout errors.
*/
if (ret == -ETIMEDOUT)
DRM_DEBUG_KMS_RATELIMITED("%s: transaction timed out\n",
aux->name);
else
DRM_DEBUG_KMS("%s: transaction failed: %d\n",
aux->name, ret);
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
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_DEBUG_KMS("%s: native nack (result=%d, size=%zu)\n",
aux->name, ret, msg->size);
return -EREMOTEIO;
case DP_AUX_NATIVE_REPLY_DEFER:
DRM_DEBUG_KMS("%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_ERROR("%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);
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
return ret;
case DP_AUX_I2C_REPLY_NACK:
DRM_DEBUG_KMS("%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_DEBUG_KMS("%s: I2C defer\n", aux->name);
drm: Fix for DP CTS test 4.2.2.5 - I2C DEFER handling For test 4.2.2.5 to pass per the Link CTS Core 1.2 rev1.1 spec, the source device must attempt at least 7 times to read the EDID when it receives an I2C defer. The normal DRM code makes only 7 retries, regardless of whether or not the response is a native defer or an I2C defer. Test 4.2.2.5 fails since there are native defers interspersed with the I2C defers which results in less than 7 EDID read attempts. The solution is to add the numer of defers to the retry counter when an I2C DEFER is returned such that another read attempt will be made. This situation should normally only occur in compliance testing, however, as a worse case real-world scenario, it would result in 13 attempts ( 6 native defers, 7 I2C defers) for a single transaction to complete. The net result is a slightly slower response to an EDID read that shouldn't significantly impact overall performance. V2: - Added a check on the number of I2C Defers to limit the number of times that the retries variable will be decremented. This is to address review feedback regarding possible infinite loops from misbehaving sink devices. V3: - Fixed the limit value to 7 instead of 8 to get the correct retry count. - Combined the increment of the defer count into the if-statement V4: - Removed i915 tag from subject as the patch is not i915-specific V5: - Updated the for-loop to add the number of i2c defers to the retry counter such that the correct number of retry attempts will be made Signed-off-by: Todd Previte <tprevite@gmail.com> Cc: dri-devel@lists.freedesktop.org Reviewed-by: Paulo Zanoni <paulo.r.zanoni@intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-04-18 15:04:18 +08:00
/* 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++;
drm: Fix for DP CTS test 4.2.2.5 - I2C DEFER handling For test 4.2.2.5 to pass per the Link CTS Core 1.2 rev1.1 spec, the source device must attempt at least 7 times to read the EDID when it receives an I2C defer. The normal DRM code makes only 7 retries, regardless of whether or not the response is a native defer or an I2C defer. Test 4.2.2.5 fails since there are native defers interspersed with the I2C defers which results in less than 7 EDID read attempts. The solution is to add the numer of defers to the retry counter when an I2C DEFER is returned such that another read attempt will be made. This situation should normally only occur in compliance testing, however, as a worse case real-world scenario, it would result in 13 attempts ( 6 native defers, 7 I2C defers) for a single transaction to complete. The net result is a slightly slower response to an EDID read that shouldn't significantly impact overall performance. V2: - Added a check on the number of I2C Defers to limit the number of times that the retries variable will be decremented. This is to address review feedback regarding possible infinite loops from misbehaving sink devices. V3: - Fixed the limit value to 7 instead of 8 to get the correct retry count. - Combined the increment of the defer count into the if-statement V4: - Removed i915 tag from subject as the patch is not i915-specific V5: - Updated the for-loop to add the number of i2c defers to the retry counter such that the correct number of retry attempts will be made Signed-off-by: Todd Previte <tprevite@gmail.com> Cc: dri-devel@lists.freedesktop.org Reviewed-by: Paulo Zanoni <paulo.r.zanoni@intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-04-18 15:04:18 +08:00
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_ERROR("%s: invalid I2C reply %#04x\n",
aux->name, msg->reply);
return -EREMOTEIO;
}
}
DRM_DEBUG_KMS("%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;
}
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
/*
* 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_DEBUG_KMS("%s: Partial I2C reply: requested %zu bytes got %d bytes\n",
aux->name, msg.size, err);
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
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;
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
unsigned transfer_size;
struct drm_dp_aux_msg msg;
int err = 0;
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
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;
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;
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
/* 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.
*/
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
transfer_size = dp_aux_i2c_transfer_size;
for (j = 0; j < msgs[i].len; j += msg.size) {
msg.buffer = msgs[i].buf + j;
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
msg.size = min(transfer_size, msgs[i].len - j);
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
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;
drm/dp: Use large transactions for I2C over AUX Older DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in their I2C over AUX implementation (fixed in newer revisions). They work fine with Windows, but fail with Linux. It turns out that they cannot keep an I2C transaction open unless the previous read was 16 bytes; shorter reads can only be followed by a zero byte transfer ending the I2C transaction. Copy Windows's behaviour, and read 16 bytes at a time. If we get a short reply, assume that there's a hardware bottleneck, and shrink our read size to match. For this purpose, use the algorithm in the DisplayPort 1.2 spec, in the hopes that it'll be closest to what Windows does. Also provide an unsafe module parameter for testing smaller transfer sizes, in case there are sinks out there that cannot work with Windows. Note also that despite the previous comment in drm_dp_i2c_xfer, this speeds up native DP EDID reads; Ville Syrjälä <ville.syrjala@linux.intel.com> found the following changes in his testing: Device under test: old -> with this patch DP->DVI (OUI 001cf8): 40ms -> 35ms DP->VGA (OUI 0022b9): 45ms -> 38ms Zotac DP->2xHDMI: 25ms -> 4ms Asus PB278 monitor: 22ms -> 3ms A back of the envelope calculation shows that peak theoretical transfer rate for 1 byte reads is around 60 kbit/s; with 16 byte reads, this increases to around 500 kbit/s, which explains the increase in speed. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=55228 Tested-by: Aidan Marks <aidanamarks@gmail.com> (v3) Signed-off-by: Simon Farnsworth <simon.farnsworth@onelan.co.uk> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2015-02-11 02:38:08 +08:00
transfer_size = err;
}
if (err < 0)
break;
}
if (err >= 0)
err = num;
/* 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_readb(aux, DP_TEST_SINK, &buf);
if (ret < 0)
return ret;
WARN_ON(!(buf & DP_TEST_SINK_START));
ret = drm_dp_dpcd_readb(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).
*/
ret = drm_dp_dpcd_read(aux, DP_TEST_CRC_R_CR, crc, 6);
if (ret < 0)
return ret;
return 0;
}
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_DEBUG_KMS("%s: Get CRC failed after retrying: %d\n",
aux->name, ret);
continue;
} else if (ret) {
DRM_DEBUG_KMS("%s: Failed to get a CRC: %d\n",
aux->name, ret);
continue;
}
crcs[0] = crc_bytes[0] | crc_bytes[1] << 8;
crcs[1] = crc_bytes[2] | crc_bytes[3] << 8;
crcs[2] = crc_bytes[4] | crc_bytes[5] << 8;
drm_crtc_add_crc_entry(crtc, false, 0, crcs);
}
}
/**
* drm_dp_remote_aux_init() - minimally initialise a remote aux channel
* @aux: DisplayPort AUX channel
*
* Used for remote aux channel in general. Merely initialize the crc work
* struct.
*/
void drm_dp_remote_aux_init(struct drm_dp_aux *aux)
{
INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work);
}
EXPORT_SYMBOL(drm_dp_remote_aux_init);
/**
* drm_dp_aux_init() - minimally initialise an aux channel
* @aux: DisplayPort AUX channel
*
* If you need to use the drm_dp_aux's i2c adapter prior to registering it
* with the outside world, call drm_dp_aux_init() first. You must still
* call drm_dp_aux_register() once the connector has been registered to
* allow userspace access to the auxiliary DP channel.
*/
void drm_dp_aux_init(struct drm_dp_aux *aux)
{
mutex_init(&aux->hw_mutex);
mutex_init(&aux->cec.lock);
INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work);
aux->ddc.algo = &drm_dp_i2c_algo;
aux->ddc.algo_data = aux;
aux->ddc.retries = 3;
aux->ddc.lock_ops = &drm_dp_i2c_lock_ops;
}
EXPORT_SYMBOL(drm_dp_aux_init);
/**
* drm_dp_aux_register() - initialise and register aux channel
* @aux: DisplayPort AUX channel
*
* Automatically calls drm_dp_aux_init() if this hasn't been done yet.
* This should only be called when the underlying &struct drm_connector is
* initialiazed already. Therefore the best place to call this is from
* &drm_connector_funcs.late_register. Not that drivers which don't follow this
* will Oops when CONFIG_DRM_DP_AUX_CHARDEV is enabled.
*
* Drivers which need to use the aux channel before that point (e.g. at driver
* load time, before drm_dev_register() has been called) need to call
* drm_dp_aux_init().
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_aux_register(struct drm_dp_aux *aux)
{
int ret;
if (!aux->ddc.algo)
drm_dp_aux_init(aux);
aux->ddc.class = I2C_CLASS_DDC;
aux->ddc.owner = THIS_MODULE;
aux->ddc.dev.parent = aux->dev;
strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(aux->dev),
sizeof(aux->ddc.name));
drm/dp: Add a drm_aux-dev module for reading/writing dpcd registers. This module is heavily based on i2c-dev. Once loaded, it provides one dev node per DP AUX channel, named drm_dp_auxN, where N is an integer. It's possible to know which connector owns this aux channel by looking at the respective sysfs /sys/class/drm_aux_dev/drm_dp_auxN/connector, if the connector device pointer was correctly set in the aux helper struct. Two main operations are provided on the registers read and write. The address of the register to be read or written is given using lseek. The seek position is updated upon read or write. v2: - lseek is used to select the register to read/write - read/write are used instead of ioctl - no blocking_notifier is used, just a direct callback v3: - use drm_dp_aux_dev prefix for public functions - chardev is named drm_dp_auxN - read/write don't allocate a buffer anymore, and transfer up to 16 bytes a time - remove notifier list from the implementation - option on menuconfig is now a boolean - add inline stub functions to avoid breakage when this option is disabled v4: - fix build system changes - actually disable this module when not selected. v5: - Use kref to avoid device closing while still in use - Don't use list, use an idr for storing aux_dev - Remove "connector" attribute - set aux.dev to the connector drm_connector device, instead of drm_device v6: - Use atomic_t for usage count - Use a mutex instead of spinlock for idr lock - Destroy chardev immediately on unregister - other minor suggestions from Ville v7: - style fixes - error handling fixes v8: - more error handling fixes v9: - remove module_init and module_exit, and add drm_dp_aux_dev_init/exit to drm_kms_helper_init/exit. Signed-off-by: Rafael Antognolli <rafael.antognolli@intel.com> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1453417821-2811-3-git-send-email-rafael.antognolli@intel.com
2016-01-22 07:10:19 +08:00
ret = drm_dp_aux_register_devnode(aux);
if (ret)
return ret;
ret = i2c_add_adapter(&aux->ddc);
if (ret) {
drm_dp_aux_unregister_devnode(aux);
return ret;
}
return 0;
}
EXPORT_SYMBOL(drm_dp_aux_register);
/**
* drm_dp_aux_unregister() - unregister an AUX adapter
* @aux: DisplayPort AUX channel
*/
void drm_dp_aux_unregister(struct drm_dp_aux *aux)
{
drm/dp: Add a drm_aux-dev module for reading/writing dpcd registers. This module is heavily based on i2c-dev. Once loaded, it provides one dev node per DP AUX channel, named drm_dp_auxN, where N is an integer. It's possible to know which connector owns this aux channel by looking at the respective sysfs /sys/class/drm_aux_dev/drm_dp_auxN/connector, if the connector device pointer was correctly set in the aux helper struct. Two main operations are provided on the registers read and write. The address of the register to be read or written is given using lseek. The seek position is updated upon read or write. v2: - lseek is used to select the register to read/write - read/write are used instead of ioctl - no blocking_notifier is used, just a direct callback v3: - use drm_dp_aux_dev prefix for public functions - chardev is named drm_dp_auxN - read/write don't allocate a buffer anymore, and transfer up to 16 bytes a time - remove notifier list from the implementation - option on menuconfig is now a boolean - add inline stub functions to avoid breakage when this option is disabled v4: - fix build system changes - actually disable this module when not selected. v5: - Use kref to avoid device closing while still in use - Don't use list, use an idr for storing aux_dev - Remove "connector" attribute - set aux.dev to the connector drm_connector device, instead of drm_device v6: - Use atomic_t for usage count - Use a mutex instead of spinlock for idr lock - Destroy chardev immediately on unregister - other minor suggestions from Ville v7: - style fixes - error handling fixes v8: - more error handling fixes v9: - remove module_init and module_exit, and add drm_dp_aux_dev_init/exit to drm_kms_helper_init/exit. Signed-off-by: Rafael Antognolli <rafael.antognolli@intel.com> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/1453417821-2811-3-git-send-email-rafael.antognolli@intel.com
2016-01-22 07:10:19 +08:00
drm_dp_aux_unregister_devnode(aux);
i2c_del_adapter(&aux->ddc);
}
EXPORT_SYMBOL(drm_dp_aux_unregister);
#define PSR_SETUP_TIME(x) [DP_PSR_SETUP_TIME_ ## x >> DP_PSR_SETUP_TIME_SHIFT] = (x)
/**
* drm_dp_psr_setup_time() - PSR setup in time usec
* @psr_cap: PSR capabilities from DPCD
*
* Returns:
* PSR setup time for the panel in microseconds, negative
* error code on failure.
*/
int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE])
{
static const u16 psr_setup_time_us[] = {
PSR_SETUP_TIME(330),
PSR_SETUP_TIME(275),
PSR_SETUP_TIME(220),
PSR_SETUP_TIME(165),
PSR_SETUP_TIME(110),
PSR_SETUP_TIME(55),
PSR_SETUP_TIME(0),
};
int i;
i = (psr_cap[1] & DP_PSR_SETUP_TIME_MASK) >> DP_PSR_SETUP_TIME_SHIFT;
if (i >= ARRAY_SIZE(psr_setup_time_us))
return -EINVAL;
return psr_setup_time_us[i];
}
EXPORT_SYMBOL(drm_dp_psr_setup_time);
#undef PSR_SETUP_TIME
/**
* drm_dp_start_crc() - start capture of frame CRCs
* @aux: DisplayPort AUX channel
* @crtc: CRTC displaying the frames whose CRCs are to be captured
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc)
{
u8 buf;
int ret;
ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf);
if (ret < 0)
return ret;
ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf | DP_TEST_SINK_START);
if (ret < 0)
return ret;
aux->crc_count = 0;
aux->crtc = crtc;
schedule_work(&aux->crc_work);
return 0;
}
EXPORT_SYMBOL(drm_dp_start_crc);
/**
* drm_dp_stop_crc() - stop capture of frame CRCs
* @aux: DisplayPort AUX channel
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_stop_crc(struct drm_dp_aux *aux)
{
u8 buf;
int ret;
ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf);
if (ret < 0)
return ret;
ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf & ~DP_TEST_SINK_START);
if (ret < 0)
return ret;
flush_work(&aux->crc_work);
aux->crtc = NULL;
return 0;
}
EXPORT_SYMBOL(drm_dp_stop_crc);
struct dpcd_quirk {
u8 oui[3];
u8 device_id[6];
bool is_branch;
u32 quirks;
};
#define OUI(first, second, third) { (first), (second), (third) }
#define DEVICE_ID(first, second, third, fourth, fifth, sixth) \
{ (first), (second), (third), (fourth), (fifth), (sixth) }
#define DEVICE_ID_ANY DEVICE_ID(0, 0, 0, 0, 0, 0)
static const struct dpcd_quirk dpcd_quirk_list[] = {
/* Analogix 7737 needs reduced M and N at HBR2 link rates */
{ OUI(0x00, 0x22, 0xb9), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_CONSTANT_N) },
/* LG LP140WF6-SPM1 eDP panel */
{ OUI(0x00, 0x22, 0xb9), DEVICE_ID('s', 'i', 'v', 'a', 'r', 'T'), false, BIT(DP_DPCD_QUIRK_CONSTANT_N) },
/* Apple panels need some additional handling to support PSR */
{ OUI(0x00, 0x10, 0xfa), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_NO_PSR) },
/* CH7511 seems to leave SINK_COUNT zeroed */
{ OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'), false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) },
/* Synaptics DP1.4 MST hubs can support DSC without virtual DPCD */
{ OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) },
/* Apple MacBookPro 2017 15 inch eDP Retina panel reports too low DP_MAX_LINK_RATE */
{ OUI(0x00, 0x10, 0xfa), DEVICE_ID(101, 68, 21, 101, 98, 97), false, BIT(DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS) },
};
#undef OUI
/*
* Get a bit mask of DPCD quirks for the sink/branch device identified by
* ident. The quirk data is shared but it's up to the drivers to act on the
* data.
*
* For now, only the OUI (first three bytes) is used, but this may be extended
* to device identification string and hardware/firmware revisions later.
*/
static u32
drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch)
{
const struct dpcd_quirk *quirk;
u32 quirks = 0;
int i;
u8 any_device[] = DEVICE_ID_ANY;
for (i = 0; i < ARRAY_SIZE(dpcd_quirk_list); i++) {
quirk = &dpcd_quirk_list[i];
if (quirk->is_branch != is_branch)
continue;
if (memcmp(quirk->oui, ident->oui, sizeof(ident->oui)) != 0)
continue;
if (memcmp(quirk->device_id, any_device, sizeof(any_device)) != 0 &&
memcmp(quirk->device_id, ident->device_id, sizeof(ident->device_id)) != 0)
continue;
quirks |= quirk->quirks;
}
return quirks;
}
#undef DEVICE_ID_ANY
#undef DEVICE_ID
struct edid_quirk {
u8 mfg_id[2];
u8 prod_id[2];
u32 quirks;
};
#define MFG(first, second) { (first), (second) }
#define PROD_ID(first, second) { (first), (second) }
/*
* Some devices have unreliable OUIDs where they don't set the device ID
* correctly, and as a result we need to use the EDID for finding additional
* DP quirks in such cases.
*/
static const struct edid_quirk edid_quirk_list[] = {
/* Optional 4K AMOLED panel in the ThinkPad X1 Extreme 2nd Generation
* only supports DPCD backlight controls
*/
{ MFG(0x4c, 0x83), PROD_ID(0x41, 0x41), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
/*
* Some Dell CML 2020 systems have panels support both AUX and PWM
* backlight control, and some only support AUX backlight control. All
* said panels start up in AUX mode by default, and we don't have any
* support for disabling HDR mode on these panels which would be
* required to switch to PWM backlight control mode (plus, I'm not
* even sure we want PWM backlight controls over DPCD backlight
* controls anyway...). Until we have a better way of detecting these,
* force DPCD backlight mode on all of them.
*/
{ MFG(0x06, 0xaf), PROD_ID(0x9b, 0x32), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
{ MFG(0x06, 0xaf), PROD_ID(0xeb, 0x41), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
{ MFG(0x4d, 0x10), PROD_ID(0xc7, 0x14), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
{ MFG(0x4d, 0x10), PROD_ID(0xe6, 0x14), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
{ MFG(0x4c, 0x83), PROD_ID(0x47, 0x41), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
{ MFG(0x09, 0xe5), PROD_ID(0xde, 0x08), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
};
#undef MFG
#undef PROD_ID
/**
* drm_dp_get_edid_quirks() - Check the EDID of a DP device to find additional
* DP-specific quirks
* @edid: The EDID to check
*
* While OUIDs are meant to be used to recognize a DisplayPort device, a lot
* of manufacturers don't seem to like following standards and neglect to fill
* the dev-ID in, making it impossible to only use OUIDs for determining
* quirks in some cases. This function can be used to check the EDID and look
* up any additional DP quirks. The bits returned by this function correspond
* to the quirk bits in &drm_dp_quirk.
*
* Returns: a bitmask of quirks, if any. The driver can check this using
* drm_dp_has_quirk().
*/
u32 drm_dp_get_edid_quirks(const struct edid *edid)
{
const struct edid_quirk *quirk;
u32 quirks = 0;
int i;
if (!edid)
return 0;
for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) {
quirk = &edid_quirk_list[i];
if (memcmp(quirk->mfg_id, edid->mfg_id,
sizeof(edid->mfg_id)) == 0 &&
memcmp(quirk->prod_id, edid->prod_code,
sizeof(edid->prod_code)) == 0)
quirks |= quirk->quirks;
}
DRM_DEBUG_KMS("DP sink: EDID mfg %*phD prod-ID %*phD quirks: 0x%04x\n",
(int)sizeof(edid->mfg_id), edid->mfg_id,
(int)sizeof(edid->prod_code), edid->prod_code, quirks);
return quirks;
}
EXPORT_SYMBOL(drm_dp_get_edid_quirks);
/**
* drm_dp_read_desc - read sink/branch descriptor from DPCD
* @aux: DisplayPort AUX channel
* @desc: Device descriptor to fill from DPCD
* @is_branch: true for branch devices, false for sink devices
*
* Read DPCD 0x400 (sink) or 0x500 (branch) into @desc. Also debug log the
* identification.
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc,
bool is_branch)
{
struct drm_dp_dpcd_ident *ident = &desc->ident;
unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI;
int ret, dev_id_len;
ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident));
if (ret < 0)
return ret;
desc->quirks = drm_dp_get_quirks(ident, is_branch);
dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id));
DRM_DEBUG_KMS("%s: DP %s: OUI %*phD dev-ID %*pE HW-rev %d.%d SW-rev %d.%d quirks 0x%04x\n",
aux->name, is_branch ? "branch" : "sink",
(int)sizeof(ident->oui), ident->oui,
dev_id_len, ident->device_id,
ident->hw_rev >> 4, ident->hw_rev & 0xf,
ident->sw_major_rev, ident->sw_minor_rev,
desc->quirks);
return 0;
}
EXPORT_SYMBOL(drm_dp_read_desc);
/**
* drm_dp_dsc_sink_max_slice_count() - Get the max slice count
* supported by the DSC sink.
* @dsc_dpcd: DSC capabilities from DPCD
* @is_edp: true if its eDP, false for DP
*
* Read the slice capabilities DPCD register from DSC sink to get
* the maximum slice count supported. This is used to populate
* the DSC parameters in the &struct drm_dsc_config by the driver.
* Driver creates an infoframe using these parameters to populate
* &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC
* infoframe using the helper function drm_dsc_pps_infoframe_pack()
*
* Returns:
* Maximum slice count supported by DSC sink or 0 its invalid
*/
u8 drm_dp_dsc_sink_max_slice_count(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE],
bool is_edp)
{
u8 slice_cap1 = dsc_dpcd[DP_DSC_SLICE_CAP_1 - DP_DSC_SUPPORT];
if (is_edp) {
/* For eDP, register DSC_SLICE_CAPABILITIES_1 gives slice count */
if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK)
return 4;
if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK)
return 2;
if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK)
return 1;
} else {
/* For DP, use values from DSC_SLICE_CAP_1 and DSC_SLICE_CAP2 */
u8 slice_cap2 = dsc_dpcd[DP_DSC_SLICE_CAP_2 - DP_DSC_SUPPORT];
if (slice_cap2 & DP_DSC_24_PER_DP_DSC_SINK)
return 24;
if (slice_cap2 & DP_DSC_20_PER_DP_DSC_SINK)
return 20;
if (slice_cap2 & DP_DSC_16_PER_DP_DSC_SINK)
return 16;
if (slice_cap1 & DP_DSC_12_PER_DP_DSC_SINK)
return 12;
if (slice_cap1 & DP_DSC_10_PER_DP_DSC_SINK)
return 10;
if (slice_cap1 & DP_DSC_8_PER_DP_DSC_SINK)
return 8;
if (slice_cap1 & DP_DSC_6_PER_DP_DSC_SINK)
return 6;
if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK)
return 4;
if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK)
return 2;
if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK)
return 1;
}
return 0;
}
EXPORT_SYMBOL(drm_dp_dsc_sink_max_slice_count);
/**
* drm_dp_dsc_sink_line_buf_depth() - Get the line buffer depth in bits
* @dsc_dpcd: DSC capabilities from DPCD
*
* Read the DSC DPCD register to parse the line buffer depth in bits which is
* number of bits of precision within the decoder line buffer supported by
* the DSC sink. This is used to populate the DSC parameters in the
* &struct drm_dsc_config by the driver.
* Driver creates an infoframe using these parameters to populate
* &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC
* infoframe using the helper function drm_dsc_pps_infoframe_pack()
*
* Returns:
* Line buffer depth supported by DSC panel or 0 its invalid
*/
u8 drm_dp_dsc_sink_line_buf_depth(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE])
{
u8 line_buf_depth = dsc_dpcd[DP_DSC_LINE_BUF_BIT_DEPTH - DP_DSC_SUPPORT];
switch (line_buf_depth & DP_DSC_LINE_BUF_BIT_DEPTH_MASK) {
case DP_DSC_LINE_BUF_BIT_DEPTH_9:
return 9;
case DP_DSC_LINE_BUF_BIT_DEPTH_10:
return 10;
case DP_DSC_LINE_BUF_BIT_DEPTH_11:
return 11;
case DP_DSC_LINE_BUF_BIT_DEPTH_12:
return 12;
case DP_DSC_LINE_BUF_BIT_DEPTH_13:
return 13;
case DP_DSC_LINE_BUF_BIT_DEPTH_14:
return 14;
case DP_DSC_LINE_BUF_BIT_DEPTH_15:
return 15;
case DP_DSC_LINE_BUF_BIT_DEPTH_16:
return 16;
case DP_DSC_LINE_BUF_BIT_DEPTH_8:
return 8;
}
return 0;
}
EXPORT_SYMBOL(drm_dp_dsc_sink_line_buf_depth);
/**
* drm_dp_dsc_sink_supported_input_bpcs() - Get all the input bits per component
* values supported by the DSC sink.
* @dsc_dpcd: DSC capabilities from DPCD
* @dsc_bpc: An array to be filled by this helper with supported
* input bpcs.
*
* Read the DSC DPCD from the sink device to parse the supported bits per
* component values. This is used to populate the DSC parameters
* in the &struct drm_dsc_config by the driver.
* Driver creates an infoframe using these parameters to populate
* &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC
* infoframe using the helper function drm_dsc_pps_infoframe_pack()
*
* Returns:
* Number of input BPC values parsed from the DPCD
*/
int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE],
u8 dsc_bpc[3])
{
int num_bpc = 0;
u8 color_depth = dsc_dpcd[DP_DSC_DEC_COLOR_DEPTH_CAP - DP_DSC_SUPPORT];
if (color_depth & DP_DSC_12_BPC)
dsc_bpc[num_bpc++] = 12;
if (color_depth & DP_DSC_10_BPC)
dsc_bpc[num_bpc++] = 10;
if (color_depth & DP_DSC_8_BPC)
dsc_bpc[num_bpc++] = 8;
return num_bpc;
}
EXPORT_SYMBOL(drm_dp_dsc_sink_supported_input_bpcs);
/**
* drm_dp_read_lttpr_common_caps - read the LTTPR common capabilities
* @aux: DisplayPort AUX channel
* @caps: buffer to return the capability info in
*
* Read capabilities common to all LTTPRs.
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_read_lttpr_common_caps(struct drm_dp_aux *aux,
u8 caps[DP_LTTPR_COMMON_CAP_SIZE])
{
int ret;
ret = drm_dp_dpcd_read(aux,
DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV,
caps, DP_LTTPR_COMMON_CAP_SIZE);
if (ret < 0)
return ret;
WARN_ON(ret != DP_LTTPR_COMMON_CAP_SIZE);
return 0;
}
EXPORT_SYMBOL(drm_dp_read_lttpr_common_caps);
/**
* drm_dp_read_lttpr_phy_caps - read the capabilities for a given LTTPR PHY
* @aux: DisplayPort AUX channel
* @dp_phy: LTTPR PHY to read the capabilities for
* @caps: buffer to return the capability info in
*
* Read the capabilities for the given LTTPR PHY.
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_read_lttpr_phy_caps(struct drm_dp_aux *aux,
enum drm_dp_phy dp_phy,
u8 caps[DP_LTTPR_PHY_CAP_SIZE])
{
int ret;
ret = drm_dp_dpcd_read(aux,
DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy),
caps, DP_LTTPR_PHY_CAP_SIZE);
if (ret < 0)
return ret;
WARN_ON(ret != DP_LTTPR_PHY_CAP_SIZE);
return 0;
}
EXPORT_SYMBOL(drm_dp_read_lttpr_phy_caps);
static u8 dp_lttpr_common_cap(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE], int r)
{
return caps[r - DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV];
}
/**
* drm_dp_lttpr_count - get the number of detected LTTPRs
* @caps: LTTPR common capabilities
*
* Get the number of detected LTTPRs from the LTTPR common capabilities info.
*
* Returns:
* -ERANGE if more than supported number (8) of LTTPRs are detected
* -EINVAL if the DP_PHY_REPEATER_CNT register contains an invalid value
* otherwise the number of detected LTTPRs
*/
int drm_dp_lttpr_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE])
{
u8 count = dp_lttpr_common_cap(caps, DP_PHY_REPEATER_CNT);
switch (hweight8(count)) {
case 0:
return 0;
case 1:
return 8 - ilog2(count);
case 8:
return -ERANGE;
default:
return -EINVAL;
}
}
EXPORT_SYMBOL(drm_dp_lttpr_count);
/**
* drm_dp_lttpr_max_link_rate - get the maximum link rate supported by all LTTPRs
* @caps: LTTPR common capabilities
*
* Returns the maximum link rate supported by all detected LTTPRs.
*/
int drm_dp_lttpr_max_link_rate(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE])
{
u8 rate = dp_lttpr_common_cap(caps, DP_MAX_LINK_RATE_PHY_REPEATER);
return drm_dp_bw_code_to_link_rate(rate);
}
EXPORT_SYMBOL(drm_dp_lttpr_max_link_rate);
/**
* drm_dp_lttpr_max_lane_count - get the maximum lane count supported by all LTTPRs
* @caps: LTTPR common capabilities
*
* Returns the maximum lane count supported by all detected LTTPRs.
*/
int drm_dp_lttpr_max_lane_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE])
{
u8 max_lanes = dp_lttpr_common_cap(caps, DP_MAX_LANE_COUNT_PHY_REPEATER);
return max_lanes & DP_MAX_LANE_COUNT_MASK;
}
EXPORT_SYMBOL(drm_dp_lttpr_max_lane_count);
/**
* drm_dp_lttpr_voltage_swing_level_3_supported - check for LTTPR vswing3 support
* @caps: LTTPR PHY capabilities
*
* Returns true if the @caps for an LTTPR TX PHY indicate support for
* voltage swing level 3.
*/
bool
drm_dp_lttpr_voltage_swing_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE])
{
u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1);
return txcap & DP_VOLTAGE_SWING_LEVEL_3_SUPPORTED;
}
EXPORT_SYMBOL(drm_dp_lttpr_voltage_swing_level_3_supported);
/**
* drm_dp_lttpr_pre_emphasis_level_3_supported - check for LTTPR preemph3 support
* @caps: LTTPR PHY capabilities
*
* Returns true if the @caps for an LTTPR TX PHY indicate support for
* pre-emphasis level 3.
*/
bool
drm_dp_lttpr_pre_emphasis_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE])
{
u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1);
return txcap & DP_PRE_EMPHASIS_LEVEL_3_SUPPORTED;
}
EXPORT_SYMBOL(drm_dp_lttpr_pre_emphasis_level_3_supported);
/**
* drm_dp_get_phy_test_pattern() - get the requested pattern from the sink.
* @aux: DisplayPort AUX channel
* @data: DP phy compliance test parameters.
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux,
struct drm_dp_phy_test_params *data)
{
int err;
u8 rate, lanes;
err = drm_dp_dpcd_readb(aux, DP_TEST_LINK_RATE, &rate);
if (err < 0)
return err;
data->link_rate = drm_dp_bw_code_to_link_rate(rate);
err = drm_dp_dpcd_readb(aux, DP_TEST_LANE_COUNT, &lanes);
if (err < 0)
return err;
data->num_lanes = lanes & DP_MAX_LANE_COUNT_MASK;
if (lanes & DP_ENHANCED_FRAME_CAP)
data->enhanced_frame_cap = true;
err = drm_dp_dpcd_readb(aux, DP_PHY_TEST_PATTERN, &data->phy_pattern);
if (err < 0)
return err;
switch (data->phy_pattern) {
case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
err = drm_dp_dpcd_read(aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0,
&data->custom80, sizeof(data->custom80));
if (err < 0)
return err;
break;
case DP_PHY_TEST_PATTERN_CP2520:
err = drm_dp_dpcd_read(aux, DP_TEST_HBR2_SCRAMBLER_RESET,
&data->hbr2_reset,
sizeof(data->hbr2_reset));
if (err < 0)
return err;
}
return 0;
}
EXPORT_SYMBOL(drm_dp_get_phy_test_pattern);
/**
* drm_dp_set_phy_test_pattern() - set the pattern to the sink.
* @aux: DisplayPort AUX channel
* @data: DP phy compliance test parameters.
* @dp_rev: DP revision to use for compliance testing
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux,
struct drm_dp_phy_test_params *data, u8 dp_rev)
{
int err, i;
u8 link_config[2];
u8 test_pattern;
link_config[0] = drm_dp_link_rate_to_bw_code(data->link_rate);
link_config[1] = data->num_lanes;
if (data->enhanced_frame_cap)
link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, link_config, 2);
if (err < 0)
return err;
test_pattern = data->phy_pattern;
if (dp_rev < 0x12) {
test_pattern = (test_pattern << 2) &
DP_LINK_QUAL_PATTERN_11_MASK;
err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET,
test_pattern);
if (err < 0)
return err;
} else {
for (i = 0; i < data->num_lanes; i++) {
err = drm_dp_dpcd_writeb(aux,
DP_LINK_QUAL_LANE0_SET + i,
test_pattern);
if (err < 0)
return err;
}
}
return 0;
}
EXPORT_SYMBOL(drm_dp_set_phy_test_pattern);
static const char *dp_pixelformat_get_name(enum dp_pixelformat pixelformat)
{
if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED)
return "Invalid";
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "RGB";
case DP_PIXELFORMAT_YUV444:
return "YUV444";
case DP_PIXELFORMAT_YUV422:
return "YUV422";
case DP_PIXELFORMAT_YUV420:
return "YUV420";
case DP_PIXELFORMAT_Y_ONLY:
return "Y_ONLY";
case DP_PIXELFORMAT_RAW:
return "RAW";
default:
return "Reserved";
}
}
static const char *dp_colorimetry_get_name(enum dp_pixelformat pixelformat,
enum dp_colorimetry colorimetry)
{
if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED)
return "Invalid";
switch (colorimetry) {
case DP_COLORIMETRY_DEFAULT:
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "sRGB";
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "BT.601";
case DP_PIXELFORMAT_Y_ONLY:
return "DICOM PS3.14";
case DP_PIXELFORMAT_RAW:
return "Custom Color Profile";
default:
return "Reserved";
}
case DP_COLORIMETRY_RGB_WIDE_FIXED: /* and DP_COLORIMETRY_BT709_YCC */
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "Wide Fixed";
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "BT.709";
default:
return "Reserved";
}
case DP_COLORIMETRY_RGB_WIDE_FLOAT: /* and DP_COLORIMETRY_XVYCC_601 */
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "Wide Float";
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "xvYCC 601";
default:
return "Reserved";
}
case DP_COLORIMETRY_OPRGB: /* and DP_COLORIMETRY_XVYCC_709 */
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "OpRGB";
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "xvYCC 709";
default:
return "Reserved";
}
case DP_COLORIMETRY_DCI_P3_RGB: /* and DP_COLORIMETRY_SYCC_601 */
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "DCI-P3";
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "sYCC 601";
default:
return "Reserved";
}
case DP_COLORIMETRY_RGB_CUSTOM: /* and DP_COLORIMETRY_OPYCC_601 */
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "Custom Profile";
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "OpYCC 601";
default:
return "Reserved";
}
case DP_COLORIMETRY_BT2020_RGB: /* and DP_COLORIMETRY_BT2020_CYCC */
switch (pixelformat) {
case DP_PIXELFORMAT_RGB:
return "BT.2020 RGB";
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "BT.2020 CYCC";
default:
return "Reserved";
}
case DP_COLORIMETRY_BT2020_YCC:
switch (pixelformat) {
case DP_PIXELFORMAT_YUV444:
case DP_PIXELFORMAT_YUV422:
case DP_PIXELFORMAT_YUV420:
return "BT.2020 YCC";
default:
return "Reserved";
}
default:
return "Invalid";
}
}
static const char *dp_dynamic_range_get_name(enum dp_dynamic_range dynamic_range)
{
switch (dynamic_range) {
case DP_DYNAMIC_RANGE_VESA:
return "VESA range";
case DP_DYNAMIC_RANGE_CTA:
return "CTA range";
default:
return "Invalid";
}
}
static const char *dp_content_type_get_name(enum dp_content_type content_type)
{
switch (content_type) {
case DP_CONTENT_TYPE_NOT_DEFINED:
return "Not defined";
case DP_CONTENT_TYPE_GRAPHICS:
return "Graphics";
case DP_CONTENT_TYPE_PHOTO:
return "Photo";
case DP_CONTENT_TYPE_VIDEO:
return "Video";
case DP_CONTENT_TYPE_GAME:
return "Game";
default:
return "Reserved";
}
}
void drm_dp_vsc_sdp_log(const char *level, struct device *dev,
const struct drm_dp_vsc_sdp *vsc)
{
#define DP_SDP_LOG(fmt, ...) dev_printk(level, dev, fmt, ##__VA_ARGS__)
DP_SDP_LOG("DP SDP: %s, revision %u, length %u\n", "VSC",
vsc->revision, vsc->length);
DP_SDP_LOG(" pixelformat: %s\n",
dp_pixelformat_get_name(vsc->pixelformat));
DP_SDP_LOG(" colorimetry: %s\n",
dp_colorimetry_get_name(vsc->pixelformat, vsc->colorimetry));
DP_SDP_LOG(" bpc: %u\n", vsc->bpc);
DP_SDP_LOG(" dynamic range: %s\n",
dp_dynamic_range_get_name(vsc->dynamic_range));
DP_SDP_LOG(" content type: %s\n",
dp_content_type_get_name(vsc->content_type));
#undef DP_SDP_LOG
}
EXPORT_SYMBOL(drm_dp_vsc_sdp_log);
/**
* drm_dp_get_pcon_max_frl_bw() - maximum frl supported by PCON
* @dpcd: DisplayPort configuration data
* @port_cap: port capabilities
*
* Returns maximum frl bandwidth supported by PCON in GBPS,
* returns 0 if not supported.
*/
int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
const u8 port_cap[4])
{
int bw;
u8 buf;
buf = port_cap[2];
bw = buf & DP_PCON_MAX_FRL_BW;
switch (bw) {
case DP_PCON_MAX_9GBPS:
return 9;
case DP_PCON_MAX_18GBPS:
return 18;
case DP_PCON_MAX_24GBPS:
return 24;
case DP_PCON_MAX_32GBPS:
return 32;
case DP_PCON_MAX_40GBPS:
return 40;
case DP_PCON_MAX_48GBPS:
return 48;
case DP_PCON_MAX_0GBPS:
default:
return 0;
}
return 0;
}
EXPORT_SYMBOL(drm_dp_get_pcon_max_frl_bw);
/**
* drm_dp_pcon_frl_prepare() - Prepare PCON for FRL.
* @aux: DisplayPort AUX channel
*
* Returns 0 if success, else returns negative error code.
*/
int drm_dp_pcon_frl_prepare(struct drm_dp_aux *aux, bool enable_frl_ready_hpd)
{
int ret;
u8 buf = DP_PCON_ENABLE_SOURCE_CTL_MODE |
DP_PCON_ENABLE_LINK_FRL_MODE;
if (enable_frl_ready_hpd)
buf |= DP_PCON_ENABLE_HPD_READY;
ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf);
return ret;
}
EXPORT_SYMBOL(drm_dp_pcon_frl_prepare);
/**
* drm_dp_pcon_is_frl_ready() - Is PCON ready for FRL
* @aux: DisplayPort AUX channel
*
* Returns true if success, else returns false.
*/
bool drm_dp_pcon_is_frl_ready(struct drm_dp_aux *aux)
{
int ret;
u8 buf;
ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf);
if (ret < 0)
return false;
if (buf & DP_PCON_FRL_READY)
return true;
return false;
}
EXPORT_SYMBOL(drm_dp_pcon_is_frl_ready);
/**
* drm_dp_pcon_frl_configure_1() - Set HDMI LINK Configuration-Step1
* @aux: DisplayPort AUX channel
* @max_frl_gbps: maximum frl bw to be configured between PCON and HDMI sink
* @concurrent_mode: true if concurrent mode or operation is required,
* false otherwise.
*
* Returns 0 if success, else returns negative error code.
*/
int drm_dp_pcon_frl_configure_1(struct drm_dp_aux *aux, int max_frl_gbps,
bool concurrent_mode)
{
int ret;
u8 buf;
ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf);
if (ret < 0)
return ret;
if (concurrent_mode)
buf |= DP_PCON_ENABLE_CONCURRENT_LINK;
else
buf &= ~DP_PCON_ENABLE_CONCURRENT_LINK;
switch (max_frl_gbps) {
case 9:
buf |= DP_PCON_ENABLE_MAX_BW_9GBPS;
break;
case 18:
buf |= DP_PCON_ENABLE_MAX_BW_18GBPS;
break;
case 24:
buf |= DP_PCON_ENABLE_MAX_BW_24GBPS;
break;
case 32:
buf |= DP_PCON_ENABLE_MAX_BW_32GBPS;
break;
case 40:
buf |= DP_PCON_ENABLE_MAX_BW_40GBPS;
break;
case 48:
buf |= DP_PCON_ENABLE_MAX_BW_48GBPS;
break;
case 0:
buf |= DP_PCON_ENABLE_MAX_BW_0GBPS;
break;
default:
return -EINVAL;
}
ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf);
if (ret < 0)
return ret;
return 0;
}
EXPORT_SYMBOL(drm_dp_pcon_frl_configure_1);
/**
* drm_dp_pcon_frl_configure_2() - Set HDMI Link configuration Step-2
* @aux: DisplayPort AUX channel
* @max_frl_mask : Max FRL BW to be tried by the PCON with HDMI Sink
* @extended_train_mode : true for Extended Mode, false for Normal Mode.
* In Normal mode, the PCON tries each frl bw from the max_frl_mask starting
* from min, and stops when link training is successful. In Extended mode, all
* frl bw selected in the mask are trained by the PCON.
*
* Returns 0 if success, else returns negative error code.
*/
int drm_dp_pcon_frl_configure_2(struct drm_dp_aux *aux, int max_frl_mask,
bool extended_train_mode)
{
int ret;
u8 buf = max_frl_mask;
if (extended_train_mode)
buf |= DP_PCON_FRL_LINK_TRAIN_EXTENDED;
ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_2, buf);
if (ret < 0)
return ret;
return 0;
}
EXPORT_SYMBOL(drm_dp_pcon_frl_configure_2);
/**
* drm_dp_pcon_reset_frl_config() - Re-Set HDMI Link configuration.
* @aux: DisplayPort AUX channel
*
* Returns 0 if success, else returns negative error code.
*/
int drm_dp_pcon_reset_frl_config(struct drm_dp_aux *aux)
{
int ret;
ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, 0x0);
if (ret < 0)
return ret;
return 0;
}
EXPORT_SYMBOL(drm_dp_pcon_reset_frl_config);
/**
* drm_dp_pcon_frl_enable() - Enable HDMI link through FRL
* @aux: DisplayPort AUX channel
*
* Returns 0 if success, else returns negative error code.
*/
int drm_dp_pcon_frl_enable(struct drm_dp_aux *aux)
{
int ret;
u8 buf = 0;
ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf);
if (ret < 0)
return ret;
if (!(buf & DP_PCON_ENABLE_SOURCE_CTL_MODE)) {
DRM_DEBUG_KMS("PCON in Autonomous mode, can't enable FRL\n");
return -EINVAL;
}
buf |= DP_PCON_ENABLE_HDMI_LINK;
ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf);
if (ret < 0)
return ret;
return 0;
}
EXPORT_SYMBOL(drm_dp_pcon_frl_enable);
/**
* drm_dp_pcon_hdmi_link_active() - check if the PCON HDMI LINK status is active.
* @aux: DisplayPort AUX channel
*
* Returns true if link is active else returns false.
*/
bool drm_dp_pcon_hdmi_link_active(struct drm_dp_aux *aux)
{
u8 buf;
int ret;
ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf);
if (ret < 0)
return false;
return buf & DP_PCON_HDMI_TX_LINK_ACTIVE;
}
EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_active);
/**
* drm_dp_pcon_hdmi_link_mode() - get the PCON HDMI LINK MODE
* @aux: DisplayPort AUX channel
* @frl_trained_mask: pointer to store bitmask of the trained bw configuration.
* Valid only if the MODE returned is FRL. For Normal Link training mode
* only 1 of the bits will be set, but in case of Extended mode, more than
* one bits can be set.
*
* Returns the link mode : TMDS or FRL on success, else returns negative error
* code.
*/
int drm_dp_pcon_hdmi_link_mode(struct drm_dp_aux *aux, u8 *frl_trained_mask)
{
u8 buf;
int mode;
int ret;
ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_POST_FRL_STATUS, &buf);
if (ret < 0)
return ret;
mode = buf & DP_PCON_HDMI_LINK_MODE;
if (frl_trained_mask && DP_PCON_HDMI_MODE_FRL == mode)
*frl_trained_mask = (buf & DP_PCON_HDMI_FRL_TRAINED_BW) >> 1;
return mode;
}
EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_mode);
/**
* drm_dp_pcon_hdmi_frl_link_error_count() - print the error count per lane
* during link failure between PCON and HDMI sink
* @aux: DisplayPort AUX channel
* @connector: DRM connector
* code.
**/
void drm_dp_pcon_hdmi_frl_link_error_count(struct drm_dp_aux *aux,
struct drm_connector *connector)
{
u8 buf, error_count;
int i, num_error;
struct drm_hdmi_info *hdmi = &connector->display_info.hdmi;
for (i = 0; i < hdmi->max_lanes; i++) {
if (drm_dp_dpcd_readb(aux, DP_PCON_HDMI_ERROR_STATUS_LN0 + i, &buf) < 0)
return;
error_count = buf & DP_PCON_HDMI_ERROR_COUNT_MASK;
switch (error_count) {
case DP_PCON_HDMI_ERROR_COUNT_HUNDRED_PLUS:
num_error = 100;
break;
case DP_PCON_HDMI_ERROR_COUNT_TEN_PLUS:
num_error = 10;
break;
case DP_PCON_HDMI_ERROR_COUNT_THREE_PLUS:
num_error = 3;
break;
default:
num_error = 0;
}
DRM_ERROR("More than %d errors since the last read for lane %d", num_error, i);
}
}
EXPORT_SYMBOL(drm_dp_pcon_hdmi_frl_link_error_count);