diff options
Diffstat (limited to 'drivers/gpu/drm/drm_dp_helper.c')
| -rw-r--r-- | drivers/gpu/drm/drm_dp_helper.c | 201 | 
1 files changed, 195 insertions, 6 deletions
diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c index eae5ef963cb7..3e6fe82c6d64 100644 --- a/drivers/gpu/drm/drm_dp_helper.c +++ b/drivers/gpu/drm/drm_dp_helper.c @@ -27,10 +27,12 @@  #include <linux/errno.h>  #include <linux/sched.h>  #include <linux/i2c.h> +#include <linux/seq_file.h>  #include <drm/drm_dp_helper.h> -#include <drm/drm_dp_aux_dev.h>  #include <drm/drmP.h> +#include "drm_crtc_helper_internal.h" +  /**   * DOC: dp helpers   * @@ -223,7 +225,7 @@ static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request,  			err = ret;  	} -	DRM_DEBUG_KMS("too many retries, giving up\n"); +	DRM_DEBUG_KMS("Too many retries, giving up. First error: %d\n", err);  	ret = err;  unlock: @@ -438,6 +440,179 @@ int drm_dp_link_configure(struct drm_dp_aux *aux, struct drm_dp_link *link)  }  EXPORT_SYMBOL(drm_dp_link_configure); +/** + * drm_dp_downstream_max_clock() - extract branch device max + *                                 pixel rate for legacy VGA + *                                 converter or max TMDS clock + *                                 rate for others + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Returns max clock in kHz on success or 0 if max clock not defined + */ +int drm_dp_downstream_max_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], +				const u8 port_cap[4]) +{ +	int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; +	bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] & +		DP_DETAILED_CAP_INFO_AVAILABLE; + +	if (!detailed_cap_info) +		return 0; + +	switch (type) { +	case DP_DS_PORT_TYPE_VGA: +		return port_cap[1] * 8 * 1000; +	case DP_DS_PORT_TYPE_DVI: +	case DP_DS_PORT_TYPE_HDMI: +	case DP_DS_PORT_TYPE_DP_DUALMODE: +		return port_cap[1] * 2500; +	default: +		return 0; +	} +} +EXPORT_SYMBOL(drm_dp_downstream_max_clock); + +/** + * drm_dp_downstream_max_bpc() - extract branch device max + *                               bits per component + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Returns max bpc on success or 0 if max bpc not defined + */ +int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE], +			      const u8 port_cap[4]) +{ +	int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; +	bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] & +		DP_DETAILED_CAP_INFO_AVAILABLE; +	int bpc; + +	if (!detailed_cap_info) +		return 0; + +	switch (type) { +	case DP_DS_PORT_TYPE_VGA: +	case DP_DS_PORT_TYPE_DVI: +	case DP_DS_PORT_TYPE_HDMI: +	case DP_DS_PORT_TYPE_DP_DUALMODE: +		bpc = port_cap[2] & DP_DS_MAX_BPC_MASK; + +		switch (bpc) { +		case DP_DS_8BPC: +			return 8; +		case DP_DS_10BPC: +			return 10; +		case DP_DS_12BPC: +			return 12; +		case DP_DS_16BPC: +			return 16; +		} +	default: +		return 0; +	} +} +EXPORT_SYMBOL(drm_dp_downstream_max_bpc); + +/** + * drm_dp_downstream_id() - identify branch device + * @aux: DisplayPort AUX channel + * @id: DisplayPort branch device id + * + * Returns branch device id on success or NULL on failure + */ +int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6]) +{ +	return drm_dp_dpcd_read(aux, DP_BRANCH_ID, id, 6); +} +EXPORT_SYMBOL(drm_dp_downstream_id); + +/** + * drm_dp_downstream_debug() - debug DP branch devices + * @m: pointer for debugfs file + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @aux: DisplayPort AUX channel + * + */ +void drm_dp_downstream_debug(struct seq_file *m, +			     const u8 dpcd[DP_RECEIVER_CAP_SIZE], +			     const u8 port_cap[4], struct drm_dp_aux *aux) +{ +	bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] & +				 DP_DETAILED_CAP_INFO_AVAILABLE; +	int clk; +	int bpc; +	char id[6]; +	int len; +	uint8_t rev[2]; +	int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; +	bool branch_device = dpcd[DP_DOWNSTREAMPORT_PRESENT] & +			     DP_DWN_STRM_PORT_PRESENT; + +	seq_printf(m, "\tDP branch device present: %s\n", +		   branch_device ? "yes" : "no"); + +	if (!branch_device) +		return; + +	switch (type) { +	case DP_DS_PORT_TYPE_DP: +		seq_puts(m, "\t\tType: DisplayPort\n"); +		break; +	case DP_DS_PORT_TYPE_VGA: +		seq_puts(m, "\t\tType: VGA\n"); +		break; +	case DP_DS_PORT_TYPE_DVI: +		seq_puts(m, "\t\tType: DVI\n"); +		break; +	case DP_DS_PORT_TYPE_HDMI: +		seq_puts(m, "\t\tType: HDMI\n"); +		break; +	case DP_DS_PORT_TYPE_NON_EDID: +		seq_puts(m, "\t\tType: others without EDID support\n"); +		break; +	case DP_DS_PORT_TYPE_DP_DUALMODE: +		seq_puts(m, "\t\tType: DP++\n"); +		break; +	case DP_DS_PORT_TYPE_WIRELESS: +		seq_puts(m, "\t\tType: Wireless\n"); +		break; +	default: +		seq_puts(m, "\t\tType: N/A\n"); +	} + +	drm_dp_downstream_id(aux, id); +	seq_printf(m, "\t\tID: %s\n", id); + +	len = drm_dp_dpcd_read(aux, DP_BRANCH_HW_REV, &rev[0], 1); +	if (len > 0) +		seq_printf(m, "\t\tHW: %d.%d\n", +			   (rev[0] & 0xf0) >> 4, rev[0] & 0xf); + +	len = drm_dp_dpcd_read(aux, DP_BRANCH_SW_REV, &rev, 2); +	if (len > 0) +		seq_printf(m, "\t\tSW: %d.%d\n", rev[0], rev[1]); + +	if (detailed_cap_info) { +		clk = drm_dp_downstream_max_clock(dpcd, port_cap); + +		if (clk > 0) { +			if (type == DP_DS_PORT_TYPE_VGA) +				seq_printf(m, "\t\tMax dot clock: %d kHz\n", clk); +			else +				seq_printf(m, "\t\tMax TMDS clock: %d kHz\n", clk); +		} + +		bpc = drm_dp_downstream_max_bpc(dpcd, port_cap); + +		if (bpc > 0) +			seq_printf(m, "\t\tMax bpc: %d\n", bpc); +	} +} +EXPORT_SYMBOL(drm_dp_downstream_debug); +  /*   * I2C-over-AUX implementation   */ @@ -574,7 +749,17 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)  			if (ret == -EBUSY)  				continue; -			DRM_DEBUG_KMS("transaction failed: %d\n", ret); +			/* +			 * While timeouts can be errors, they're usually normal +			 * behavior (for instance, when a driver tries to +			 * communicate with a non-existant DisplayPort device). +			 * Avoid spamming the kernel log with timeout errors. +			 */ +			if (ret == -ETIMEDOUT) +				DRM_DEBUG_KMS_RATELIMITED("transaction timed out\n"); +			else +				DRM_DEBUG_KMS("transaction failed: %d\n", ret); +  			return ret;  		} @@ -790,6 +975,12 @@ static void unlock_bus(struct i2c_adapter *i2c, unsigned int flags)  	mutex_unlock(&i2c_to_aux(i2c)->hw_mutex);  } +static const struct i2c_lock_operations drm_dp_i2c_lock_ops = { +	.lock_bus = lock_bus, +	.trylock_bus = trylock_bus, +	.unlock_bus = unlock_bus, +}; +  /**   * drm_dp_aux_init() - minimally initialise an aux channel   * @aux: DisplayPort AUX channel @@ -807,9 +998,7 @@ void drm_dp_aux_init(struct drm_dp_aux *aux)  	aux->ddc.algo_data = aux;  	aux->ddc.retries = 3; -	aux->ddc.lock_bus = lock_bus; -	aux->ddc.trylock_bus = trylock_bus; -	aux->ddc.unlock_bus = unlock_bus; +	aux->ddc.lock_ops = &drm_dp_i2c_lock_ops;  }  EXPORT_SYMBOL(drm_dp_aux_init);  | 
