diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index 8b3d8ed..347e41c 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -654,6 +654,55 @@ static int radeon_vga_mode_valid(struct drm_connector *connector, return MODE_OK; } +static bool radeon_check_hpd_status_unchanged(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct radeon_device *rdev = dev->dev_private; + struct radeon_connector *radeon_connector = to_radeon_connector(connector); + enum drm_connector_status status; + + /* We only trust HPD on R600 and newer ASICS. */ + if (rdev->family >= CHIP_R600 + && radeon_connector->hpd.hpd != RADEON_HPD_NONE) { + if (radeon_hpd_sense(rdev, radeon_connector->hpd.hpd)) + status = connector_status_connected; + else + status = connector_status_disconnected; + + if (connector->status == status) + return true; + } + + return false; +} + +static bool radeon_check_shared_ddc_connected(struct drm_connector *connector) +{ + struct radeon_connector *radeon_connector = to_radeon_connector(connector); + struct radeon_connector *shared_radeon_connector; + struct drm_connector *shared_connector; + struct drm_device *dev; + struct radeon_device *rdev; + + shared_radeon_connector = radeon_connector->shared_ddc_connector; + if (!shared_radeon_connector) + return false; + + shared_connector = &shared_radeon_connector->base; + dev = shared_connector->dev; + rdev = dev->dev_private; + + /* Only check when our shared connector has reliable HPD. */ + if (rdev->family >= CHIP_R600 + && shared_radeon_connector->hpd.hpd != RADEON_HPD_NONE) { + if (shared_connector->status == connector_status_connected + && radeon_hpd_sense(rdev, shared_radeon_connector->hpd.hpd)) + return true; + } + + return false; +} + static enum drm_connector_status radeon_vga_detect(struct drm_connector *connector, bool force) { @@ -665,6 +714,18 @@ radeon_vga_detect(struct drm_connector *connector, bool force) bool dret = false; enum drm_connector_status ret = connector_status_disconnected; + if (!force && radeon_check_hpd_status_unchanged(connector)) + return connector->status; + + if (!force && radeon_check_shared_ddc_connected(connector)) { + /* Drop EDID if disconnected. */ + if (radeon_connector->edid) { + kfree(radeon_connector->edid); + radeon_connector->edid = NULL; + } + goto out; + } + encoder = radeon_best_single_encoder(connector); if (!encoder) ret = connector_status_disconnected; @@ -689,7 +750,7 @@ radeon_vga_detect(struct drm_connector *connector, bool force) /* some oems have boards with separate digital and analog connectors * with a shared ddc line (often vga + hdmi) */ - if (radeon_connector->use_digital && radeon_connector->shared_ddc) { + if (radeon_connector->use_digital && radeon_connector->shared_ddc_connector) { kfree(radeon_connector->edid); radeon_connector->edid = NULL; ret = connector_status_disconnected; @@ -697,6 +758,11 @@ radeon_vga_detect(struct drm_connector *connector, bool force) ret = connector_status_connected; } } else { + /* Drop EDID if disconnected. */ + if (radeon_connector->edid) { + kfree(radeon_connector->edid); + radeon_connector->edid = NULL; + } /* if we aren't forcing don't do destructive polling */ if (!force) { @@ -730,6 +796,7 @@ radeon_vga_detect(struct drm_connector *connector, bool force) ret = connector_status_connected; } +out: radeon_connector_update_scratch_regs(connector, ret); return ret; } @@ -851,6 +918,9 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) enum drm_connector_status ret = connector_status_disconnected; bool dret = false; + if (!force && radeon_check_hpd_status_unchanged(connector)) + return connector->status; + if (radeon_connector->ddc_bus) dret = radeon_ddc_probe(radeon_connector); if (dret) { @@ -877,7 +947,7 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) /* some oems have boards with separate digital and analog connectors * with a shared ddc line (often vga + hdmi) */ - if ((!radeon_connector->use_digital) && radeon_connector->shared_ddc) { + if ((!radeon_connector->use_digital) && radeon_connector->shared_ddc_connector) { kfree(radeon_connector->edid); radeon_connector->edid = NULL; ret = connector_status_disconnected; @@ -889,16 +959,14 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) * 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 (radeon_connector->shared_ddc && (ret == connector_status_connected)) { + if (radeon_connector->shared_ddc_connector && (ret == connector_status_connected)) { struct drm_connector *list_connector; struct radeon_connector *list_radeon_connector; list_for_each_entry(list_connector, &dev->mode_config.connector_list, head) { if (connector == list_connector) continue; list_radeon_connector = to_radeon_connector(list_connector); - if (list_radeon_connector->shared_ddc && - (list_radeon_connector->ddc_bus->rec.i2c_id == - radeon_connector->ddc_bus->rec.i2c_id)) { + if (list_radeon_connector->shared_ddc_connector == radeon_connector) { /* cases where both connectors are digital */ if (list_connector->connector_type != DRM_MODE_CONNECTOR_VGA) { /* hpd is our only option in this case */ @@ -912,6 +980,12 @@ radeon_dvi_detect(struct drm_connector *connector, bool force) } } } + } else { + /* Drop EDID if monitor is no longer connected. */ + if (radeon_connector->edid) { + kfree(radeon_connector->edid); + radeon_connector->edid = NULL; + } } if ((ret == connector_status_connected) && (radeon_connector->use_digital == true)) @@ -1250,6 +1324,9 @@ radeon_dp_detect(struct drm_connector *connector, bool force) struct radeon_connector_atom_dig *radeon_dig_connector = radeon_connector->con_priv; struct drm_encoder *encoder = radeon_best_single_encoder(connector); + if (!force && radeon_check_hpd_status_unchanged(connector)) + return connector->status; + if (radeon_connector->edid) { kfree(radeon_connector->edid); radeon_connector->edid = NULL; @@ -1385,11 +1462,12 @@ radeon_add_atom_connector(struct drm_device *dev, struct radeon_device *rdev = dev->dev_private; struct drm_connector *connector; struct radeon_connector *radeon_connector; + struct radeon_connector *new_radeon_connector; + struct radeon_connector *shared_radeon_connector = NULL; struct radeon_connector_atom_dig *radeon_dig_connector; struct drm_encoder *encoder; struct radeon_encoder *radeon_encoder; uint32_t subpixel_order = SubPixelNone; - bool shared_ddc = false; bool is_dp_bridge = false; if (connector_type == DRM_MODE_CONNECTOR_Unknown) @@ -1402,26 +1480,6 @@ radeon_add_atom_connector(struct drm_device *dev, (radeon_tv == 0)) return; - /* see if we already added it */ - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - radeon_connector = to_radeon_connector(connector); - if (radeon_connector->connector_id == connector_id) { - radeon_connector->devices |= supported_device; - return; - } - if (radeon_connector->ddc_bus && i2c_bus->valid) { - if (radeon_connector->ddc_bus->rec.i2c_id == i2c_bus->i2c_id) { - radeon_connector->shared_ddc = true; - shared_ddc = true; - } - if (radeon_connector->router_bus && router->ddc_valid && - (radeon_connector->router.router_id == router->router_id)) { - radeon_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) { radeon_encoder = to_radeon_encoder(encoder); @@ -1437,15 +1495,43 @@ radeon_add_atom_connector(struct drm_device *dev, } } - radeon_connector = kzalloc(sizeof(struct radeon_connector), GFP_KERNEL); - if (!radeon_connector) + /* see if we already added it */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + radeon_connector = to_radeon_connector(connector); + if (radeon_connector->connector_id == connector_id) { + radeon_connector->devices |= supported_device; + return; + } + } + + new_radeon_connector = kzalloc(sizeof(struct radeon_connector), GFP_KERNEL); + if (!new_radeon_connector) return; + /* check for shared ddc between connectors */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + radeon_connector = to_radeon_connector(connector); + if (radeon_connector->ddc_bus && i2c_bus->valid) { + if (radeon_connector->ddc_bus->rec.i2c_id == i2c_bus->i2c_id) { + radeon_connector->shared_ddc_connector = new_radeon_connector; + new_radeon_connector->shared_ddc_connector = radeon_connector; + shared_radeon_connector = radeon_connector; + } + if (radeon_connector->router_bus && router->ddc_valid && + (radeon_connector->router.router_id == router->router_id)) { + radeon_connector->shared_ddc_connector = NULL; + new_radeon_connector->shared_ddc_connector = NULL; + shared_radeon_connector = NULL; + } + } + } + + radeon_connector = new_radeon_connector; + connector = &radeon_connector->base; radeon_connector->connector_id = connector_id; radeon_connector->devices = supported_device; - radeon_connector->shared_ddc = shared_ddc; radeon_connector->connector_object_id = connector_object_id; radeon_connector->hpd = *hpd; @@ -1538,9 +1624,12 @@ radeon_add_atom_connector(struct drm_device *dev, drm_connector_attach_property(&radeon_connector->base, rdev->mode_info.load_detect_property, 1); - /* no HPD on analog connectors */ - radeon_connector->hpd.hpd = RADEON_HPD_NONE; - connector->polled = DRM_CONNECTOR_POLL_CONNECT; + /* If we share DDC/HPD with a DVI connector we want to be + * polled on HPD events so all state gets correctly updated. */ + if (shared_radeon_connector + && (shared_radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_DVII + || shared_radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_DVID)) + radeon_connector->hpd.hpd = shared_radeon_connector->hpd.hpd; connector->interlace_allowed = true; connector->doublescan_allowed = true; break; @@ -1601,6 +1690,19 @@ radeon_add_atom_connector(struct drm_device *dev, connector->doublescan_allowed = true; else connector->doublescan_allowed = false; + + /* If we share DDC/HPD with a VGA connector we want it to be + * polled on HPD events so all state gets correctly updated. */ + if (shared_radeon_connector + && shared_radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_VGA) { + shared_radeon_connector->hpd.hpd = radeon_connector->hpd.hpd; + if (shared_radeon_connector->hpd.hpd == RADEON_HPD_NONE) { + if (i2c_bus->valid) + shared_radeon_connector->base.polled + = DRM_CONNECTOR_POLL_CONNECT; + } else + shared_radeon_connector->base.polled = DRM_CONNECTOR_POLL_HPD; + } break; case DRM_MODE_CONNECTOR_HDMIA: case DRM_MODE_CONNECTOR_HDMIB: @@ -1748,8 +1850,11 @@ radeon_add_atom_connector(struct drm_device *dev, return; failed: + radeon_connector = new_radeon_connector->shared_ddc_connector; + if (radeon_connector && radeon_connector->shared_ddc_connector == new_radeon_connector) + radeon_connector->shared_ddc_connector = NULL; drm_connector_cleanup(connector); - kfree(connector); + kfree(new_radeon_connector); } void diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index 4330e32..b707eb7 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -437,8 +437,9 @@ struct radeon_connector { uint32_t connector_id; uint32_t devices; struct radeon_i2c_chan *ddc_bus; - /* some systems have an hdmi and vga port with a shared ddc line */ - bool shared_ddc; + /* some systems have an hdmi (or dvi) + and vga port with a shared ddc line */ + struct radeon_connector *shared_ddc_connector; bool use_digital; /* we need to mind the EDID between detect and get modes due to analog/digital/tvencoder */