/* * 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/display/drm_dp_helper.h>#include <drm/drm_crtc_helper.h>#include <drm/drm_edid.h>#include <drm/drm_modeset_helper_vtables.h>#include <drm/drm_probe_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 "amdgpu_display.h" #include <linux/pm_runtime.h> void amdgpu_connector_hotplug(struct drm_connector *connector){ struct drm_device *dev = connector->dev; struct amdgpu_device *adev = drm_to_adev(dev); 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 && amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd) && amdgpu_atombios_dp_needs_link_train(amdgpu_connector)) { /* Don't start link training before we have the DPCD */ if (amdgpu_atombios_dp_get_dpcd(amdgpu_connector)) return; /* Turn the connector off and back on immediately, which * will trigger link training */ drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); } }} 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 int 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 (connector->display_info.is_hdmi) { if (connector->display_info.bpc) bpc = connector->display_info.bpc; } } break; case DRM_MODE_CONNECTOR_DVID: case DRM_MODE_CONNECTOR_HDMIA: if (connector->display_info.is_hdmi) { 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) || connector->display_info.is_hdmi) { 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 (connector->display_info.is_hdmi) { /* * 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->display_info.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->display_info.max_tmds_clock; 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_rgb444_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 voidamdgpu_connector_update_scratch_regs(struct drm_connector *connector, enum drm_connector_status status){ struct drm_encoder *best_encoder; struct drm_encoder *encoder; const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private; bool connected; best_encoder = connector_funcs->best_encoder(connector); drm_connector_for_each_possible_encoder(connector, encoder) { 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; drm_connector_for_each_possible_encoder(connector, encoder) { if (encoder->encoder_type == encoder_type) return encoder; } return NULL;} static const struct drm_edid *amdgpu_connector_get_hardcoded_edid(struct amdgpu_device *adev){ return drm_edid_dup(adev->mode_info.bios_hardcoded_edid);} static void amdgpu_connector_get_edid(struct drm_connector *connector){ struct drm_device *dev = connector->dev; struct amdgpu_device *adev = drm_to_adev(dev); 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_edid_read_ddc(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_edid_read_ddc(connector, &amdgpu_connector->ddc_bus->aux.ddc); else if (amdgpu_connector->ddc_bus) amdgpu_connector->edid = drm_edid_read_ddc(connector, &amdgpu_connector->ddc_bus->adapter); } else if (amdgpu_connector->ddc_bus) { amdgpu_connector->edid = drm_edid_read_ddc(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); drm_edid_connector_update(connector, amdgpu_connector->edid); } }} 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_edid_connector_update(connector, amdgpu_connector->edid); ret = drm_edid_connector_add_modes(connector); return ret; } drm_edid_connector_update(connector, NULL); return 0;} static struct drm_encoder *amdgpu_connector_best_single_encoder(struct drm_connector *connector){ struct drm_encoder *encoder; /* pick the first one */ drm_connector_for_each_possible_encoder(connector, encoder) return encoder; 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); if (!mode) return NULL; 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); if (!mode) return NULL; 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; int n; struct mode_size { char name[DRM_DISPLAY_MODE_LEN]; int w; int h; } common_modes[] = { { "640x480", 640, 480}, { "800x600", 800, 600}, { "1024x768", 1024, 768}, { "1280x720", 1280, 720}, { "1280x800", 1280, 800}, {"1280x1024", 1280, 1024}, { "1440x900", 1440, 900}, {"1680x1050", 1680, 1050}, {"1600x1200", 1600, 1200}, {"1920x1080", 1920, 1080}, {"1920x1200", 1920, 1200} }; n = ARRAY_SIZE(common_modes); for (i = 0; i < n; 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; } mode = drm_cvt_mode(dev, common_modes[i].w, common_modes[i].h, 60, false, false, false); if (!mode) return; strscpy(mode->name, common_modes[i].name, DRM_DISPLAY_MODE_LEN); 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 = drm_to_adev(dev); 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);Lines 1–500 of 2063. Load more
/* * 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/display/drm_dp_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_probe_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 "amdgpu_display.h" #include <linux/pm_runtime.h> void amdgpu_connector_hotplug(struct drm_connector *connector) { struct drm_device *dev = connector->dev; struct amdgpu_device *adev = drm_to_adev(dev); 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 && amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd) && amdgpu_atombios_dp_needs_link_train(amdgpu_connector)) { /* Don't start link training before we have the DPCD */ if (amdgpu_atombios_dp_get_dpcd(amdgpu_connector)) return; /* Turn the connector off and back on immediately, which * will trigger link training */ drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); } } } 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 int 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 (connector->display_info.is_hdmi) { if (connector->display_info.bpc) bpc = connector->display_info.bpc; } } break; case DRM_MODE_CONNECTOR_DVID: case DRM_MODE_CONNECTOR_HDMIA: if (connector->display_info.is_hdmi) { 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) || connector->display_info.is_hdmi) { 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 (connector->display_info.is_hdmi) { /* * 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->display_info.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->display_info.max_tmds_clock; 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_rgb444_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; struct drm_encoder *encoder; const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private; bool connected; best_encoder = connector_funcs->best_encoder(connector); drm_connector_for_each_possible_encoder(connector, encoder) { 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; drm_connector_for_each_possible_encoder(connector, encoder) { if (encoder->encoder_type == encoder_type) return encoder; } return NULL; } static const struct drm_edid * amdgpu_connector_get_hardcoded_edid(struct amdgpu_device *adev) { return drm_edid_dup(adev->mode_info.bios_hardcoded_edid); } static void amdgpu_connector_get_edid(struct drm_connector *connector) { struct drm_device *dev = connector->dev; struct amdgpu_device *adev = drm_to_adev(dev); 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_edid_read_ddc(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_edid_read_ddc(connector, &amdgpu_connector->ddc_bus->aux.ddc); else if (amdgpu_connector->ddc_bus) amdgpu_connector->edid = drm_edid_read_ddc(connector, &amdgpu_connector->ddc_bus->adapter); } else if (amdgpu_connector->ddc_bus) { amdgpu_connector->edid = drm_edid_read_ddc(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); drm_edid_connector_update(connector, amdgpu_connector->edid); } } } 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_edid_connector_update(connector, amdgpu_connector->edid); ret = drm_edid_connector_add_modes(connector); return ret; } drm_edid_connector_update(connector, NULL); return 0; } static struct drm_encoder * amdgpu_connector_best_single_encoder(struct drm_connector *connector) { struct drm_encoder *encoder; /* pick the first one */ drm_connector_for_each_possible_encoder(connector, encoder) return encoder; 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); if (!mode) return NULL; 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); if (!mode) return NULL; 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; int n; struct mode_size { char name[DRM_DISPLAY_MODE_LEN]; int w; int h; } common_modes[] = { { "640x480", 640, 480}, { "800x600", 800, 600}, { "1024x768", 1024, 768}, { "1280x720", 1280, 720}, { "1280x800", 1280, 800}, {"1280x1024", 1280, 1024}, { "1440x900", 1440, 900}, {"1680x1050", 1680, 1050}, {"1600x1200", 1600, 1200}, {"1920x1080", 1920, 1080}, {"1920x1200", 1920, 1200} }; n = ARRAY_SIZE(common_modes); for (i = 0; i < n; 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; } mode = drm_cvt_mode(dev, common_modes[i].w, common_modes[i].h, 60, false, false, false); if (!mode) return; strscpy(mode->name, common_modes[i].name, DRM_DISPLAY_MODE_LEN); 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 = drm_to_adev(dev); 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);