forked from Minki/linux
2f9ba199da
If this happens (and it recently did), we free a structure while part of it is still in use, which results in non-obvious crashes. The way it's detached is not trivial (DRM core has to call the connector .destroy callback and things must be torn down in the right order), so better detect it and warn early. Signed-off-by: Grazvydas Ignotas <notasas@gmail.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
2009 lines
65 KiB
C
2009 lines
65 KiB
C
/*
|
|
* Copyright 2007-8 Advanced Micro Devices, Inc.
|
|
* Copyright 2008 Red Hat Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* Authors: Dave Airlie
|
|
* Alex Deucher
|
|
*/
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/amdgpu_drm.h>
|
|
#include "amdgpu.h"
|
|
#include "atom.h"
|
|
#include "atombios_encoders.h"
|
|
#include "atombios_dp.h"
|
|
#include "amdgpu_connectors.h"
|
|
#include "amdgpu_i2c.h"
|
|
|
|
#include <linux/pm_runtime.h>
|
|
|
|
void amdgpu_connector_hotplug(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
/* bail if the connector does not have hpd pin, e.g.,
|
|
* VGA, TV, etc.
|
|
*/
|
|
if (amdgpu_connector->hpd.hpd == AMDGPU_HPD_NONE)
|
|
return;
|
|
|
|
amdgpu_display_hpd_set_polarity(adev, amdgpu_connector->hpd.hpd);
|
|
|
|
/* if the connector is already off, don't turn it back on */
|
|
if (connector->dpms != DRM_MODE_DPMS_ON)
|
|
return;
|
|
|
|
/* just deal with DP (not eDP) here. */
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
|
|
struct amdgpu_connector_atom_dig *dig_connector =
|
|
amdgpu_connector->con_priv;
|
|
|
|
/* if existing sink type was not DP no need to retrain */
|
|
if (dig_connector->dp_sink_type != CONNECTOR_OBJECT_ID_DISPLAYPORT)
|
|
return;
|
|
|
|
/* first get sink type as it may be reset after (un)plug */
|
|
dig_connector->dp_sink_type = amdgpu_atombios_dp_get_sinktype(amdgpu_connector);
|
|
/* don't do anything if sink is not display port, i.e.,
|
|
* passive dp->(dvi|hdmi) adaptor
|
|
*/
|
|
if (dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) {
|
|
int saved_dpms = connector->dpms;
|
|
/* Only turn off the display if it's physically disconnected */
|
|
if (!amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) {
|
|
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
|
|
} else if (amdgpu_atombios_dp_needs_link_train(amdgpu_connector)) {
|
|
/* Don't try to start link training before we
|
|
* have the dpcd */
|
|
if (amdgpu_atombios_dp_get_dpcd(amdgpu_connector))
|
|
return;
|
|
|
|
/* set it to OFF so that drm_helper_connector_dpms()
|
|
* won't return immediately since the current state
|
|
* is ON at this point.
|
|
*/
|
|
connector->dpms = DRM_MODE_DPMS_OFF;
|
|
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON);
|
|
}
|
|
connector->dpms = saved_dpms;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void amdgpu_connector_property_change_mode(struct drm_encoder *encoder)
|
|
{
|
|
struct drm_crtc *crtc = encoder->crtc;
|
|
|
|
if (crtc && crtc->enabled) {
|
|
drm_crtc_helper_set_mode(crtc, &crtc->mode,
|
|
crtc->x, crtc->y, crtc->primary->fb);
|
|
}
|
|
}
|
|
|
|
int amdgpu_connector_get_monitor_bpc(struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct amdgpu_connector_atom_dig *dig_connector;
|
|
int bpc = 8;
|
|
unsigned mode_clock, max_tmds_clock;
|
|
|
|
switch (connector->connector_type) {
|
|
case DRM_MODE_CONNECTOR_DVII:
|
|
case DRM_MODE_CONNECTOR_HDMIB:
|
|
if (amdgpu_connector->use_digital) {
|
|
if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
|
|
if (connector->display_info.bpc)
|
|
bpc = connector->display_info.bpc;
|
|
}
|
|
}
|
|
break;
|
|
case DRM_MODE_CONNECTOR_DVID:
|
|
case DRM_MODE_CONNECTOR_HDMIA:
|
|
if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
|
|
if (connector->display_info.bpc)
|
|
bpc = connector->display_info.bpc;
|
|
}
|
|
break;
|
|
case DRM_MODE_CONNECTOR_DisplayPort:
|
|
dig_connector = amdgpu_connector->con_priv;
|
|
if ((dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) ||
|
|
(dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) ||
|
|
drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
|
|
if (connector->display_info.bpc)
|
|
bpc = connector->display_info.bpc;
|
|
}
|
|
break;
|
|
case DRM_MODE_CONNECTOR_eDP:
|
|
case DRM_MODE_CONNECTOR_LVDS:
|
|
if (connector->display_info.bpc)
|
|
bpc = connector->display_info.bpc;
|
|
else {
|
|
const struct drm_connector_helper_funcs *connector_funcs =
|
|
connector->helper_private;
|
|
struct drm_encoder *encoder = connector_funcs->best_encoder(connector);
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
if (dig->lcd_misc & ATOM_PANEL_MISC_V13_6BIT_PER_COLOR)
|
|
bpc = 6;
|
|
else if (dig->lcd_misc & ATOM_PANEL_MISC_V13_8BIT_PER_COLOR)
|
|
bpc = 8;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
|
|
/*
|
|
* Pre DCE-8 hw can't handle > 12 bpc, and more than 12 bpc doesn't make
|
|
* much sense without support for > 12 bpc framebuffers. RGB 4:4:4 at
|
|
* 12 bpc is always supported on hdmi deep color sinks, as this is
|
|
* required by the HDMI-1.3 spec. Clamp to a safe 12 bpc maximum.
|
|
*/
|
|
if (bpc > 12) {
|
|
DRM_DEBUG("%s: HDMI deep color %d bpc unsupported. Using 12 bpc.\n",
|
|
connector->name, bpc);
|
|
bpc = 12;
|
|
}
|
|
|
|
/* Any defined maximum tmds clock limit we must not exceed? */
|
|
if (connector->max_tmds_clock > 0) {
|
|
/* mode_clock is clock in kHz for mode to be modeset on this connector */
|
|
mode_clock = amdgpu_connector->pixelclock_for_modeset;
|
|
|
|
/* Maximum allowable input clock in kHz */
|
|
max_tmds_clock = connector->max_tmds_clock * 1000;
|
|
|
|
DRM_DEBUG("%s: hdmi mode dotclock %d kHz, max tmds input clock %d kHz.\n",
|
|
connector->name, mode_clock, max_tmds_clock);
|
|
|
|
/* Check if bpc is within clock limit. Try to degrade gracefully otherwise */
|
|
if ((bpc == 12) && (mode_clock * 3/2 > max_tmds_clock)) {
|
|
if ((connector->display_info.edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) &&
|
|
(mode_clock * 5/4 <= max_tmds_clock))
|
|
bpc = 10;
|
|
else
|
|
bpc = 8;
|
|
|
|
DRM_DEBUG("%s: HDMI deep color 12 bpc exceeds max tmds clock. Using %d bpc.\n",
|
|
connector->name, bpc);
|
|
}
|
|
|
|
if ((bpc == 10) && (mode_clock * 5/4 > max_tmds_clock)) {
|
|
bpc = 8;
|
|
DRM_DEBUG("%s: HDMI deep color 10 bpc exceeds max tmds clock. Using %d bpc.\n",
|
|
connector->name, bpc);
|
|
}
|
|
} else if (bpc > 8) {
|
|
/* max_tmds_clock missing, but hdmi spec mandates it for deep color. */
|
|
DRM_DEBUG("%s: Required max tmds clock for HDMI deep color missing. Using 8 bpc.\n",
|
|
connector->name);
|
|
bpc = 8;
|
|
}
|
|
}
|
|
|
|
if ((amdgpu_deep_color == 0) && (bpc > 8)) {
|
|
DRM_DEBUG("%s: Deep color disabled. Set amdgpu module param deep_color=1 to enable.\n",
|
|
connector->name);
|
|
bpc = 8;
|
|
}
|
|
|
|
DRM_DEBUG("%s: Display bpc=%d, returned bpc=%d\n",
|
|
connector->name, connector->display_info.bpc, bpc);
|
|
|
|
return bpc;
|
|
}
|
|
|
|
static void
|
|
amdgpu_connector_update_scratch_regs(struct drm_connector *connector,
|
|
enum drm_connector_status status)
|
|
{
|
|
struct drm_encoder *best_encoder = NULL;
|
|
struct drm_encoder *encoder = NULL;
|
|
const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private;
|
|
bool connected;
|
|
int i;
|
|
|
|
best_encoder = connector_funcs->best_encoder(connector);
|
|
|
|
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
|
|
if (connector->encoder_ids[i] == 0)
|
|
break;
|
|
|
|
encoder = drm_encoder_find(connector->dev,
|
|
connector->encoder_ids[i]);
|
|
if (!encoder)
|
|
continue;
|
|
|
|
if ((encoder == best_encoder) && (status == connector_status_connected))
|
|
connected = true;
|
|
else
|
|
connected = false;
|
|
|
|
amdgpu_atombios_encoder_set_bios_scratch_regs(connector, encoder, connected);
|
|
|
|
}
|
|
}
|
|
|
|
static struct drm_encoder *
|
|
amdgpu_connector_find_encoder(struct drm_connector *connector,
|
|
int encoder_type)
|
|
{
|
|
struct drm_encoder *encoder;
|
|
int i;
|
|
|
|
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
|
|
if (connector->encoder_ids[i] == 0)
|
|
break;
|
|
encoder = drm_encoder_find(connector->dev,
|
|
connector->encoder_ids[i]);
|
|
if (!encoder)
|
|
continue;
|
|
|
|
if (encoder->encoder_type == encoder_type)
|
|
return encoder;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct edid *amdgpu_connector_edid(struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct drm_property_blob *edid_blob = connector->edid_blob_ptr;
|
|
|
|
if (amdgpu_connector->edid) {
|
|
return amdgpu_connector->edid;
|
|
} else if (edid_blob) {
|
|
struct edid *edid = kmemdup(edid_blob->data, edid_blob->length, GFP_KERNEL);
|
|
if (edid)
|
|
amdgpu_connector->edid = edid;
|
|
}
|
|
return amdgpu_connector->edid;
|
|
}
|
|
|
|
static struct edid *
|
|
amdgpu_connector_get_hardcoded_edid(struct amdgpu_device *adev)
|
|
{
|
|
struct edid *edid;
|
|
|
|
if (adev->mode_info.bios_hardcoded_edid) {
|
|
edid = kmalloc(adev->mode_info.bios_hardcoded_edid_size, GFP_KERNEL);
|
|
if (edid) {
|
|
memcpy((unsigned char *)edid,
|
|
(unsigned char *)adev->mode_info.bios_hardcoded_edid,
|
|
adev->mode_info.bios_hardcoded_edid_size);
|
|
return edid;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void amdgpu_connector_get_edid(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
if (amdgpu_connector->edid)
|
|
return;
|
|
|
|
/* on hw with routers, select right port */
|
|
if (amdgpu_connector->router.ddc_valid)
|
|
amdgpu_i2c_router_select_ddc_port(amdgpu_connector);
|
|
|
|
if ((amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) !=
|
|
ENCODER_OBJECT_ID_NONE) &&
|
|
amdgpu_connector->ddc_bus->has_aux) {
|
|
amdgpu_connector->edid = drm_get_edid(connector,
|
|
&amdgpu_connector->ddc_bus->aux.ddc);
|
|
} else if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ||
|
|
(connector->connector_type == DRM_MODE_CONNECTOR_eDP)) {
|
|
struct amdgpu_connector_atom_dig *dig = amdgpu_connector->con_priv;
|
|
|
|
if ((dig->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT ||
|
|
dig->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) &&
|
|
amdgpu_connector->ddc_bus->has_aux)
|
|
amdgpu_connector->edid = drm_get_edid(connector,
|
|
&amdgpu_connector->ddc_bus->aux.ddc);
|
|
else if (amdgpu_connector->ddc_bus)
|
|
amdgpu_connector->edid = drm_get_edid(connector,
|
|
&amdgpu_connector->ddc_bus->adapter);
|
|
} else if (amdgpu_connector->ddc_bus) {
|
|
amdgpu_connector->edid = drm_get_edid(connector,
|
|
&amdgpu_connector->ddc_bus->adapter);
|
|
}
|
|
|
|
if (!amdgpu_connector->edid) {
|
|
/* some laptops provide a hardcoded edid in rom for LCDs */
|
|
if (((connector->connector_type == DRM_MODE_CONNECTOR_LVDS) ||
|
|
(connector->connector_type == DRM_MODE_CONNECTOR_eDP)))
|
|
amdgpu_connector->edid = amdgpu_connector_get_hardcoded_edid(adev);
|
|
}
|
|
}
|
|
|
|
static void amdgpu_connector_free_edid(struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
if (amdgpu_connector->edid) {
|
|
kfree(amdgpu_connector->edid);
|
|
amdgpu_connector->edid = NULL;
|
|
}
|
|
}
|
|
|
|
static int amdgpu_connector_ddc_get_modes(struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
int ret;
|
|
|
|
if (amdgpu_connector->edid) {
|
|
drm_mode_connector_update_edid_property(connector, amdgpu_connector->edid);
|
|
ret = drm_add_edid_modes(connector, amdgpu_connector->edid);
|
|
drm_edid_to_eld(connector, amdgpu_connector->edid);
|
|
return ret;
|
|
}
|
|
drm_mode_connector_update_edid_property(connector, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static struct drm_encoder *
|
|
amdgpu_connector_best_single_encoder(struct drm_connector *connector)
|
|
{
|
|
int enc_id = connector->encoder_ids[0];
|
|
|
|
/* pick the encoder ids */
|
|
if (enc_id)
|
|
return drm_encoder_find(connector->dev, enc_id);
|
|
return NULL;
|
|
}
|
|
|
|
static void amdgpu_get_native_mode(struct drm_connector *connector)
|
|
{
|
|
struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector);
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
|
|
if (encoder == NULL)
|
|
return;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (!list_empty(&connector->probed_modes)) {
|
|
struct drm_display_mode *preferred_mode =
|
|
list_first_entry(&connector->probed_modes,
|
|
struct drm_display_mode, head);
|
|
|
|
amdgpu_encoder->native_mode = *preferred_mode;
|
|
} else {
|
|
amdgpu_encoder->native_mode.clock = 0;
|
|
}
|
|
}
|
|
|
|
static struct drm_display_mode *
|
|
amdgpu_connector_lcd_native_mode(struct drm_encoder *encoder)
|
|
{
|
|
struct drm_device *dev = encoder->dev;
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct drm_display_mode *mode = NULL;
|
|
struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
|
|
|
|
if (native_mode->hdisplay != 0 &&
|
|
native_mode->vdisplay != 0 &&
|
|
native_mode->clock != 0) {
|
|
mode = drm_mode_duplicate(dev, native_mode);
|
|
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
|
|
drm_mode_set_name(mode);
|
|
|
|
DRM_DEBUG_KMS("Adding native panel mode %s\n", mode->name);
|
|
} else if (native_mode->hdisplay != 0 &&
|
|
native_mode->vdisplay != 0) {
|
|
/* mac laptops without an edid */
|
|
/* Note that this is not necessarily the exact panel mode,
|
|
* but an approximation based on the cvt formula. For these
|
|
* systems we should ideally read the mode info out of the
|
|
* registers or add a mode table, but this works and is much
|
|
* simpler.
|
|
*/
|
|
mode = drm_cvt_mode(dev, native_mode->hdisplay, native_mode->vdisplay, 60, true, false, false);
|
|
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
|
|
DRM_DEBUG_KMS("Adding cvt approximation of native panel mode %s\n", mode->name);
|
|
}
|
|
return mode;
|
|
}
|
|
|
|
static void amdgpu_connector_add_common_modes(struct drm_encoder *encoder,
|
|
struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = encoder->dev;
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct drm_display_mode *mode = NULL;
|
|
struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
|
|
int i;
|
|
static const struct mode_size {
|
|
int w;
|
|
int h;
|
|
} common_modes[17] = {
|
|
{ 640, 480},
|
|
{ 720, 480},
|
|
{ 800, 600},
|
|
{ 848, 480},
|
|
{1024, 768},
|
|
{1152, 768},
|
|
{1280, 720},
|
|
{1280, 800},
|
|
{1280, 854},
|
|
{1280, 960},
|
|
{1280, 1024},
|
|
{1440, 900},
|
|
{1400, 1050},
|
|
{1680, 1050},
|
|
{1600, 1200},
|
|
{1920, 1080},
|
|
{1920, 1200}
|
|
};
|
|
|
|
for (i = 0; i < 17; i++) {
|
|
if (amdgpu_encoder->devices & (ATOM_DEVICE_TV_SUPPORT)) {
|
|
if (common_modes[i].w > 1024 ||
|
|
common_modes[i].h > 768)
|
|
continue;
|
|
}
|
|
if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) {
|
|
if (common_modes[i].w > native_mode->hdisplay ||
|
|
common_modes[i].h > native_mode->vdisplay ||
|
|
(common_modes[i].w == native_mode->hdisplay &&
|
|
common_modes[i].h == native_mode->vdisplay))
|
|
continue;
|
|
}
|
|
if (common_modes[i].w < 320 || common_modes[i].h < 200)
|
|
continue;
|
|
|
|
mode = drm_cvt_mode(dev, common_modes[i].w, common_modes[i].h, 60, false, false, false);
|
|
drm_mode_probed_add(connector, mode);
|
|
}
|
|
}
|
|
|
|
static int amdgpu_connector_set_property(struct drm_connector *connector,
|
|
struct drm_property *property,
|
|
uint64_t val)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
struct drm_encoder *encoder;
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
|
|
if (property == adev->mode_info.coherent_mode_property) {
|
|
struct amdgpu_encoder_atom_dig *dig;
|
|
bool new_coherent_mode;
|
|
|
|
/* need to find digital encoder on connector */
|
|
encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (!amdgpu_encoder->enc_priv)
|
|
return 0;
|
|
|
|
dig = amdgpu_encoder->enc_priv;
|
|
new_coherent_mode = val ? true : false;
|
|
if (dig->coherent_mode != new_coherent_mode) {
|
|
dig->coherent_mode = new_coherent_mode;
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
}
|
|
}
|
|
|
|
if (property == adev->mode_info.audio_property) {
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
/* need to find digital encoder on connector */
|
|
encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (amdgpu_connector->audio != val) {
|
|
amdgpu_connector->audio = val;
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
}
|
|
}
|
|
|
|
if (property == adev->mode_info.dither_property) {
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
/* need to find digital encoder on connector */
|
|
encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (amdgpu_connector->dither != val) {
|
|
amdgpu_connector->dither = val;
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
}
|
|
}
|
|
|
|
if (property == adev->mode_info.underscan_property) {
|
|
/* need to find digital encoder on connector */
|
|
encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (amdgpu_encoder->underscan_type != val) {
|
|
amdgpu_encoder->underscan_type = val;
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
}
|
|
}
|
|
|
|
if (property == adev->mode_info.underscan_hborder_property) {
|
|
/* need to find digital encoder on connector */
|
|
encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (amdgpu_encoder->underscan_hborder != val) {
|
|
amdgpu_encoder->underscan_hborder = val;
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
}
|
|
}
|
|
|
|
if (property == adev->mode_info.underscan_vborder_property) {
|
|
/* need to find digital encoder on connector */
|
|
encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (amdgpu_encoder->underscan_vborder != val) {
|
|
amdgpu_encoder->underscan_vborder = val;
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
}
|
|
}
|
|
|
|
if (property == adev->mode_info.load_detect_property) {
|
|
struct amdgpu_connector *amdgpu_connector =
|
|
to_amdgpu_connector(connector);
|
|
|
|
if (val == 0)
|
|
amdgpu_connector->dac_load_detect = false;
|
|
else
|
|
amdgpu_connector->dac_load_detect = true;
|
|
}
|
|
|
|
if (property == dev->mode_config.scaling_mode_property) {
|
|
enum amdgpu_rmx_type rmx_type;
|
|
|
|
if (connector->encoder) {
|
|
amdgpu_encoder = to_amdgpu_encoder(connector->encoder);
|
|
} else {
|
|
const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private;
|
|
amdgpu_encoder = to_amdgpu_encoder(connector_funcs->best_encoder(connector));
|
|
}
|
|
|
|
switch (val) {
|
|
default:
|
|
case DRM_MODE_SCALE_NONE: rmx_type = RMX_OFF; break;
|
|
case DRM_MODE_SCALE_CENTER: rmx_type = RMX_CENTER; break;
|
|
case DRM_MODE_SCALE_ASPECT: rmx_type = RMX_ASPECT; break;
|
|
case DRM_MODE_SCALE_FULLSCREEN: rmx_type = RMX_FULL; break;
|
|
}
|
|
if (amdgpu_encoder->rmx_type == rmx_type)
|
|
return 0;
|
|
|
|
if ((rmx_type != DRM_MODE_SCALE_NONE) &&
|
|
(amdgpu_encoder->native_mode.clock == 0))
|
|
return 0;
|
|
|
|
amdgpu_encoder->rmx_type = rmx_type;
|
|
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
amdgpu_connector_fixup_lcd_native_mode(struct drm_encoder *encoder,
|
|
struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
|
|
struct drm_display_mode *t, *mode;
|
|
|
|
/* If the EDID preferred mode doesn't match the native mode, use it */
|
|
list_for_each_entry_safe(mode, t, &connector->probed_modes, head) {
|
|
if (mode->type & DRM_MODE_TYPE_PREFERRED) {
|
|
if (mode->hdisplay != native_mode->hdisplay ||
|
|
mode->vdisplay != native_mode->vdisplay)
|
|
memcpy(native_mode, mode, sizeof(*mode));
|
|
}
|
|
}
|
|
|
|
/* Try to get native mode details from EDID if necessary */
|
|
if (!native_mode->clock) {
|
|
list_for_each_entry_safe(mode, t, &connector->probed_modes, head) {
|
|
if (mode->hdisplay == native_mode->hdisplay &&
|
|
mode->vdisplay == native_mode->vdisplay) {
|
|
*native_mode = *mode;
|
|
drm_mode_set_crtcinfo(native_mode, CRTC_INTERLACE_HALVE_V);
|
|
DRM_DEBUG_KMS("Determined LVDS native mode details from EDID\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!native_mode->clock) {
|
|
DRM_DEBUG_KMS("No LVDS native mode details, disabling RMX\n");
|
|
amdgpu_encoder->rmx_type = RMX_OFF;
|
|
}
|
|
}
|
|
|
|
static int amdgpu_connector_lvds_get_modes(struct drm_connector *connector)
|
|
{
|
|
struct drm_encoder *encoder;
|
|
int ret = 0;
|
|
struct drm_display_mode *mode;
|
|
|
|
amdgpu_connector_get_edid(connector);
|
|
ret = amdgpu_connector_ddc_get_modes(connector);
|
|
if (ret > 0) {
|
|
encoder = amdgpu_connector_best_single_encoder(connector);
|
|
if (encoder) {
|
|
amdgpu_connector_fixup_lcd_native_mode(encoder, connector);
|
|
/* add scaled modes */
|
|
amdgpu_connector_add_common_modes(encoder, connector);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
encoder = amdgpu_connector_best_single_encoder(connector);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
/* we have no EDID modes */
|
|
mode = amdgpu_connector_lcd_native_mode(encoder);
|
|
if (mode) {
|
|
ret = 1;
|
|
drm_mode_probed_add(connector, mode);
|
|
/* add the width/height from vbios tables if available */
|
|
connector->display_info.width_mm = mode->width_mm;
|
|
connector->display_info.height_mm = mode->height_mm;
|
|
/* add scaled modes */
|
|
amdgpu_connector_add_common_modes(encoder, connector);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int amdgpu_connector_lvds_mode_valid(struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector);
|
|
|
|
if ((mode->hdisplay < 320) || (mode->vdisplay < 240))
|
|
return MODE_PANEL;
|
|
|
|
if (encoder) {
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
|
|
|
|
/* AVIVO hardware supports downscaling modes larger than the panel
|
|
* to the panel size, but I'm not sure this is desirable.
|
|
*/
|
|
if ((mode->hdisplay > native_mode->hdisplay) ||
|
|
(mode->vdisplay > native_mode->vdisplay))
|
|
return MODE_PANEL;
|
|
|
|
/* if scaling is disabled, block non-native modes */
|
|
if (amdgpu_encoder->rmx_type == RMX_OFF) {
|
|
if ((mode->hdisplay != native_mode->hdisplay) ||
|
|
(mode->vdisplay != native_mode->vdisplay))
|
|
return MODE_PANEL;
|
|
}
|
|
}
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
amdgpu_connector_lvds_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector);
|
|
enum drm_connector_status ret = connector_status_disconnected;
|
|
int r;
|
|
|
|
r = pm_runtime_get_sync(connector->dev->dev);
|
|
if (r < 0)
|
|
return connector_status_disconnected;
|
|
|
|
if (encoder) {
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
|
|
|
|
/* check if panel is valid */
|
|
if (native_mode->hdisplay >= 320 && native_mode->vdisplay >= 240)
|
|
ret = connector_status_connected;
|
|
|
|
}
|
|
|
|
/* check for edid as well */
|
|
amdgpu_connector_get_edid(connector);
|
|
if (amdgpu_connector->edid)
|
|
ret = connector_status_connected;
|
|
/* check acpi lid status ??? */
|
|
|
|
amdgpu_connector_update_scratch_regs(connector, ret);
|
|
pm_runtime_mark_last_busy(connector->dev->dev);
|
|
pm_runtime_put_autosuspend(connector->dev->dev);
|
|
return ret;
|
|
}
|
|
|
|
static void amdgpu_connector_destroy(struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
if (amdgpu_connector->ddc_bus->has_aux) {
|
|
drm_dp_aux_unregister(&amdgpu_connector->ddc_bus->aux);
|
|
amdgpu_connector->ddc_bus->has_aux = false;
|
|
}
|
|
amdgpu_connector_free_edid(connector);
|
|
kfree(amdgpu_connector->con_priv);
|
|
drm_connector_unregister(connector);
|
|
drm_connector_cleanup(connector);
|
|
kfree(connector);
|
|
}
|
|
|
|
static int amdgpu_connector_set_lcd_property(struct drm_connector *connector,
|
|
struct drm_property *property,
|
|
uint64_t value)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
enum amdgpu_rmx_type rmx_type;
|
|
|
|
DRM_DEBUG_KMS("\n");
|
|
if (property != dev->mode_config.scaling_mode_property)
|
|
return 0;
|
|
|
|
if (connector->encoder)
|
|
amdgpu_encoder = to_amdgpu_encoder(connector->encoder);
|
|
else {
|
|
const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private;
|
|
amdgpu_encoder = to_amdgpu_encoder(connector_funcs->best_encoder(connector));
|
|
}
|
|
|
|
switch (value) {
|
|
case DRM_MODE_SCALE_NONE: rmx_type = RMX_OFF; break;
|
|
case DRM_MODE_SCALE_CENTER: rmx_type = RMX_CENTER; break;
|
|
case DRM_MODE_SCALE_ASPECT: rmx_type = RMX_ASPECT; break;
|
|
default:
|
|
case DRM_MODE_SCALE_FULLSCREEN: rmx_type = RMX_FULL; break;
|
|
}
|
|
if (amdgpu_encoder->rmx_type == rmx_type)
|
|
return 0;
|
|
|
|
amdgpu_encoder->rmx_type = rmx_type;
|
|
|
|
amdgpu_connector_property_change_mode(&amdgpu_encoder->base);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct drm_connector_helper_funcs amdgpu_connector_lvds_helper_funcs = {
|
|
.get_modes = amdgpu_connector_lvds_get_modes,
|
|
.mode_valid = amdgpu_connector_lvds_mode_valid,
|
|
.best_encoder = amdgpu_connector_best_single_encoder,
|
|
};
|
|
|
|
static const struct drm_connector_funcs amdgpu_connector_lvds_funcs = {
|
|
.dpms = drm_helper_connector_dpms,
|
|
.detect = amdgpu_connector_lvds_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.destroy = amdgpu_connector_destroy,
|
|
.set_property = amdgpu_connector_set_lcd_property,
|
|
};
|
|
|
|
static int amdgpu_connector_vga_get_modes(struct drm_connector *connector)
|
|
{
|
|
int ret;
|
|
|
|
amdgpu_connector_get_edid(connector);
|
|
ret = amdgpu_connector_ddc_get_modes(connector);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int amdgpu_connector_vga_mode_valid(struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
/* XXX check mode bandwidth */
|
|
|
|
if ((mode->clock / 10) > adev->clock.max_pixel_clock)
|
|
return MODE_CLOCK_HIGH;
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
amdgpu_connector_vga_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct drm_encoder *encoder;
|
|
const struct drm_encoder_helper_funcs *encoder_funcs;
|
|
bool dret = false;
|
|
enum drm_connector_status ret = connector_status_disconnected;
|
|
int r;
|
|
|
|
r = pm_runtime_get_sync(connector->dev->dev);
|
|
if (r < 0)
|
|
return connector_status_disconnected;
|
|
|
|
encoder = amdgpu_connector_best_single_encoder(connector);
|
|
if (!encoder)
|
|
ret = connector_status_disconnected;
|
|
|
|
if (amdgpu_connector->ddc_bus)
|
|
dret = amdgpu_ddc_probe(amdgpu_connector, false);
|
|
if (dret) {
|
|
amdgpu_connector->detected_by_load = false;
|
|
amdgpu_connector_free_edid(connector);
|
|
amdgpu_connector_get_edid(connector);
|
|
|
|
if (!amdgpu_connector->edid) {
|
|
DRM_ERROR("%s: probed a monitor but no|invalid EDID\n",
|
|
connector->name);
|
|
ret = connector_status_connected;
|
|
} else {
|
|
amdgpu_connector->use_digital =
|
|
!!(amdgpu_connector->edid->input & DRM_EDID_INPUT_DIGITAL);
|
|
|
|
/* some oems have boards with separate digital and analog connectors
|
|
* with a shared ddc line (often vga + hdmi)
|
|
*/
|
|
if (amdgpu_connector->use_digital && amdgpu_connector->shared_ddc) {
|
|
amdgpu_connector_free_edid(connector);
|
|
ret = connector_status_disconnected;
|
|
} else {
|
|
ret = connector_status_connected;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
/* if we aren't forcing don't do destructive polling */
|
|
if (!force) {
|
|
/* only return the previous status if we last
|
|
* detected a monitor via load.
|
|
*/
|
|
if (amdgpu_connector->detected_by_load)
|
|
ret = connector->status;
|
|
goto out;
|
|
}
|
|
|
|
if (amdgpu_connector->dac_load_detect && encoder) {
|
|
encoder_funcs = encoder->helper_private;
|
|
ret = encoder_funcs->detect(encoder, connector);
|
|
if (ret != connector_status_disconnected)
|
|
amdgpu_connector->detected_by_load = true;
|
|
}
|
|
}
|
|
|
|
amdgpu_connector_update_scratch_regs(connector, ret);
|
|
|
|
out:
|
|
pm_runtime_mark_last_busy(connector->dev->dev);
|
|
pm_runtime_put_autosuspend(connector->dev->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs amdgpu_connector_vga_helper_funcs = {
|
|
.get_modes = amdgpu_connector_vga_get_modes,
|
|
.mode_valid = amdgpu_connector_vga_mode_valid,
|
|
.best_encoder = amdgpu_connector_best_single_encoder,
|
|
};
|
|
|
|
static const struct drm_connector_funcs amdgpu_connector_vga_funcs = {
|
|
.dpms = drm_helper_connector_dpms,
|
|
.detect = amdgpu_connector_vga_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.destroy = amdgpu_connector_destroy,
|
|
.set_property = amdgpu_connector_set_property,
|
|
};
|
|
|
|
static bool
|
|
amdgpu_connector_check_hpd_status_unchanged(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
enum drm_connector_status status;
|
|
|
|
if (amdgpu_connector->hpd.hpd != AMDGPU_HPD_NONE) {
|
|
if (amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd))
|
|
status = connector_status_connected;
|
|
else
|
|
status = connector_status_disconnected;
|
|
if (connector->status == status)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* DVI is complicated
|
|
* Do a DDC probe, if DDC probe passes, get the full EDID so
|
|
* we can do analog/digital monitor detection at this point.
|
|
* If the monitor is an analog monitor or we got no DDC,
|
|
* we need to find the DAC encoder object for this connector.
|
|
* If we got no DDC, we do load detection on the DAC encoder object.
|
|
* If we got analog DDC or load detection passes on the DAC encoder
|
|
* we have to check if this analog encoder is shared with anyone else (TV)
|
|
* if its shared we have to set the other connector to disconnected.
|
|
*/
|
|
static enum drm_connector_status
|
|
amdgpu_connector_dvi_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct drm_encoder *encoder = NULL;
|
|
const struct drm_encoder_helper_funcs *encoder_funcs;
|
|
int i, r;
|
|
enum drm_connector_status ret = connector_status_disconnected;
|
|
bool dret = false, broken_edid = false;
|
|
|
|
r = pm_runtime_get_sync(connector->dev->dev);
|
|
if (r < 0)
|
|
return connector_status_disconnected;
|
|
|
|
if (!force && amdgpu_connector_check_hpd_status_unchanged(connector)) {
|
|
ret = connector->status;
|
|
goto exit;
|
|
}
|
|
|
|
if (amdgpu_connector->ddc_bus)
|
|
dret = amdgpu_ddc_probe(amdgpu_connector, false);
|
|
if (dret) {
|
|
amdgpu_connector->detected_by_load = false;
|
|
amdgpu_connector_free_edid(connector);
|
|
amdgpu_connector_get_edid(connector);
|
|
|
|
if (!amdgpu_connector->edid) {
|
|
DRM_ERROR("%s: probed a monitor but no|invalid EDID\n",
|
|
connector->name);
|
|
ret = connector_status_connected;
|
|
broken_edid = true; /* defer use_digital to later */
|
|
} else {
|
|
amdgpu_connector->use_digital =
|
|
!!(amdgpu_connector->edid->input & DRM_EDID_INPUT_DIGITAL);
|
|
|
|
/* some oems have boards with separate digital and analog connectors
|
|
* with a shared ddc line (often vga + hdmi)
|
|
*/
|
|
if ((!amdgpu_connector->use_digital) && amdgpu_connector->shared_ddc) {
|
|
amdgpu_connector_free_edid(connector);
|
|
ret = connector_status_disconnected;
|
|
} else {
|
|
ret = connector_status_connected;
|
|
}
|
|
|
|
/* This gets complicated. We have boards with VGA + HDMI with a
|
|
* shared DDC line and we have boards with DVI-D + HDMI with a shared
|
|
* DDC line. The latter is more complex because with DVI<->HDMI adapters
|
|
* you don't really know what's connected to which port as both are digital.
|
|
*/
|
|
if (amdgpu_connector->shared_ddc && (ret == connector_status_connected)) {
|
|
struct drm_connector *list_connector;
|
|
struct amdgpu_connector *list_amdgpu_connector;
|
|
list_for_each_entry(list_connector, &dev->mode_config.connector_list, head) {
|
|
if (connector == list_connector)
|
|
continue;
|
|
list_amdgpu_connector = to_amdgpu_connector(list_connector);
|
|
if (list_amdgpu_connector->shared_ddc &&
|
|
(list_amdgpu_connector->ddc_bus->rec.i2c_id ==
|
|
amdgpu_connector->ddc_bus->rec.i2c_id)) {
|
|
/* cases where both connectors are digital */
|
|
if (list_connector->connector_type != DRM_MODE_CONNECTOR_VGA) {
|
|
/* hpd is our only option in this case */
|
|
if (!amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) {
|
|
amdgpu_connector_free_edid(connector);
|
|
ret = connector_status_disconnected;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((ret == connector_status_connected) && (amdgpu_connector->use_digital == true))
|
|
goto out;
|
|
|
|
/* DVI-D and HDMI-A are digital only */
|
|
if ((connector->connector_type == DRM_MODE_CONNECTOR_DVID) ||
|
|
(connector->connector_type == DRM_MODE_CONNECTOR_HDMIA))
|
|
goto out;
|
|
|
|
/* if we aren't forcing don't do destructive polling */
|
|
if (!force) {
|
|
/* only return the previous status if we last
|
|
* detected a monitor via load.
|
|
*/
|
|
if (amdgpu_connector->detected_by_load)
|
|
ret = connector->status;
|
|
goto out;
|
|
}
|
|
|
|
/* find analog encoder */
|
|
if (amdgpu_connector->dac_load_detect) {
|
|
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
|
|
if (connector->encoder_ids[i] == 0)
|
|
break;
|
|
|
|
encoder = drm_encoder_find(connector->dev, connector->encoder_ids[i]);
|
|
if (!encoder)
|
|
continue;
|
|
|
|
if (encoder->encoder_type != DRM_MODE_ENCODER_DAC &&
|
|
encoder->encoder_type != DRM_MODE_ENCODER_TVDAC)
|
|
continue;
|
|
|
|
encoder_funcs = encoder->helper_private;
|
|
if (encoder_funcs->detect) {
|
|
if (!broken_edid) {
|
|
if (ret != connector_status_connected) {
|
|
/* deal with analog monitors without DDC */
|
|
ret = encoder_funcs->detect(encoder, connector);
|
|
if (ret == connector_status_connected) {
|
|
amdgpu_connector->use_digital = false;
|
|
}
|
|
if (ret != connector_status_disconnected)
|
|
amdgpu_connector->detected_by_load = true;
|
|
}
|
|
} else {
|
|
enum drm_connector_status lret;
|
|
/* assume digital unless load detected otherwise */
|
|
amdgpu_connector->use_digital = true;
|
|
lret = encoder_funcs->detect(encoder, connector);
|
|
DRM_DEBUG_KMS("load_detect %x returned: %x\n",encoder->encoder_type,lret);
|
|
if (lret == connector_status_connected)
|
|
amdgpu_connector->use_digital = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
/* updated in get modes as well since we need to know if it's analog or digital */
|
|
amdgpu_connector_update_scratch_regs(connector, ret);
|
|
|
|
exit:
|
|
pm_runtime_mark_last_busy(connector->dev->dev);
|
|
pm_runtime_put_autosuspend(connector->dev->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* okay need to be smart in here about which encoder to pick */
|
|
static struct drm_encoder *
|
|
amdgpu_connector_dvi_encoder(struct drm_connector *connector)
|
|
{
|
|
int enc_id = connector->encoder_ids[0];
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct drm_encoder *encoder;
|
|
int i;
|
|
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
|
|
if (connector->encoder_ids[i] == 0)
|
|
break;
|
|
|
|
encoder = drm_encoder_find(connector->dev, connector->encoder_ids[i]);
|
|
if (!encoder)
|
|
continue;
|
|
|
|
if (amdgpu_connector->use_digital == true) {
|
|
if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS)
|
|
return encoder;
|
|
} else {
|
|
if (encoder->encoder_type == DRM_MODE_ENCODER_DAC ||
|
|
encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
|
|
return encoder;
|
|
}
|
|
}
|
|
|
|
/* see if we have a default encoder TODO */
|
|
|
|
/* then check use digitial */
|
|
/* pick the first one */
|
|
if (enc_id)
|
|
return drm_encoder_find(connector->dev, enc_id);
|
|
return NULL;
|
|
}
|
|
|
|
static void amdgpu_connector_dvi_force(struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
if (connector->force == DRM_FORCE_ON)
|
|
amdgpu_connector->use_digital = false;
|
|
if (connector->force == DRM_FORCE_ON_DIGITAL)
|
|
amdgpu_connector->use_digital = true;
|
|
}
|
|
|
|
static int amdgpu_connector_dvi_mode_valid(struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
/* XXX check mode bandwidth */
|
|
|
|
if (amdgpu_connector->use_digital && (mode->clock > 165000)) {
|
|
if ((amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_DUAL_LINK_DVI_I) ||
|
|
(amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_DUAL_LINK_DVI_D) ||
|
|
(amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_HDMI_TYPE_B)) {
|
|
return MODE_OK;
|
|
} else if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
|
|
/* HDMI 1.3+ supports max clock of 340 Mhz */
|
|
if (mode->clock > 340000)
|
|
return MODE_CLOCK_HIGH;
|
|
else
|
|
return MODE_OK;
|
|
} else {
|
|
return MODE_CLOCK_HIGH;
|
|
}
|
|
}
|
|
|
|
/* check against the max pixel clock */
|
|
if ((mode->clock / 10) > adev->clock.max_pixel_clock)
|
|
return MODE_CLOCK_HIGH;
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs amdgpu_connector_dvi_helper_funcs = {
|
|
.get_modes = amdgpu_connector_vga_get_modes,
|
|
.mode_valid = amdgpu_connector_dvi_mode_valid,
|
|
.best_encoder = amdgpu_connector_dvi_encoder,
|
|
};
|
|
|
|
static const struct drm_connector_funcs amdgpu_connector_dvi_funcs = {
|
|
.dpms = drm_helper_connector_dpms,
|
|
.detect = amdgpu_connector_dvi_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.set_property = amdgpu_connector_set_property,
|
|
.destroy = amdgpu_connector_destroy,
|
|
.force = amdgpu_connector_dvi_force,
|
|
};
|
|
|
|
static int amdgpu_connector_dp_get_modes(struct drm_connector *connector)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct amdgpu_connector_atom_dig *amdgpu_dig_connector = amdgpu_connector->con_priv;
|
|
struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector);
|
|
int ret;
|
|
|
|
if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) ||
|
|
(connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
|
|
struct drm_display_mode *mode;
|
|
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
|
|
if (!amdgpu_dig_connector->edp_on)
|
|
amdgpu_atombios_encoder_set_edp_panel_power(connector,
|
|
ATOM_TRANSMITTER_ACTION_POWER_ON);
|
|
amdgpu_connector_get_edid(connector);
|
|
ret = amdgpu_connector_ddc_get_modes(connector);
|
|
if (!amdgpu_dig_connector->edp_on)
|
|
amdgpu_atombios_encoder_set_edp_panel_power(connector,
|
|
ATOM_TRANSMITTER_ACTION_POWER_OFF);
|
|
} else {
|
|
/* need to setup ddc on the bridge */
|
|
if (amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) !=
|
|
ENCODER_OBJECT_ID_NONE) {
|
|
if (encoder)
|
|
amdgpu_atombios_encoder_setup_ext_encoder_ddc(encoder);
|
|
}
|
|
amdgpu_connector_get_edid(connector);
|
|
ret = amdgpu_connector_ddc_get_modes(connector);
|
|
}
|
|
|
|
if (ret > 0) {
|
|
if (encoder) {
|
|
amdgpu_connector_fixup_lcd_native_mode(encoder, connector);
|
|
/* add scaled modes */
|
|
amdgpu_connector_add_common_modes(encoder, connector);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
/* we have no EDID modes */
|
|
mode = amdgpu_connector_lcd_native_mode(encoder);
|
|
if (mode) {
|
|
ret = 1;
|
|
drm_mode_probed_add(connector, mode);
|
|
/* add the width/height from vbios tables if available */
|
|
connector->display_info.width_mm = mode->width_mm;
|
|
connector->display_info.height_mm = mode->height_mm;
|
|
/* add scaled modes */
|
|
amdgpu_connector_add_common_modes(encoder, connector);
|
|
}
|
|
} else {
|
|
/* need to setup ddc on the bridge */
|
|
if (amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) !=
|
|
ENCODER_OBJECT_ID_NONE) {
|
|
if (encoder)
|
|
amdgpu_atombios_encoder_setup_ext_encoder_ddc(encoder);
|
|
}
|
|
amdgpu_connector_get_edid(connector);
|
|
ret = amdgpu_connector_ddc_get_modes(connector);
|
|
|
|
amdgpu_get_native_mode(connector);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
u16 amdgpu_connector_encoder_get_dp_bridge_encoder_id(struct drm_connector *connector)
|
|
{
|
|
struct drm_encoder *encoder;
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
int i;
|
|
|
|
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
|
|
if (connector->encoder_ids[i] == 0)
|
|
break;
|
|
|
|
encoder = drm_encoder_find(connector->dev,
|
|
connector->encoder_ids[i]);
|
|
if (!encoder)
|
|
continue;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
switch (amdgpu_encoder->encoder_id) {
|
|
case ENCODER_OBJECT_ID_TRAVIS:
|
|
case ENCODER_OBJECT_ID_NUTMEG:
|
|
return amdgpu_encoder->encoder_id;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ENCODER_OBJECT_ID_NONE;
|
|
}
|
|
|
|
static bool amdgpu_connector_encoder_is_hbr2(struct drm_connector *connector)
|
|
{
|
|
struct drm_encoder *encoder;
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
int i;
|
|
bool found = false;
|
|
|
|
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
|
|
if (connector->encoder_ids[i] == 0)
|
|
break;
|
|
encoder = drm_encoder_find(connector->dev,
|
|
connector->encoder_ids[i]);
|
|
if (!encoder)
|
|
continue;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
if (amdgpu_encoder->caps & ATOM_ENCODER_CAP_RECORD_HBR2)
|
|
found = true;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool amdgpu_connector_is_dp12_capable(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
if ((adev->clock.default_dispclk >= 53900) &&
|
|
amdgpu_connector_encoder_is_hbr2(connector)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
amdgpu_connector_dp_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
enum drm_connector_status ret = connector_status_disconnected;
|
|
struct amdgpu_connector_atom_dig *amdgpu_dig_connector = amdgpu_connector->con_priv;
|
|
struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector);
|
|
int r;
|
|
|
|
r = pm_runtime_get_sync(connector->dev->dev);
|
|
if (r < 0)
|
|
return connector_status_disconnected;
|
|
|
|
if (!force && amdgpu_connector_check_hpd_status_unchanged(connector)) {
|
|
ret = connector->status;
|
|
goto out;
|
|
}
|
|
|
|
amdgpu_connector_free_edid(connector);
|
|
|
|
if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) ||
|
|
(connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
|
|
if (encoder) {
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
|
|
|
|
/* check if panel is valid */
|
|
if (native_mode->hdisplay >= 320 && native_mode->vdisplay >= 240)
|
|
ret = connector_status_connected;
|
|
}
|
|
/* eDP is always DP */
|
|
amdgpu_dig_connector->dp_sink_type = CONNECTOR_OBJECT_ID_DISPLAYPORT;
|
|
if (!amdgpu_dig_connector->edp_on)
|
|
amdgpu_atombios_encoder_set_edp_panel_power(connector,
|
|
ATOM_TRANSMITTER_ACTION_POWER_ON);
|
|
if (!amdgpu_atombios_dp_get_dpcd(amdgpu_connector))
|
|
ret = connector_status_connected;
|
|
if (!amdgpu_dig_connector->edp_on)
|
|
amdgpu_atombios_encoder_set_edp_panel_power(connector,
|
|
ATOM_TRANSMITTER_ACTION_POWER_OFF);
|
|
} else if (amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) !=
|
|
ENCODER_OBJECT_ID_NONE) {
|
|
/* DP bridges are always DP */
|
|
amdgpu_dig_connector->dp_sink_type = CONNECTOR_OBJECT_ID_DISPLAYPORT;
|
|
/* get the DPCD from the bridge */
|
|
amdgpu_atombios_dp_get_dpcd(amdgpu_connector);
|
|
|
|
if (encoder) {
|
|
/* setup ddc on the bridge */
|
|
amdgpu_atombios_encoder_setup_ext_encoder_ddc(encoder);
|
|
/* bridge chips are always aux */
|
|
if (amdgpu_ddc_probe(amdgpu_connector, true)) /* try DDC */
|
|
ret = connector_status_connected;
|
|
else if (amdgpu_connector->dac_load_detect) { /* try load detection */
|
|
const struct drm_encoder_helper_funcs *encoder_funcs = encoder->helper_private;
|
|
ret = encoder_funcs->detect(encoder, connector);
|
|
}
|
|
}
|
|
} else {
|
|
amdgpu_dig_connector->dp_sink_type =
|
|
amdgpu_atombios_dp_get_sinktype(amdgpu_connector);
|
|
if (amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) {
|
|
ret = connector_status_connected;
|
|
if (amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT)
|
|
amdgpu_atombios_dp_get_dpcd(amdgpu_connector);
|
|
} else {
|
|
if (amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) {
|
|
if (!amdgpu_atombios_dp_get_dpcd(amdgpu_connector))
|
|
ret = connector_status_connected;
|
|
} else {
|
|
/* try non-aux ddc (DP to DVI/HDMI/etc. adapter) */
|
|
if (amdgpu_ddc_probe(amdgpu_connector, false))
|
|
ret = connector_status_connected;
|
|
}
|
|
}
|
|
}
|
|
|
|
amdgpu_connector_update_scratch_regs(connector, ret);
|
|
out:
|
|
pm_runtime_mark_last_busy(connector->dev->dev);
|
|
pm_runtime_put_autosuspend(connector->dev->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int amdgpu_connector_dp_mode_valid(struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
struct amdgpu_connector_atom_dig *amdgpu_dig_connector = amdgpu_connector->con_priv;
|
|
|
|
/* XXX check mode bandwidth */
|
|
|
|
if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) ||
|
|
(connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
|
|
struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector);
|
|
|
|
if ((mode->hdisplay < 320) || (mode->vdisplay < 240))
|
|
return MODE_PANEL;
|
|
|
|
if (encoder) {
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
|
|
|
|
/* AVIVO hardware supports downscaling modes larger than the panel
|
|
* to the panel size, but I'm not sure this is desirable.
|
|
*/
|
|
if ((mode->hdisplay > native_mode->hdisplay) ||
|
|
(mode->vdisplay > native_mode->vdisplay))
|
|
return MODE_PANEL;
|
|
|
|
/* if scaling is disabled, block non-native modes */
|
|
if (amdgpu_encoder->rmx_type == RMX_OFF) {
|
|
if ((mode->hdisplay != native_mode->hdisplay) ||
|
|
(mode->vdisplay != native_mode->vdisplay))
|
|
return MODE_PANEL;
|
|
}
|
|
}
|
|
return MODE_OK;
|
|
} else {
|
|
if ((amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) ||
|
|
(amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP)) {
|
|
return amdgpu_atombios_dp_mode_valid_helper(connector, mode);
|
|
} else {
|
|
if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
|
|
/* HDMI 1.3+ supports max clock of 340 Mhz */
|
|
if (mode->clock > 340000)
|
|
return MODE_CLOCK_HIGH;
|
|
} else {
|
|
if (mode->clock > 165000)
|
|
return MODE_CLOCK_HIGH;
|
|
}
|
|
}
|
|
}
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs amdgpu_connector_dp_helper_funcs = {
|
|
.get_modes = amdgpu_connector_dp_get_modes,
|
|
.mode_valid = amdgpu_connector_dp_mode_valid,
|
|
.best_encoder = amdgpu_connector_dvi_encoder,
|
|
};
|
|
|
|
static const struct drm_connector_funcs amdgpu_connector_dp_funcs = {
|
|
.dpms = drm_helper_connector_dpms,
|
|
.detect = amdgpu_connector_dp_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.set_property = amdgpu_connector_set_property,
|
|
.destroy = amdgpu_connector_destroy,
|
|
.force = amdgpu_connector_dvi_force,
|
|
};
|
|
|
|
static const struct drm_connector_funcs amdgpu_connector_edp_funcs = {
|
|
.dpms = drm_helper_connector_dpms,
|
|
.detect = amdgpu_connector_dp_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.set_property = amdgpu_connector_set_lcd_property,
|
|
.destroy = amdgpu_connector_destroy,
|
|
.force = amdgpu_connector_dvi_force,
|
|
};
|
|
|
|
static struct drm_encoder *
|
|
amdgpu_connector_virtual_encoder(struct drm_connector *connector)
|
|
{
|
|
int enc_id = connector->encoder_ids[0];
|
|
struct drm_encoder *encoder;
|
|
int i;
|
|
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
|
|
if (connector->encoder_ids[i] == 0)
|
|
break;
|
|
|
|
encoder = drm_encoder_find(connector->dev, connector->encoder_ids[i]);
|
|
if (!encoder)
|
|
continue;
|
|
|
|
if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
|
|
return encoder;
|
|
}
|
|
|
|
/* pick the first one */
|
|
if (enc_id)
|
|
return drm_encoder_find(connector->dev, enc_id);
|
|
return NULL;
|
|
}
|
|
|
|
static int amdgpu_connector_virtual_get_modes(struct drm_connector *connector)
|
|
{
|
|
struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector);
|
|
|
|
if (encoder) {
|
|
amdgpu_connector_add_common_modes(encoder, connector);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amdgpu_connector_virtual_mode_valid(struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
return MODE_OK;
|
|
}
|
|
|
|
static int
|
|
amdgpu_connector_virtual_dpms(struct drm_connector *connector, int mode)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
|
|
amdgpu_connector_virtual_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
return connector_status_connected;
|
|
}
|
|
|
|
static int
|
|
amdgpu_connector_virtual_set_property(struct drm_connector *connector,
|
|
struct drm_property *property,
|
|
uint64_t val)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void amdgpu_connector_virtual_force(struct drm_connector *connector)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs amdgpu_connector_virtual_helper_funcs = {
|
|
.get_modes = amdgpu_connector_virtual_get_modes,
|
|
.mode_valid = amdgpu_connector_virtual_mode_valid,
|
|
.best_encoder = amdgpu_connector_virtual_encoder,
|
|
};
|
|
|
|
static const struct drm_connector_funcs amdgpu_connector_virtual_funcs = {
|
|
.dpms = amdgpu_connector_virtual_dpms,
|
|
.detect = amdgpu_connector_virtual_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.set_property = amdgpu_connector_virtual_set_property,
|
|
.destroy = amdgpu_connector_destroy,
|
|
.force = amdgpu_connector_virtual_force,
|
|
};
|
|
|
|
void
|
|
amdgpu_connector_add(struct amdgpu_device *adev,
|
|
uint32_t connector_id,
|
|
uint32_t supported_device,
|
|
int connector_type,
|
|
struct amdgpu_i2c_bus_rec *i2c_bus,
|
|
uint16_t connector_object_id,
|
|
struct amdgpu_hpd *hpd,
|
|
struct amdgpu_router *router)
|
|
{
|
|
struct drm_device *dev = adev->ddev;
|
|
struct drm_connector *connector;
|
|
struct amdgpu_connector *amdgpu_connector;
|
|
struct amdgpu_connector_atom_dig *amdgpu_dig_connector;
|
|
struct drm_encoder *encoder;
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
uint32_t subpixel_order = SubPixelNone;
|
|
bool shared_ddc = false;
|
|
bool is_dp_bridge = false;
|
|
bool has_aux = false;
|
|
|
|
if (connector_type == DRM_MODE_CONNECTOR_Unknown)
|
|
return;
|
|
|
|
/* see if we already added it */
|
|
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
|
amdgpu_connector = to_amdgpu_connector(connector);
|
|
if (amdgpu_connector->connector_id == connector_id) {
|
|
amdgpu_connector->devices |= supported_device;
|
|
return;
|
|
}
|
|
if (amdgpu_connector->ddc_bus && i2c_bus->valid) {
|
|
if (amdgpu_connector->ddc_bus->rec.i2c_id == i2c_bus->i2c_id) {
|
|
amdgpu_connector->shared_ddc = true;
|
|
shared_ddc = true;
|
|
}
|
|
if (amdgpu_connector->router_bus && router->ddc_valid &&
|
|
(amdgpu_connector->router.router_id == router->router_id)) {
|
|
amdgpu_connector->shared_ddc = false;
|
|
shared_ddc = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check if it's a dp bridge */
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
if (amdgpu_encoder->devices & supported_device) {
|
|
switch (amdgpu_encoder->encoder_id) {
|
|
case ENCODER_OBJECT_ID_TRAVIS:
|
|
case ENCODER_OBJECT_ID_NUTMEG:
|
|
is_dp_bridge = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
amdgpu_connector = kzalloc(sizeof(struct amdgpu_connector), GFP_KERNEL);
|
|
if (!amdgpu_connector)
|
|
return;
|
|
|
|
connector = &amdgpu_connector->base;
|
|
|
|
amdgpu_connector->connector_id = connector_id;
|
|
amdgpu_connector->devices = supported_device;
|
|
amdgpu_connector->shared_ddc = shared_ddc;
|
|
amdgpu_connector->connector_object_id = connector_object_id;
|
|
amdgpu_connector->hpd = *hpd;
|
|
|
|
amdgpu_connector->router = *router;
|
|
if (router->ddc_valid || router->cd_valid) {
|
|
amdgpu_connector->router_bus = amdgpu_i2c_lookup(adev, &router->i2c_info);
|
|
if (!amdgpu_connector->router_bus)
|
|
DRM_ERROR("Failed to assign router i2c bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
|
|
if (is_dp_bridge) {
|
|
amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL);
|
|
if (!amdgpu_dig_connector)
|
|
goto failed;
|
|
amdgpu_connector->con_priv = amdgpu_dig_connector;
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (amdgpu_connector->ddc_bus)
|
|
has_aux = true;
|
|
else
|
|
DRM_ERROR("DP: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
switch (connector_type) {
|
|
case DRM_MODE_CONNECTOR_VGA:
|
|
case DRM_MODE_CONNECTOR_DVIA:
|
|
default:
|
|
drm_connector_init(dev, &amdgpu_connector->base,
|
|
&amdgpu_connector_dp_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base,
|
|
&amdgpu_connector_dp_helper_funcs);
|
|
connector->interlace_allowed = true;
|
|
connector->doublescan_allowed = true;
|
|
amdgpu_connector->dac_load_detect = true;
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.load_detect_property,
|
|
1);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_NONE);
|
|
break;
|
|
case DRM_MODE_CONNECTOR_DVII:
|
|
case DRM_MODE_CONNECTOR_DVID:
|
|
case DRM_MODE_CONNECTOR_HDMIA:
|
|
case DRM_MODE_CONNECTOR_HDMIB:
|
|
case DRM_MODE_CONNECTOR_DisplayPort:
|
|
drm_connector_init(dev, &amdgpu_connector->base,
|
|
&amdgpu_connector_dp_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base,
|
|
&amdgpu_connector_dp_helper_funcs);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_property,
|
|
UNDERSCAN_OFF);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_hborder_property,
|
|
0);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_vborder_property,
|
|
0);
|
|
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_NONE);
|
|
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.dither_property,
|
|
AMDGPU_FMT_DITHER_DISABLE);
|
|
|
|
if (amdgpu_audio != 0)
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.audio_property,
|
|
AMDGPU_AUDIO_AUTO);
|
|
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
connector->interlace_allowed = true;
|
|
if (connector_type == DRM_MODE_CONNECTOR_HDMIB)
|
|
connector->doublescan_allowed = true;
|
|
else
|
|
connector->doublescan_allowed = false;
|
|
if (connector_type == DRM_MODE_CONNECTOR_DVII) {
|
|
amdgpu_connector->dac_load_detect = true;
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.load_detect_property,
|
|
1);
|
|
}
|
|
break;
|
|
case DRM_MODE_CONNECTOR_LVDS:
|
|
case DRM_MODE_CONNECTOR_eDP:
|
|
drm_connector_init(dev, &amdgpu_connector->base,
|
|
&amdgpu_connector_edp_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base,
|
|
&amdgpu_connector_dp_helper_funcs);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_FULLSCREEN);
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
connector->interlace_allowed = false;
|
|
connector->doublescan_allowed = false;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (connector_type) {
|
|
case DRM_MODE_CONNECTOR_VGA:
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_vga_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_vga_helper_funcs);
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (!amdgpu_connector->ddc_bus)
|
|
DRM_ERROR("VGA: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
amdgpu_connector->dac_load_detect = true;
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.load_detect_property,
|
|
1);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_NONE);
|
|
/* no HPD on analog connectors */
|
|
amdgpu_connector->hpd.hpd = AMDGPU_HPD_NONE;
|
|
connector->interlace_allowed = true;
|
|
connector->doublescan_allowed = true;
|
|
break;
|
|
case DRM_MODE_CONNECTOR_DVIA:
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_vga_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_vga_helper_funcs);
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (!amdgpu_connector->ddc_bus)
|
|
DRM_ERROR("DVIA: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
amdgpu_connector->dac_load_detect = true;
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.load_detect_property,
|
|
1);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_NONE);
|
|
/* no HPD on analog connectors */
|
|
amdgpu_connector->hpd.hpd = AMDGPU_HPD_NONE;
|
|
connector->interlace_allowed = true;
|
|
connector->doublescan_allowed = true;
|
|
break;
|
|
case DRM_MODE_CONNECTOR_DVII:
|
|
case DRM_MODE_CONNECTOR_DVID:
|
|
amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL);
|
|
if (!amdgpu_dig_connector)
|
|
goto failed;
|
|
amdgpu_connector->con_priv = amdgpu_dig_connector;
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_dvi_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dvi_helper_funcs);
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (!amdgpu_connector->ddc_bus)
|
|
DRM_ERROR("DVI: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.coherent_mode_property,
|
|
1);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_property,
|
|
UNDERSCAN_OFF);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_hborder_property,
|
|
0);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_vborder_property,
|
|
0);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_NONE);
|
|
|
|
if (amdgpu_audio != 0) {
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.audio_property,
|
|
AMDGPU_AUDIO_AUTO);
|
|
}
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.dither_property,
|
|
AMDGPU_FMT_DITHER_DISABLE);
|
|
if (connector_type == DRM_MODE_CONNECTOR_DVII) {
|
|
amdgpu_connector->dac_load_detect = true;
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.load_detect_property,
|
|
1);
|
|
}
|
|
connector->interlace_allowed = true;
|
|
if (connector_type == DRM_MODE_CONNECTOR_DVII)
|
|
connector->doublescan_allowed = true;
|
|
else
|
|
connector->doublescan_allowed = false;
|
|
break;
|
|
case DRM_MODE_CONNECTOR_HDMIA:
|
|
case DRM_MODE_CONNECTOR_HDMIB:
|
|
amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL);
|
|
if (!amdgpu_dig_connector)
|
|
goto failed;
|
|
amdgpu_connector->con_priv = amdgpu_dig_connector;
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_dvi_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dvi_helper_funcs);
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (!amdgpu_connector->ddc_bus)
|
|
DRM_ERROR("HDMI: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.coherent_mode_property,
|
|
1);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_property,
|
|
UNDERSCAN_OFF);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_hborder_property,
|
|
0);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_vborder_property,
|
|
0);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_NONE);
|
|
if (amdgpu_audio != 0) {
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.audio_property,
|
|
AMDGPU_AUDIO_AUTO);
|
|
}
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.dither_property,
|
|
AMDGPU_FMT_DITHER_DISABLE);
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
connector->interlace_allowed = true;
|
|
if (connector_type == DRM_MODE_CONNECTOR_HDMIB)
|
|
connector->doublescan_allowed = true;
|
|
else
|
|
connector->doublescan_allowed = false;
|
|
break;
|
|
case DRM_MODE_CONNECTOR_DisplayPort:
|
|
amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL);
|
|
if (!amdgpu_dig_connector)
|
|
goto failed;
|
|
amdgpu_connector->con_priv = amdgpu_dig_connector;
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_dp_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dp_helper_funcs);
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (amdgpu_connector->ddc_bus)
|
|
has_aux = true;
|
|
else
|
|
DRM_ERROR("DP: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.coherent_mode_property,
|
|
1);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_property,
|
|
UNDERSCAN_OFF);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_hborder_property,
|
|
0);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.underscan_vborder_property,
|
|
0);
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_NONE);
|
|
if (amdgpu_audio != 0) {
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.audio_property,
|
|
AMDGPU_AUDIO_AUTO);
|
|
}
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
adev->mode_info.dither_property,
|
|
AMDGPU_FMT_DITHER_DISABLE);
|
|
connector->interlace_allowed = true;
|
|
/* in theory with a DP to VGA converter... */
|
|
connector->doublescan_allowed = false;
|
|
break;
|
|
case DRM_MODE_CONNECTOR_eDP:
|
|
amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL);
|
|
if (!amdgpu_dig_connector)
|
|
goto failed;
|
|
amdgpu_connector->con_priv = amdgpu_dig_connector;
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_edp_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dp_helper_funcs);
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (amdgpu_connector->ddc_bus)
|
|
has_aux = true;
|
|
else
|
|
DRM_ERROR("DP: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_FULLSCREEN);
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
connector->interlace_allowed = false;
|
|
connector->doublescan_allowed = false;
|
|
break;
|
|
case DRM_MODE_CONNECTOR_LVDS:
|
|
amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL);
|
|
if (!amdgpu_dig_connector)
|
|
goto failed;
|
|
amdgpu_connector->con_priv = amdgpu_dig_connector;
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_lvds_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_lvds_helper_funcs);
|
|
if (i2c_bus->valid) {
|
|
amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus);
|
|
if (!amdgpu_connector->ddc_bus)
|
|
DRM_ERROR("LVDS: Failed to assign ddc bus! Check dmesg for i2c errors.\n");
|
|
}
|
|
drm_object_attach_property(&amdgpu_connector->base.base,
|
|
dev->mode_config.scaling_mode_property,
|
|
DRM_MODE_SCALE_FULLSCREEN);
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
connector->interlace_allowed = false;
|
|
connector->doublescan_allowed = false;
|
|
break;
|
|
case DRM_MODE_CONNECTOR_VIRTUAL:
|
|
amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL);
|
|
if (!amdgpu_dig_connector)
|
|
goto failed;
|
|
amdgpu_connector->con_priv = amdgpu_dig_connector;
|
|
drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_virtual_funcs, connector_type);
|
|
drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_virtual_helper_funcs);
|
|
subpixel_order = SubPixelHorizontalRGB;
|
|
connector->interlace_allowed = false;
|
|
connector->doublescan_allowed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (amdgpu_connector->hpd.hpd == AMDGPU_HPD_NONE) {
|
|
if (i2c_bus->valid) {
|
|
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
|
|
DRM_CONNECTOR_POLL_DISCONNECT;
|
|
}
|
|
} else
|
|
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
|
|
|
connector->display_info.subpixel_order = subpixel_order;
|
|
drm_connector_register(connector);
|
|
|
|
if (has_aux)
|
|
amdgpu_atombios_dp_aux_init(amdgpu_connector);
|
|
|
|
return;
|
|
|
|
failed:
|
|
drm_connector_cleanup(connector);
|
|
kfree(connector);
|
|
}
|