diff options
Diffstat (limited to 'drivers/gpu/drm/drm_edid.c')
| -rw-r--r-- | drivers/gpu/drm/drm_edid.c | 207 | 
1 files changed, 127 insertions, 80 deletions
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index ec77bd3e1f08..336be31ff3de 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -957,13 +957,13 @@ static const struct drm_display_mode edid_cea_modes[] = {  		   798, 858, 0, 480, 489, 495, 525, 0,  		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),  	  .vrefresh = 240, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, -	/* 58 - 720(1440)x480i@240 */ +	/* 58 - 720(1440)x480i@240Hz */  	{ DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 54000, 720, 739,  		   801, 858, 0, 480, 488, 494, 525, 0,  		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |  			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK),  	  .vrefresh = 240, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, }, -	/* 59 - 720(1440)x480i@240 */ +	/* 59 - 720(1440)x480i@240Hz */  	{ DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 54000, 720, 739,  		   801, 858, 0, 480, 488, 494, 525, 0,  		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | @@ -1260,6 +1260,34 @@ drm_do_probe_ddc_edid(void *data, u8 *buf, unsigned int block, size_t len)  	return ret == xfers ? 0 : -1;  } +static void connector_bad_edid(struct drm_connector *connector, +			       u8 *edid, int num_blocks) +{ +	int i; + +	if (connector->bad_edid_counter++ && !(drm_debug & DRM_UT_KMS)) +		return; + +	dev_warn(connector->dev->dev, +		 "%s: EDID is invalid:\n", +		 connector->name); +	for (i = 0; i < num_blocks; i++) { +		u8 *block = edid + i * EDID_LENGTH; +		char prefix[20]; + +		if (drm_edid_is_zero(block, EDID_LENGTH)) +			sprintf(prefix, "\t[%02x] ZERO ", i); +		else if (!drm_edid_block_valid(block, i, false, NULL)) +			sprintf(prefix, "\t[%02x] BAD  ", i); +		else +			sprintf(prefix, "\t[%02x] GOOD ", i); + +		print_hex_dump(KERN_WARNING, +			       prefix, DUMP_PREFIX_NONE, 16, 1, +			       block, EDID_LENGTH, false); +	} +} +  /**   * drm_do_get_edid - get EDID data using a custom EDID block read function   * @connector: connector we're probing @@ -1282,20 +1310,19 @@ struct edid *drm_do_get_edid(struct drm_connector *connector,  	void *data)  {  	int i, j = 0, valid_extensions = 0; -	u8 *block, *new; -	bool print_bad_edid = !connector->bad_edid_counter || (drm_debug & DRM_UT_KMS); +	u8 *edid, *new; -	if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL) +	if ((edid = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)  		return NULL;  	/* base block fetch */  	for (i = 0; i < 4; i++) { -		if (get_edid_block(data, block, 0, EDID_LENGTH)) +		if (get_edid_block(data, edid, 0, EDID_LENGTH))  			goto out; -		if (drm_edid_block_valid(block, 0, print_bad_edid, +		if (drm_edid_block_valid(edid, 0, false,  					 &connector->edid_corrupt))  			break; -		if (i == 0 && drm_edid_is_zero(block, EDID_LENGTH)) { +		if (i == 0 && drm_edid_is_zero(edid, EDID_LENGTH)) {  			connector->null_edid_counter++;  			goto carp;  		} @@ -1304,58 +1331,62 @@ struct edid *drm_do_get_edid(struct drm_connector *connector,  		goto carp;  	/* if there's no extensions, we're done */ -	if (block[0x7e] == 0) -		return (struct edid *)block; +	valid_extensions = edid[0x7e]; +	if (valid_extensions == 0) +		return (struct edid *)edid; -	new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL); +	new = krealloc(edid, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL);  	if (!new)  		goto out; -	block = new; +	edid = new; + +	for (j = 1; j <= edid[0x7e]; j++) { +		u8 *block = edid + j * EDID_LENGTH; -	for (j = 1; j <= block[0x7e]; j++) {  		for (i = 0; i < 4; i++) { -			if (get_edid_block(data, -				  block + (valid_extensions + 1) * EDID_LENGTH, -				  j, EDID_LENGTH)) +			if (get_edid_block(data, block, j, EDID_LENGTH))  				goto out; -			if (drm_edid_block_valid(block + (valid_extensions + 1) -						 * EDID_LENGTH, j, -						 print_bad_edid, -						 NULL)) { -				valid_extensions++; +			if (drm_edid_block_valid(block, j, false, NULL))  				break; -			}  		} -		if (i == 4 && print_bad_edid) { -			dev_warn(connector->dev->dev, -			 "%s: Ignoring invalid EDID block %d.\n", -			 connector->name, j); - -			connector->bad_edid_counter++; -		} +		if (i == 4) +			valid_extensions--;  	} -	if (valid_extensions != block[0x7e]) { -		block[EDID_LENGTH-1] += block[0x7e] - valid_extensions; -		block[0x7e] = valid_extensions; -		new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL); +	if (valid_extensions != edid[0x7e]) { +		u8 *base; + +		connector_bad_edid(connector, edid, edid[0x7e] + 1); + +		edid[EDID_LENGTH-1] += edid[0x7e] - valid_extensions; +		edid[0x7e] = valid_extensions; + +		new = kmalloc((valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL);  		if (!new)  			goto out; -		block = new; -	} -	return (struct edid *)block; +		base = new; +		for (i = 0; i <= edid[0x7e]; i++) { +			u8 *block = edid + i * EDID_LENGTH; -carp: -	if (print_bad_edid) { -		dev_warn(connector->dev->dev, "%s: EDID block %d invalid.\n", -			 connector->name, j); +			if (!drm_edid_block_valid(block, i, false, NULL)) +				continue; + +			memcpy(base, block, EDID_LENGTH); +			base += EDID_LENGTH; +		} + +		kfree(edid); +		edid = new;  	} -	connector->bad_edid_counter++; +	return (struct edid *)edid; + +carp: +	connector_bad_edid(connector, edid, 1);  out: -	kfree(block); +	kfree(edid);  	return NULL;  }  EXPORT_SYMBOL_GPL(drm_do_get_edid); @@ -2582,6 +2613,41 @@ cea_mode_alternate_clock(const struct drm_display_mode *cea_mode)  	return clock;  } +static bool +cea_mode_alternate_timings(u8 vic, struct drm_display_mode *mode) +{ +	/* +	 * For certain VICs the spec allows the vertical +	 * front porch to vary by one or two lines. +	 * +	 * cea_modes[] stores the variant with the shortest +	 * vertical front porch. We can adjust the mode to +	 * get the other variants by simply increasing the +	 * vertical front porch length. +	 */ +	BUILD_BUG_ON(edid_cea_modes[8].vtotal != 262 || +		     edid_cea_modes[9].vtotal != 262 || +		     edid_cea_modes[12].vtotal != 262 || +		     edid_cea_modes[13].vtotal != 262 || +		     edid_cea_modes[23].vtotal != 312 || +		     edid_cea_modes[24].vtotal != 312 || +		     edid_cea_modes[27].vtotal != 312 || +		     edid_cea_modes[28].vtotal != 312); + +	if (((vic == 8 || vic == 9 || +	      vic == 12 || vic == 13) && mode->vtotal < 263) || +	    ((vic == 23 || vic == 24 || +	      vic == 27 || vic == 28) && mode->vtotal < 314)) { +		mode->vsync_start++; +		mode->vsync_end++; +		mode->vtotal++; + +		return true; +	} + +	return false; +} +  static u8 drm_match_cea_mode_clock_tolerance(const struct drm_display_mode *to_match,  					     unsigned int clock_tolerance)  { @@ -2591,19 +2657,21 @@ static u8 drm_match_cea_mode_clock_tolerance(const struct drm_display_mode *to_m  		return 0;  	for (vic = 1; vic < ARRAY_SIZE(edid_cea_modes); vic++) { -		const struct drm_display_mode *cea_mode = &edid_cea_modes[vic]; +		struct drm_display_mode cea_mode = edid_cea_modes[vic];  		unsigned int clock1, clock2;  		/* Check both 60Hz and 59.94Hz */ -		clock1 = cea_mode->clock; -		clock2 = cea_mode_alternate_clock(cea_mode); +		clock1 = cea_mode.clock; +		clock2 = cea_mode_alternate_clock(&cea_mode);  		if (abs(to_match->clock - clock1) > clock_tolerance &&  		    abs(to_match->clock - clock2) > clock_tolerance)  			continue; -		if (drm_mode_equal_no_clocks(to_match, cea_mode)) -			return vic; +		do { +			if (drm_mode_equal_no_clocks_no_stereo(to_match, &cea_mode)) +				return vic; +		} while (cea_mode_alternate_timings(vic, &cea_mode));  	}  	return 0; @@ -2624,18 +2692,23 @@ u8 drm_match_cea_mode(const struct drm_display_mode *to_match)  		return 0;  	for (vic = 1; vic < ARRAY_SIZE(edid_cea_modes); vic++) { -		const struct drm_display_mode *cea_mode = &edid_cea_modes[vic]; +		struct drm_display_mode cea_mode = edid_cea_modes[vic];  		unsigned int clock1, clock2;  		/* Check both 60Hz and 59.94Hz */ -		clock1 = cea_mode->clock; -		clock2 = cea_mode_alternate_clock(cea_mode); +		clock1 = cea_mode.clock; +		clock2 = cea_mode_alternate_clock(&cea_mode); -		if ((KHZ2PICOS(to_match->clock) == KHZ2PICOS(clock1) || -		     KHZ2PICOS(to_match->clock) == KHZ2PICOS(clock2)) && -		    drm_mode_equal_no_clocks_no_stereo(to_match, cea_mode)) -			return vic; +		if (KHZ2PICOS(to_match->clock) != KHZ2PICOS(clock1) && +		    KHZ2PICOS(to_match->clock) != KHZ2PICOS(clock2)) +			continue; + +		do { +			if (drm_mode_equal_no_clocks_no_stereo(to_match, &cea_mode)) +				return vic; +		} while (cea_mode_alternate_timings(vic, &cea_mode));  	} +  	return 0;  }  EXPORT_SYMBOL(drm_match_cea_mode); @@ -3580,32 +3653,6 @@ int drm_av_sync_delay(struct drm_connector *connector,  EXPORT_SYMBOL(drm_av_sync_delay);  /** - * drm_select_eld - select one ELD from multiple HDMI/DP sinks - * @encoder: the encoder just changed display mode - * - * It's possible for one encoder to be associated with multiple HDMI/DP sinks. - * The policy is now hard coded to simply use the first HDMI/DP sink's ELD. - * - * Return: The connector associated with the first HDMI/DP sink that has ELD - * attached to it. - */ -struct drm_connector *drm_select_eld(struct drm_encoder *encoder) -{ -	struct drm_connector *connector; -	struct drm_device *dev = encoder->dev; - -	WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); -	WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex)); - -	drm_for_each_connector(connector, dev) -		if (connector->encoder == encoder && connector->eld[0]) -			return connector; - -	return NULL; -} -EXPORT_SYMBOL(drm_select_eld); - -/**   * drm_detect_hdmi_monitor - detect whether monitor is HDMI   * @edid: monitor EDID information   *  |