diff options
Diffstat (limited to 'drivers/gpu/drm/drm_dp_mst_topology.c')
| -rw-r--r-- | drivers/gpu/drm/drm_dp_mst_topology.c | 142 | 
1 files changed, 132 insertions, 10 deletions
| diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 0984b9a34d55..82add736e17d 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -36,6 +36,8 @@  #include <drm/drm_print.h>  #include <drm/drm_probe_helper.h> +#include "drm_crtc_helper_internal.h" +  /**   * DOC: dp mst helper   * @@ -53,6 +55,9 @@ static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr,  				     int id,  				     struct drm_dp_payload *payload); +static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, +				 struct drm_dp_mst_port *port, +				 int offset, int size, u8 *bytes);  static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,  				  struct drm_dp_mst_port *port,  				  int offset, int size, u8 *bytes); @@ -1483,6 +1488,52 @@ static bool drm_dp_port_setup_pdt(struct drm_dp_mst_port *port)  	return send_link;  } +/** + * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to read + * @buffer: buffer to store the register values + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_read() does for local + * devices via actual AUX CH. + * + * Return: Number of bytes read, or negative error code on failure. + */ +ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, +			     unsigned int offset, void *buffer, size_t size) +{ +	struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, +						    aux); + +	return drm_dp_send_dpcd_read(port->mgr, port, +				     offset, size, buffer); +} + +/** + * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to write + * @buffer: buffer containing the values to write + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_write() does for local + * devices via actual AUX CH. + * + * Return: 0 on success, negative error code on failure. + */ +ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, +			      unsigned int offset, void *buffer, size_t size) +{ +	struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, +						    aux); + +	return drm_dp_send_dpcd_write(port->mgr, port, +				      offset, size, buffer); +} +  static void drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid)  {  	int ret; @@ -1526,6 +1577,46 @@ static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb,  	strlcat(proppath, temp, proppath_size);  } +/** + * drm_dp_mst_connector_late_register() - Late MST connector registration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to register the remote aux device for this MST port. Drivers should + * call this from their mst connector's late_register hook to enable MST aux + * devices. + * + * Return: 0 on success, negative error code on failure. + */ +int drm_dp_mst_connector_late_register(struct drm_connector *connector, +				       struct drm_dp_mst_port *port) +{ +	DRM_DEBUG_KMS("registering %s remote bus for %s\n", +		      port->aux.name, connector->kdev->kobj.name); + +	port->aux.dev = connector->kdev; +	return drm_dp_aux_register_devnode(&port->aux); +} +EXPORT_SYMBOL(drm_dp_mst_connector_late_register); + +/** + * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to unregister the remote aux device for this MST port, registered by + * drm_dp_mst_connector_late_register(). Drivers should call this from their mst + * connector's early_unregister hook. + */ +void drm_dp_mst_connector_early_unregister(struct drm_connector *connector, +					   struct drm_dp_mst_port *port) +{ +	DRM_DEBUG_KMS("unregistering %s remote bus for %s\n", +		      port->aux.name, connector->kdev->kobj.name); +	drm_dp_aux_unregister_devnode(&port->aux); +} +EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister); +  static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,  			    struct drm_device *dev,  			    struct drm_dp_link_addr_reply_port *port_msg) @@ -1548,6 +1639,7 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,  		port->mgr = mstb->mgr;  		port->aux.name = "DPMST";  		port->aux.dev = dev->dev; +		port->aux.is_remote = true;  		/*  		 * Make sure the memory allocation for our parent branch stays @@ -1816,7 +1908,6 @@ static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr,  	return false;  } -#if 0  static int build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, u8 port_num, u32 offset, u8 num_bytes)  {  	struct drm_dp_sideband_msg_req_body req; @@ -1829,7 +1920,6 @@ static int build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, u8 port_num, u32  	return 0;  } -#endif  static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr,  				    bool up, u8 *msg, int len) @@ -2441,26 +2531,58 @@ int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr)  }  EXPORT_SYMBOL(drm_dp_update_payload_part2); -#if 0 /* unused as of yet */  static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr,  				 struct drm_dp_mst_port *port, -				 int offset, int size) +				 int offset, int size, u8 *bytes)  {  	int len; +	int ret = 0;  	struct drm_dp_sideband_msg_tx *txmsg; +	struct drm_dp_mst_branch *mstb; + +	mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); +	if (!mstb) +		return -EINVAL;  	txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); -	if (!txmsg) -		return -ENOMEM; +	if (!txmsg) { +		ret = -ENOMEM; +		goto fail_put; +	} -	len = build_dpcd_read(txmsg, port->port_num, 0, 8); +	len = build_dpcd_read(txmsg, port->port_num, offset, size);  	txmsg->dst = port->parent;  	drm_dp_queue_down_tx(mgr, txmsg); -	return 0; +	ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); +	if (ret < 0) +		goto fail_free; + +	/* DPCD read should never be NACKed */ +	if (txmsg->reply.reply_type == 1) { +		DRM_ERROR("mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n", +			  mstb, port->port_num, offset, size); +		ret = -EIO; +		goto fail_free; +	} + +	if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) { +		ret = -EPROTO; +		goto fail_free; +	} + +	ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes, +		    size); +	memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret); + +fail_free: +	kfree(txmsg); +fail_put: +	drm_dp_mst_topology_put_mstb(mstb); + +	return ret;  } -#endif  static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,  				  struct drm_dp_mst_port *port, @@ -2489,7 +2611,7 @@ static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,  	ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);  	if (ret > 0) {  		if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) -			ret = -EINVAL; +			ret = -EIO;  		else  			ret = 0;  	} | 
