1343 lines · 33.9 KB · C
RawDownload
// SPDX-License-Identifier: MIT/* * Copyright 2018 Noralf Trønnes * Copyright (c) 2006-2009 Red Hat Inc. * Copyright (c) 2006-2008 Intel Corporation *   Jesse Barnes <jesse.barnes@intel.com> * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> */ #include "drm/drm_modeset_lock.h" #include <linux/export.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/string_helpers.h> #include <drm/drm_atomic.h>#include <drm/drm_client.h>#include <drm/drm_connector.h>#include <drm/drm_crtc.h>#include <drm/drm_device.h>#include <drm/drm_drv.h>#include <drm/drm_edid.h>#include <drm/drm_encoder.h>#include <drm/drm_print.h> #include "drm_crtc_internal.h"#include "drm_internal.h" #define DRM_CLIENT_MAX_CLONED_CONNECTORS	8 struct drm_client_offset {	int x, y;}; int drm_client_modeset_create(struct drm_client_dev *client){	struct drm_device *dev = client->dev;	unsigned int num_crtc = dev->mode_config.num_crtc;	unsigned int max_connector_count = 1;	struct drm_mode_set *modeset;	struct drm_crtc *crtc;	int i = 0; 	/* Add terminating zero entry to enable index less iteration */	client->modesets = kzalloc_objs(*client->modesets, num_crtc + 1);	if (!client->modesets)		return -ENOMEM; 	mutex_init(&client->modeset_mutex); 	drm_for_each_crtc(crtc, dev)		client->modesets[i++].crtc = crtc; 	/* Cloning is only supported in the single crtc case. */	if (num_crtc == 1)		max_connector_count = DRM_CLIENT_MAX_CLONED_CONNECTORS; 	for (modeset = client->modesets; modeset->crtc; modeset++) {		modeset->connectors = kzalloc_objs(*modeset->connectors,						   max_connector_count);		if (!modeset->connectors)			goto err_free;	} 	return 0; err_free:	drm_client_modeset_free(client); 	return -ENOMEM;} static void drm_client_modeset_release(struct drm_client_dev *client){	struct drm_mode_set *modeset; 	drm_client_for_each_modeset(modeset, client) {		int i; 		drm_mode_destroy(client->dev, modeset->mode);		modeset->mode = NULL;		modeset->fb = NULL; 		for (i = 0; i < modeset->num_connectors; i++) {			drm_connector_put(modeset->connectors[i]);			modeset->connectors[i] = NULL;		}		modeset->num_connectors = 0;	}} void drm_client_modeset_free(struct drm_client_dev *client){	struct drm_mode_set *modeset; 	mutex_lock(&client->modeset_mutex); 	drm_client_modeset_release(client); 	drm_client_for_each_modeset(modeset, client)		kfree(modeset->connectors); 	mutex_unlock(&client->modeset_mutex); 	mutex_destroy(&client->modeset_mutex);	kfree(client->modesets);} static struct drm_mode_set *drm_client_find_modeset(struct drm_client_dev *client, struct drm_crtc *crtc){	struct drm_mode_set *modeset; 	drm_client_for_each_modeset(modeset, client)		if (modeset->crtc == crtc)			return modeset; 	return NULL;} static const struct drm_display_mode *drm_connector_get_tiled_mode(struct drm_connector *connector){	const struct drm_display_mode *mode; 	list_for_each_entry(mode, &connector->modes, head) {		if (mode->hdisplay == connector->tile_h_size &&		    mode->vdisplay == connector->tile_v_size)			return mode;	}	return NULL;} static const struct drm_display_mode *drm_connector_fallback_non_tiled_mode(struct drm_connector *connector){	const struct drm_display_mode *mode; 	list_for_each_entry(mode, &connector->modes, head) {		if (mode->hdisplay == connector->tile_h_size &&		    mode->vdisplay == connector->tile_v_size)			continue;		return mode;	}	return NULL;} static const struct drm_display_mode *drm_connector_preferred_mode(struct drm_connector *connector, int width, int height){	const struct drm_display_mode *mode; 	list_for_each_entry(mode, &connector->modes, head) {		if (mode->hdisplay > width ||		    mode->vdisplay > height)			continue;		if (mode->type & DRM_MODE_TYPE_PREFERRED)			return mode;	}	return NULL;} static const struct drm_display_mode *drm_connector_first_mode(struct drm_connector *connector){	return list_first_entry_or_null(&connector->modes,					struct drm_display_mode, head);} static const struct drm_display_mode *drm_connector_pick_cmdline_mode(struct drm_connector *connector){	const struct drm_cmdline_mode *cmdline_mode;	const struct drm_display_mode *mode;	bool prefer_non_interlace; 	/*	 * Find a user-defined mode. If the user gave us a valid	 * mode on the kernel command line, it will show up in this	 * list.	 */ 	list_for_each_entry(mode, &connector->modes, head) {		if (mode->type & DRM_MODE_TYPE_USERDEF)			return mode;	} 	cmdline_mode = &connector->cmdline_mode;	if (cmdline_mode->specified == false)		return NULL; 	/*	 * Attempt to find a matching mode in the list of modes we	 * have gotten so far.	 */ 	prefer_non_interlace = !cmdline_mode->interlace;again:	list_for_each_entry(mode, &connector->modes, head) {		/* check width/height */		if (mode->hdisplay != cmdline_mode->xres ||		    mode->vdisplay != cmdline_mode->yres)			continue; 		if (cmdline_mode->refresh_specified) {			if (drm_mode_vrefresh(mode) != cmdline_mode->refresh)				continue;		} 		if (cmdline_mode->interlace) {			if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))				continue;		} else if (prefer_non_interlace) {			if (mode->flags & DRM_MODE_FLAG_INTERLACE)				continue;		}		return mode;	} 	if (prefer_non_interlace) {		prefer_non_interlace = false;		goto again;	} 	return NULL;} static bool drm_connector_enabled(struct drm_connector *connector, bool strict){	bool enable; 	if (connector->display_info.non_desktop)		return false; 	if (strict)		enable = connector->status == connector_status_connected;	else		enable = connector->status != connector_status_disconnected; 	return enable;} static void drm_client_connectors_enabled(struct drm_connector *connectors[],					  unsigned int connector_count,					  bool enabled[]){	bool any_enabled = false;	struct drm_connector *connector;	int i = 0; 	for (i = 0; i < connector_count; i++) {		connector = connectors[i];		enabled[i] = drm_connector_enabled(connector, true);		drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] enabled? %s\n",			    connector->base.id, connector->name,			    connector->display_info.non_desktop ?			    "non desktop" : str_yes_no(enabled[i])); 		any_enabled |= enabled[i];	} 	if (any_enabled)		return; 	for (i = 0; i < connector_count; i++)		enabled[i] = drm_connector_enabled(connectors[i], false);} static void mode_replace(struct drm_device *dev,			 const struct drm_display_mode **dst,			 const struct drm_display_mode *src){	drm_mode_destroy(dev, (struct drm_display_mode *)*dst); 	*dst = src ? drm_mode_duplicate(dev, src) : NULL;} static void modes_destroy(struct drm_device *dev,			  const struct drm_display_mode *modes[],			  int count){	int i; 	for (i = 0; i < count; i++)		mode_replace(dev, &modes[i], NULL);} static bool drm_client_target_cloned(struct drm_device *dev,				     struct drm_connector *connectors[],				     unsigned int connector_count,				     const struct drm_display_mode *modes[],				     struct drm_client_offset offsets[],				     bool enabled[], int width, int height){	int count, i;	bool can_clone = false;	struct drm_display_mode *dmt_mode; 	/* only contemplate cloning in the single crtc case */	if (dev->mode_config.num_crtc > 1)		return false; 	count = 0;	for (i = 0; i < connector_count; i++) {		if (enabled[i])			count++;	} 	/* only contemplate cloning if more than one connector is enabled */	if (count <= 1)		return false; 	/* check the command line or if nothing common pick 1024x768 */	can_clone = true;	for (i = 0; i < connector_count; i++) {		int j; 		if (!enabled[i])			continue; 		mode_replace(dev, &modes[i],			     drm_connector_pick_cmdline_mode(connectors[i]));		if (!modes[i]) {			can_clone = false;			break;		}		for (j = 0; j < i; j++) {			if (!enabled[j])				continue;			if (!drm_mode_match(modes[j], modes[i],					    DRM_MODE_MATCH_TIMINGS |					    DRM_MODE_MATCH_CLOCK |					    DRM_MODE_MATCH_FLAGS |					    DRM_MODE_MATCH_3D_FLAGS))				can_clone = false;		}	} 	if (can_clone) {		drm_dbg_kms(dev, "can clone using command line\n");		return true;	} 	/* try and find a 1024x768 mode on each connector */	can_clone = true;	dmt_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false); 	if (!dmt_mode)		goto fail; 	for (i = 0; i < connector_count; i++) {		const struct drm_display_mode *mode; 		if (!enabled[i])			continue; 		list_for_each_entry(mode, &connectors[i]->modes, head) {			if (drm_mode_match(mode, dmt_mode,					   DRM_MODE_MATCH_TIMINGS |					   DRM_MODE_MATCH_CLOCK |					   DRM_MODE_MATCH_FLAGS |					   DRM_MODE_MATCH_3D_FLAGS))				mode_replace(dev, &modes[i], mode);		}		if (!modes[i])			can_clone = false;	}	drm_mode_destroy(dev, dmt_mode); 	if (can_clone) {		drm_dbg_kms(dev, "can clone using 1024x768\n");		return true;	}fail:	drm_info(dev, "kms: can't enable cloning when we probably wanted to.\n");	return false;} static int drm_client_get_tile_offsets(struct drm_device *dev,				       struct drm_connector *connectors[],				       unsigned int connector_count,				       const struct drm_display_mode *modes[],				       struct drm_client_offset offsets[],				       int idx,				       int h_idx, int v_idx){	int i;	int hoffset = 0, voffset = 0; 	for (i = 0; i < connector_count; i++) {		struct drm_connector *connector = connectors[i]; 		if (!connector->has_tile)			continue; 		if (!modes[i] && (h_idx || v_idx)) {			drm_dbg_kms(dev,				    "[CONNECTOR:%d:%s] no modes for connector tiled %d\n",				    connector->base.id, connector->name, i);			continue;		}		if (connector->tile_h_loc < h_idx)			hoffset += modes[i]->hdisplay; 		if (connector->tile_v_loc < v_idx)			voffset += modes[i]->vdisplay;	}	offsets[idx].x = hoffset;	offsets[idx].y = voffset;	drm_dbg_kms(dev, "returned %d %d for %d %d\n", hoffset, voffset, h_idx, v_idx);	return 0;} static bool drm_client_target_preferred(struct drm_device *dev,					struct drm_connector *connectors[],					unsigned int connector_count,					const struct drm_display_mode *modes[],					struct drm_client_offset offsets[],					bool enabled[], int width, int height){	const u64 mask = BIT_ULL(connector_count) - 1;	u64 conn_configured = 0;	int tile_pass = 0;	int num_tiled_conns = 0;	int i; 	for (i = 0; i < connector_count; i++) {		if (connectors[i]->has_tile &&		    connectors[i]->status == connector_status_connected)			num_tiled_conns++;	} retry:	for (i = 0; i < connector_count; i++) {		struct drm_connector *connector = connectors[i];		const char *mode_type;  		if (conn_configured & BIT_ULL(i))			continue; 		if (enabled[i] == false) {			conn_configured |= BIT_ULL(i);			continue;		} 		/* first pass over all the untiled connectors */		if (tile_pass == 0 && connector->has_tile)			continue; 		if (tile_pass == 1) {			if (connector->tile_h_loc != 0 ||			    connector->tile_v_loc != 0)				continue; 		} else {			if (connector->tile_h_loc != tile_pass - 1 &&			    connector->tile_v_loc != tile_pass - 1)			/* if this tile_pass doesn't cover any of the tiles - keep going */				continue; 			/*			 * find the tile offsets for this pass - need to find			 * all tiles left and above			 */			drm_client_get_tile_offsets(dev, connectors, connector_count,						    modes, offsets, i,						    connector->tile_h_loc, connector->tile_v_loc);		} 		mode_type = "cmdline";		mode_replace(dev, &modes[i],			     drm_connector_pick_cmdline_mode(connector)); 		if (!modes[i]) {			mode_type = "preferred";			mode_replace(dev, &modes[i],				     drm_connector_preferred_mode(connector, width, height));		} 		if (!modes[i]) {			mode_type = "first";			mode_replace(dev, &modes[i],				     drm_connector_first_mode(connector));		} 		/*		 * In case of tiled mode if all tiles not present fallback to		 * first available non tiled mode.		 * After all tiles are present, try to find the tiled mode		 * for all and if tiled mode not present due to fbcon size		 * limitations, use first non tiled mode only for		 * tile 0,0 and set to no mode for all other tiles.		 */		if (connector->has_tile) {			if (num_tiled_conns <			    connector->num_h_tile * connector->num_v_tile ||			    (connector->tile_h_loc == 0 &&

Lines 1–500 of 1343.