mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
![Maxime Ripard](/assets/img/avatar_default.png)
Commit 3aeeb13d89
("drm/modes: Support modes names on the command
line") initially introduced the named modes support by essentially
matching the name passed on the command-line to the mode names defined
by the drivers.
This proved to be difficult to work with, since all drivers had to
provide properly named modes. This was also needed because we weren't
passing a full blown-mode to the drivers, but were only filling its
name.
Thanks to the previous patches, we now generate a proper mode, and we
thus can use the usual matching algo on timings, and can simply drop the
name match.
Reviewed-by: Noralf Trønnes <noralf@tronnes.org>
Suggested-by: Noralf Trønnes <noralf@tronnes.org>
Acked-in-principle-or-something-like-that-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://lore.kernel.org/r/20220728-rpi-analog-tv-properties-v10-10-256dad125326@cerno.tech
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
1239 lines
32 KiB
C
1239 lines
32 KiB
C
// 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/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;
|
|
unsigned int i = 0;
|
|
|
|
/* Add terminating zero entry to enable index less iteration */
|
|
client->modesets = kcalloc(num_crtc + 1, sizeof(*client->modesets), GFP_KERNEL);
|
|
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 = kcalloc(max_connector_count,
|
|
sizeof(*modeset->connectors), GFP_KERNEL);
|
|
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;
|
|
unsigned int i;
|
|
|
|
drm_client_for_each_modeset(modeset, client) {
|
|
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 struct drm_display_mode *
|
|
drm_connector_get_tiled_mode(struct drm_connector *connector)
|
|
{
|
|
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 struct drm_display_mode *
|
|
drm_connector_fallback_non_tiled_mode(struct drm_connector *connector)
|
|
{
|
|
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 struct drm_display_mode *
|
|
drm_connector_has_preferred_mode(struct drm_connector *connector, int width, int height)
|
|
{
|
|
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 struct drm_display_mode *drm_connector_pick_cmdline_mode(struct drm_connector *connector)
|
|
{
|
|
struct drm_cmdline_mode *cmdline_mode;
|
|
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_DEBUG_KMS("connector %d enabled? %s\n", connector->base.id,
|
|
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 bool drm_client_target_cloned(struct drm_device *dev,
|
|
struct drm_connector **connectors,
|
|
unsigned int connector_count,
|
|
struct drm_display_mode **modes,
|
|
struct drm_client_offset *offsets,
|
|
bool *enabled, int width, int height)
|
|
{
|
|
int count, i, j;
|
|
bool can_clone = false;
|
|
struct drm_display_mode *dmt_mode, *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++) {
|
|
if (!enabled[i])
|
|
continue;
|
|
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_DEBUG_KMS("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);
|
|
|
|
for (i = 0; i < connector_count; i++) {
|
|
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))
|
|
modes[i] = mode;
|
|
}
|
|
if (!modes[i])
|
|
can_clone = false;
|
|
}
|
|
|
|
if (can_clone) {
|
|
DRM_DEBUG_KMS("can clone using 1024x768\n");
|
|
return true;
|
|
}
|
|
DRM_INFO("kms: can't enable cloning when we probably wanted to.\n");
|
|
return false;
|
|
}
|
|
|
|
static int drm_client_get_tile_offsets(struct drm_connector **connectors,
|
|
unsigned int connector_count,
|
|
struct drm_display_mode **modes,
|
|
struct drm_client_offset *offsets,
|
|
int idx,
|
|
int h_idx, int v_idx)
|
|
{
|
|
struct drm_connector *connector;
|
|
int i;
|
|
int hoffset = 0, voffset = 0;
|
|
|
|
for (i = 0; i < connector_count; i++) {
|
|
connector = connectors[i];
|
|
if (!connector->has_tile)
|
|
continue;
|
|
|
|
if (!modes[i] && (h_idx || v_idx)) {
|
|
DRM_DEBUG_KMS("no modes for connector tiled %d %d\n", i,
|
|
connector->base.id);
|
|
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_DEBUG_KMS("returned %d %d for %d %d\n", hoffset, voffset, h_idx, v_idx);
|
|
return 0;
|
|
}
|
|
|
|
static bool drm_client_target_preferred(struct drm_connector **connectors,
|
|
unsigned int connector_count,
|
|
struct drm_display_mode **modes,
|
|
struct drm_client_offset *offsets,
|
|
bool *enabled, int width, int height)
|
|
{
|
|
const u64 mask = BIT_ULL(connector_count) - 1;
|
|
struct drm_connector *connector;
|
|
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++) {
|
|
connector = connectors[i];
|
|
|
|
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(connectors, connector_count, modes, offsets, i,
|
|
connector->tile_h_loc, connector->tile_v_loc);
|
|
}
|
|
DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n",
|
|
connector->base.id);
|
|
|
|
/* got for command line mode first */
|
|
modes[i] = drm_connector_pick_cmdline_mode(connector);
|
|
if (!modes[i]) {
|
|
DRM_DEBUG_KMS("looking for preferred mode on connector %d %d\n",
|
|
connector->base.id, connector->tile_group ? connector->tile_group->id : 0);
|
|
modes[i] = drm_connector_has_preferred_mode(connector, width, height);
|
|
}
|
|
/* No preferred modes, pick one off the list */
|
|
if (!modes[i] && !list_empty(&connector->modes)) {
|
|
list_for_each_entry(modes[i], &connector->modes, head)
|
|
break;
|
|
}
|
|
/*
|
|
* 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 &&
|
|
connector->tile_v_loc == 0 &&
|
|
!drm_connector_get_tiled_mode(connector))) {
|
|
DRM_DEBUG_KMS("Falling back to non tiled mode on Connector %d\n",
|
|
connector->base.id);
|
|
modes[i] = drm_connector_fallback_non_tiled_mode(connector);
|
|
} else {
|
|
modes[i] = drm_connector_get_tiled_mode(connector);
|
|
}
|
|
}
|
|
|
|
DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name :
|
|
"none");
|
|
conn_configured |= BIT_ULL(i);
|
|
}
|
|
|
|
if ((conn_configured & mask) != mask) {
|
|
tile_pass++;
|
|
goto retry;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool connector_has_possible_crtc(struct drm_connector *connector,
|
|
struct drm_crtc *crtc)
|
|
{
|
|
struct drm_encoder *encoder;
|
|
|
|
drm_connector_for_each_possible_encoder(connector, encoder) {
|
|
if (encoder->possible_crtcs & drm_crtc_mask(crtc))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int drm_client_pick_crtcs(struct drm_client_dev *client,
|
|
struct drm_connector **connectors,
|
|
unsigned int connector_count,
|
|
struct drm_crtc **best_crtcs,
|
|
struct drm_display_mode **modes,
|
|
int n, int width, int height)
|
|
{
|
|
struct drm_device *dev = client->dev;
|
|
struct drm_connector *connector;
|
|
int my_score, best_score, score;
|
|
struct drm_crtc **crtcs, *crtc;
|
|
struct drm_mode_set *modeset;
|
|
int o;
|
|
|
|
if (n == connector_count)
|
|
return 0;
|
|
|
|
connector = connectors[n];
|
|
|
|
best_crtcs[n] = NULL;
|
|
best_score = drm_client_pick_crtcs(client, connectors, connector_count,
|
|
best_crtcs, modes, n + 1, width, height);
|
|
if (modes[n] == NULL)
|
|
return best_score;
|
|
|
|
crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL);
|
|
if (!crtcs)
|
|
return best_score;
|
|
|
|
my_score = 1;
|
|
if (connector->status == connector_status_connected)
|
|
my_score++;
|
|
if (connector->cmdline_mode.specified)
|
|
my_score++;
|
|
if (drm_connector_has_preferred_mode(connector, width, height))
|
|
my_score++;
|
|
|
|
/*
|
|
* select a crtc for this connector and then attempt to configure
|
|
* remaining connectors
|
|
*/
|
|
drm_client_for_each_modeset(modeset, client) {
|
|
crtc = modeset->crtc;
|
|
|
|
if (!connector_has_possible_crtc(connector, crtc))
|
|
continue;
|
|
|
|
for (o = 0; o < n; o++)
|
|
if (best_crtcs[o] == crtc)
|
|
break;
|
|
|
|
if (o < n) {
|
|
/* ignore cloning unless only a single crtc */
|
|
if (dev->mode_config.num_crtc > 1)
|
|
continue;
|
|
|
|
if (!drm_mode_equal(modes[o], modes[n]))
|
|
continue;
|
|
}
|
|
|
|
crtcs[n] = crtc;
|
|
memcpy(crtcs, best_crtcs, n * sizeof(*crtcs));
|
|
score = my_score + drm_client_pick_crtcs(client, connectors, connector_count,
|
|
crtcs, modes, n + 1, width, height);
|
|
if (score > best_score) {
|
|
best_score = score;
|
|
memcpy(best_crtcs, crtcs, connector_count * sizeof(*crtcs));
|
|
}
|
|
}
|
|
|
|
kfree(crtcs);
|
|
return best_score;
|
|
}
|
|
|
|
/* Try to read the BIOS display configuration and use it for the initial config */
|
|
static bool drm_client_firmware_config(struct drm_client_dev *client,
|
|
struct drm_connector **connectors,
|
|
unsigned int connector_count,
|
|
struct drm_crtc **crtcs,
|
|
struct drm_display_mode **modes,
|
|
struct drm_client_offset *offsets,
|
|
bool *enabled, int width, int height)
|
|
{
|
|
const int count = min_t(unsigned int, connector_count, BITS_PER_LONG);
|
|
unsigned long conn_configured, conn_seq, mask;
|
|
struct drm_device *dev = client->dev;
|
|
int i, j;
|
|
bool *save_enabled;
|
|
bool fallback = true, ret = true;
|
|
int num_connectors_enabled = 0;
|
|
int num_connectors_detected = 0;
|
|
int num_tiled_conns = 0;
|
|
struct drm_modeset_acquire_ctx ctx;
|
|
|
|
if (!drm_drv_uses_atomic_modeset(dev))
|
|
return false;
|
|
|
|
if (WARN_ON(count <= 0))
|
|
return false;
|
|
|
|
save_enabled = kcalloc(count, sizeof(bool), GFP_KERNEL);
|
|
if (!save_enabled)
|
|
return false;
|
|
|
|
drm_modeset_acquire_init(&ctx, 0);
|
|
|
|
while (drm_modeset_lock_all_ctx(dev, &ctx) != 0)
|
|
drm_modeset_backoff(&ctx);
|
|
|
|
memcpy(save_enabled, enabled, count);
|
|
mask = GENMASK(count - 1, 0);
|
|
conn_configured = 0;
|
|
for (i = 0; i < count; i++) {
|
|
if (connectors[i]->has_tile &&
|
|
connectors[i]->status == connector_status_connected)
|
|
num_tiled_conns++;
|
|
}
|
|
retry:
|
|
conn_seq = conn_configured;
|
|
for (i = 0; i < count; i++) {
|
|
struct drm_connector *connector;
|
|
struct drm_encoder *encoder;
|
|
struct drm_crtc *new_crtc;
|
|
|
|
connector = connectors[i];
|
|
|
|
if (conn_configured & BIT(i))
|
|
continue;
|
|
|
|
if (conn_seq == 0 && !connector->has_tile)
|
|
continue;
|
|
|
|
if (connector->status == connector_status_connected)
|
|
num_connectors_detected++;
|
|
|
|
if (!enabled[i]) {
|
|
DRM_DEBUG_KMS("connector %s not enabled, skipping\n",
|
|
connector->name);
|
|
conn_configured |= BIT(i);
|
|
continue;
|
|
}
|
|
|
|
if (connector->force == DRM_FORCE_OFF) {
|
|
DRM_DEBUG_KMS("connector %s is disabled by user, skipping\n",
|
|
connector->name);
|
|
enabled[i] = false;
|
|
continue;
|
|
}
|
|
|
|
encoder = connector->state->best_encoder;
|
|
if (!encoder || WARN_ON(!connector->state->crtc)) {
|
|
if (connector->force > DRM_FORCE_OFF)
|
|
goto bail;
|
|
|
|
DRM_DEBUG_KMS("connector %s has no encoder or crtc, skipping\n",
|
|
connector->name);
|
|
enabled[i] = false;
|
|
conn_configured |= BIT(i);
|
|
continue;
|
|
}
|
|
|
|
num_connectors_enabled++;
|
|
|
|
new_crtc = connector->state->crtc;
|
|
|
|
/*
|
|
* Make sure we're not trying to drive multiple connectors
|
|
* with a single CRTC, since our cloning support may not
|
|
* match the BIOS.
|
|
*/
|
|
for (j = 0; j < count; j++) {
|
|
if (crtcs[j] == new_crtc) {
|
|
DRM_DEBUG_KMS("fallback: cloned configuration\n");
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
DRM_DEBUG_KMS("looking for cmdline mode on connector %s\n",
|
|
connector->name);
|
|
|
|
/* go for command line mode first */
|
|
modes[i] = drm_connector_pick_cmdline_mode(connector);
|
|
|
|
/* try for preferred next */
|
|
if (!modes[i]) {
|
|
DRM_DEBUG_KMS("looking for preferred mode on connector %s %d\n",
|
|
connector->name, connector->has_tile);
|
|
modes[i] = drm_connector_has_preferred_mode(connector, width, height);
|
|
}
|
|
|
|
/* No preferred mode marked by the EDID? Are there any modes? */
|
|
if (!modes[i] && !list_empty(&connector->modes)) {
|
|
DRM_DEBUG_KMS("using first mode listed on connector %s\n",
|
|
connector->name);
|
|
modes[i] = list_first_entry(&connector->modes,
|
|
struct drm_display_mode,
|
|
head);
|
|
}
|
|
|
|
/* last resort: use current mode */
|
|
if (!modes[i]) {
|
|
/*
|
|
* IMPORTANT: We want to use the adjusted mode (i.e.
|
|
* after the panel fitter upscaling) as the initial
|
|
* config, not the input mode, which is what crtc->mode
|
|
* usually contains. But since our current
|
|
* code puts a mode derived from the post-pfit timings
|
|
* into crtc->mode this works out correctly.
|
|
*
|
|
* This is crtc->mode and not crtc->state->mode for the
|
|
* fastboot check to work correctly.
|
|
*/
|
|
DRM_DEBUG_KMS("looking for current mode on connector %s\n",
|
|
connector->name);
|
|
modes[i] = &connector->state->crtc->mode;
|
|
}
|
|
/*
|
|
* In case of tiled modes, if all tiles are not present
|
|
* then fallback to a non tiled mode.
|
|
*/
|
|
if (connector->has_tile &&
|
|
num_tiled_conns < connector->num_h_tile * connector->num_v_tile) {
|
|
DRM_DEBUG_KMS("Falling back to non tiled mode on Connector %d\n",
|
|
connector->base.id);
|
|
modes[i] = drm_connector_fallback_non_tiled_mode(connector);
|
|
}
|
|
crtcs[i] = new_crtc;
|
|
|
|
DRM_DEBUG_KMS("connector %s on [CRTC:%d:%s]: %dx%d%s\n",
|
|
connector->name,
|
|
connector->state->crtc->base.id,
|
|
connector->state->crtc->name,
|
|
modes[i]->hdisplay, modes[i]->vdisplay,
|
|
modes[i]->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "");
|
|
|
|
fallback = false;
|
|
conn_configured |= BIT(i);
|
|
}
|
|
|
|
if ((conn_configured & mask) != mask && conn_configured != conn_seq)
|
|
goto retry;
|
|
|
|
/*
|
|
* If the BIOS didn't enable everything it could, fall back to have the
|
|
* same user experiencing of lighting up as much as possible like the
|
|
* fbdev helper library.
|
|
*/
|
|
if (num_connectors_enabled != num_connectors_detected &&
|
|
num_connectors_enabled < dev->mode_config.num_crtc) {
|
|
DRM_DEBUG_KMS("fallback: Not all outputs enabled\n");
|
|
DRM_DEBUG_KMS("Enabled: %i, detected: %i\n", num_connectors_enabled,
|
|
num_connectors_detected);
|
|
fallback = true;
|
|
}
|
|
|
|
if (fallback) {
|
|
bail:
|
|
DRM_DEBUG_KMS("Not using firmware configuration\n");
|
|
memcpy(enabled, save_enabled, count);
|
|
ret = false;
|
|
}
|
|
|
|
drm_modeset_drop_locks(&ctx);
|
|
drm_modeset_acquire_fini(&ctx);
|
|
|
|
kfree(save_enabled);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* drm_client_modeset_probe() - Probe for displays
|
|
* @client: DRM client
|
|
* @width: Maximum display mode width (optional)
|
|
* @height: Maximum display mode height (optional)
|
|
*
|
|
* This function sets up display pipelines for enabled connectors and stores the
|
|
* config in the client's modeset array.
|
|
*
|
|
* Returns:
|
|
* Zero on success or negative error code on failure.
|
|
*/
|
|
int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height)
|
|
{
|
|
struct drm_connector *connector, **connectors = NULL;
|
|
struct drm_connector_list_iter conn_iter;
|
|
struct drm_device *dev = client->dev;
|
|
unsigned int total_modes_count = 0;
|
|
struct drm_client_offset *offsets;
|
|
unsigned int connector_count = 0;
|
|
struct drm_display_mode **modes;
|
|
struct drm_crtc **crtcs;
|
|
int i, ret = 0;
|
|
bool *enabled;
|
|
|
|
DRM_DEBUG_KMS("\n");
|
|
|
|
if (!width)
|
|
width = dev->mode_config.max_width;
|
|
if (!height)
|
|
height = dev->mode_config.max_height;
|
|
|
|
drm_connector_list_iter_begin(dev, &conn_iter);
|
|
drm_client_for_each_connector_iter(connector, &conn_iter) {
|
|
struct drm_connector **tmp;
|
|
|
|
tmp = krealloc(connectors, (connector_count + 1) * sizeof(*connectors), GFP_KERNEL);
|
|
if (!tmp) {
|
|
ret = -ENOMEM;
|
|
goto free_connectors;
|
|
}
|
|
|
|
connectors = tmp;
|
|
drm_connector_get(connector);
|
|
connectors[connector_count++] = connector;
|
|
}
|
|
drm_connector_list_iter_end(&conn_iter);
|
|
|
|
if (!connector_count)
|
|
return 0;
|
|
|
|
crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL);
|
|
modes = kcalloc(connector_count, sizeof(*modes), GFP_KERNEL);
|
|
offsets = kcalloc(connector_count, sizeof(*offsets), GFP_KERNEL);
|
|
enabled = kcalloc(connector_count, sizeof(bool), GFP_KERNEL);
|
|
if (!crtcs || !modes || !enabled || !offsets) {
|
|
DRM_ERROR("Memory allocation failed\n");
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&client->modeset_mutex);
|
|
|
|
mutex_lock(&dev->mode_config.mutex);
|
|
for (i = 0; i < connector_count; i++)
|
|
total_modes_count += connectors[i]->funcs->fill_modes(connectors[i], width, height);
|
|
if (!total_modes_count)
|
|
DRM_DEBUG_KMS("No connectors reported connected with modes\n");
|
|
drm_client_connectors_enabled(connectors, connector_count, enabled);
|
|
|
|
if (!drm_client_firmware_config(client, connectors, connector_count, crtcs,
|
|
modes, offsets, enabled, width, height)) {
|
|
memset(modes, 0, connector_count * sizeof(*modes));
|
|
memset(crtcs, 0, connector_count * sizeof(*crtcs));
|
|
memset(offsets, 0, connector_count * sizeof(*offsets));
|
|
|
|
if (!drm_client_target_cloned(dev, connectors, connector_count, modes,
|
|
offsets, enabled, width, height) &&
|
|
!drm_client_target_preferred(connectors, connector_count, modes,
|
|
offsets, enabled, width, height))
|
|
DRM_ERROR("Unable to find initial modes\n");
|
|
|
|
DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n",
|
|
width, height);
|
|
|
|
drm_client_pick_crtcs(client, connectors, connector_count,
|
|
crtcs, modes, 0, width, height);
|
|
}
|
|
mutex_unlock(&dev->mode_config.mutex);
|
|
|
|
drm_client_modeset_release(client);
|
|
|
|
for (i = 0; i < connector_count; i++) {
|
|
struct drm_display_mode *mode = modes[i];
|
|
struct drm_crtc *crtc = crtcs[i];
|
|
struct drm_client_offset *offset = &offsets[i];
|
|
|
|
if (mode && crtc) {
|
|
struct drm_mode_set *modeset = drm_client_find_modeset(client, crtc);
|
|
struct drm_connector *connector = connectors[i];
|
|
|
|
DRM_DEBUG_KMS("desired mode %s set on crtc %d (%d,%d)\n",
|
|
mode->name, crtc->base.id, offset->x, offset->y);
|
|
|
|
if (WARN_ON_ONCE(modeset->num_connectors == DRM_CLIENT_MAX_CLONED_CONNECTORS ||
|
|
(dev->mode_config.num_crtc > 1 && modeset->num_connectors == 1))) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
modeset->mode = drm_mode_duplicate(dev, mode);
|
|
drm_connector_get(connector);
|
|
modeset->connectors[modeset->num_connectors++] = connector;
|
|
modeset->x = offset->x;
|
|
modeset->y = offset->y;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&client->modeset_mutex);
|
|
out:
|
|
kfree(crtcs);
|
|
kfree(modes);
|
|
kfree(offsets);
|
|
kfree(enabled);
|
|
free_connectors:
|
|
for (i = 0; i < connector_count; i++)
|
|
drm_connector_put(connectors[i]);
|
|
kfree(connectors);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(drm_client_modeset_probe);
|
|
|
|
/**
|
|
* drm_client_rotation() - Check the initial rotation value
|
|
* @modeset: DRM modeset
|
|
* @rotation: Returned rotation value
|
|
*
|
|
* This function checks if the primary plane in @modeset can hw rotate
|
|
* to match the rotation needed on its connector.
|
|
*
|
|
* Note: Currently only 0 and 180 degrees are supported.
|
|
*
|
|
* Return:
|
|
* True if the plane can do the rotation, false otherwise.
|
|
*/
|
|
bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
|
|
{
|
|
struct drm_connector *connector = modeset->connectors[0];
|
|
struct drm_plane *plane = modeset->crtc->primary;
|
|
struct drm_cmdline_mode *cmdline;
|
|
u64 valid_mask = 0;
|
|
unsigned int i;
|
|
|
|
if (!modeset->num_connectors)
|
|
return false;
|
|
|
|
switch (connector->display_info.panel_orientation) {
|
|
case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP:
|
|
*rotation = DRM_MODE_ROTATE_180;
|
|
break;
|
|
case DRM_MODE_PANEL_ORIENTATION_LEFT_UP:
|
|
*rotation = DRM_MODE_ROTATE_90;
|
|
break;
|
|
case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP:
|
|
*rotation = DRM_MODE_ROTATE_270;
|
|
break;
|
|
default:
|
|
*rotation = DRM_MODE_ROTATE_0;
|
|
}
|
|
|
|
/**
|
|
* The panel already defined the default rotation
|
|
* through its orientation. Whatever has been provided
|
|
* on the command line needs to be added to that.
|
|
*
|
|
* Unfortunately, the rotations are at different bit
|
|
* indices, so the math to add them up are not as
|
|
* trivial as they could.
|
|
*
|
|
* Reflections on the other hand are pretty trivial to deal with, a
|
|
* simple XOR between the two handle the addition nicely.
|
|
*/
|
|
cmdline = &connector->cmdline_mode;
|
|
if (cmdline->specified && cmdline->rotation_reflection) {
|
|
unsigned int cmdline_rest, panel_rest;
|
|
unsigned int cmdline_rot, panel_rot;
|
|
unsigned int sum_rot, sum_rest;
|
|
|
|
panel_rot = ilog2(*rotation & DRM_MODE_ROTATE_MASK);
|
|
cmdline_rot = ilog2(cmdline->rotation_reflection & DRM_MODE_ROTATE_MASK);
|
|
sum_rot = (panel_rot + cmdline_rot) % 4;
|
|
|
|
panel_rest = *rotation & ~DRM_MODE_ROTATE_MASK;
|
|
cmdline_rest = cmdline->rotation_reflection & ~DRM_MODE_ROTATE_MASK;
|
|
sum_rest = panel_rest ^ cmdline_rest;
|
|
|
|
*rotation = (1 << sum_rot) | sum_rest;
|
|
}
|
|
|
|
/*
|
|
* TODO: support 90 / 270 degree hardware rotation,
|
|
* depending on the hardware this may require the framebuffer
|
|
* to be in a specific tiling format.
|
|
*/
|
|
if (((*rotation & DRM_MODE_ROTATE_MASK) != DRM_MODE_ROTATE_0 &&
|
|
(*rotation & DRM_MODE_ROTATE_MASK) != DRM_MODE_ROTATE_180) ||
|
|
!plane->rotation_property)
|
|
return false;
|
|
|
|
for (i = 0; i < plane->rotation_property->num_values; i++)
|
|
valid_mask |= (1ULL << plane->rotation_property->values[i]);
|
|
|
|
if (!(*rotation & valid_mask))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(drm_client_rotation);
|
|
|
|
static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
|
|
{
|
|
struct drm_device *dev = client->dev;
|
|
struct drm_plane *plane;
|
|
struct drm_atomic_state *state;
|
|
struct drm_modeset_acquire_ctx ctx;
|
|
struct drm_mode_set *mode_set;
|
|
int ret;
|
|
|
|
drm_modeset_acquire_init(&ctx, 0);
|
|
|
|
state = drm_atomic_state_alloc(dev);
|
|
if (!state) {
|
|
ret = -ENOMEM;
|
|
goto out_ctx;
|
|
}
|
|
|
|
state->acquire_ctx = &ctx;
|
|
retry:
|
|
drm_for_each_plane(plane, dev) {
|
|
struct drm_plane_state *plane_state;
|
|
|
|
plane_state = drm_atomic_get_plane_state(state, plane);
|
|
if (IS_ERR(plane_state)) {
|
|
ret = PTR_ERR(plane_state);
|
|
goto out_state;
|
|
}
|
|
|
|
plane_state->rotation = DRM_MODE_ROTATE_0;
|
|
|
|
/* disable non-primary: */
|
|
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
|
|
continue;
|
|
|
|
ret = __drm_atomic_helper_disable_plane(plane, plane_state);
|
|
if (ret != 0)
|
|
goto out_state;
|
|
}
|
|
|
|
drm_client_for_each_modeset(mode_set, client) {
|
|
struct drm_plane *primary = mode_set->crtc->primary;
|
|
unsigned int rotation;
|
|
|
|
if (drm_client_rotation(mode_set, &rotation)) {
|
|
struct drm_plane_state *plane_state;
|
|
|
|
/* Cannot fail as we've already gotten the plane state above */
|
|
plane_state = drm_atomic_get_new_plane_state(state, primary);
|
|
plane_state->rotation = rotation;
|
|
}
|
|
|
|
ret = __drm_atomic_helper_set_config(mode_set, state);
|
|
if (ret != 0)
|
|
goto out_state;
|
|
|
|
/*
|
|
* __drm_atomic_helper_set_config() sets active when a
|
|
* mode is set, unconditionally clear it if we force DPMS off
|
|
*/
|
|
if (!active) {
|
|
struct drm_crtc *crtc = mode_set->crtc;
|
|
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
|
|
|
crtc_state->active = false;
|
|
}
|
|
}
|
|
|
|
if (check)
|
|
ret = drm_atomic_check_only(state);
|
|
else
|
|
ret = drm_atomic_commit(state);
|
|
|
|
out_state:
|
|
if (ret == -EDEADLK)
|
|
goto backoff;
|
|
|
|
drm_atomic_state_put(state);
|
|
out_ctx:
|
|
drm_modeset_drop_locks(&ctx);
|
|
drm_modeset_acquire_fini(&ctx);
|
|
|
|
return ret;
|
|
|
|
backoff:
|
|
drm_atomic_state_clear(state);
|
|
drm_modeset_backoff(&ctx);
|
|
|
|
goto retry;
|
|
}
|
|
|
|
static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
|
|
{
|
|
struct drm_device *dev = client->dev;
|
|
struct drm_mode_set *mode_set;
|
|
struct drm_plane *plane;
|
|
int ret = 0;
|
|
|
|
drm_modeset_lock_all(dev);
|
|
drm_for_each_plane(plane, dev) {
|
|
if (plane->type != DRM_PLANE_TYPE_PRIMARY)
|
|
drm_plane_force_disable(plane);
|
|
|
|
if (plane->rotation_property)
|
|
drm_mode_plane_set_obj_prop(plane,
|
|
plane->rotation_property,
|
|
DRM_MODE_ROTATE_0);
|
|
}
|
|
|
|
drm_client_for_each_modeset(mode_set, client) {
|
|
struct drm_crtc *crtc = mode_set->crtc;
|
|
|
|
if (crtc->funcs->cursor_set2) {
|
|
ret = crtc->funcs->cursor_set2(crtc, NULL, 0, 0, 0, 0, 0);
|
|
if (ret)
|
|
goto out;
|
|
} else if (crtc->funcs->cursor_set) {
|
|
ret = crtc->funcs->cursor_set(crtc, NULL, 0, 0, 0);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
ret = drm_mode_set_config_internal(mode_set);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
out:
|
|
drm_modeset_unlock_all(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* drm_client_modeset_check() - Check modeset configuration
|
|
* @client: DRM client
|
|
*
|
|
* Check modeset configuration.
|
|
*
|
|
* Returns:
|
|
* Zero on success or negative error code on failure.
|
|
*/
|
|
int drm_client_modeset_check(struct drm_client_dev *client)
|
|
{
|
|
int ret;
|
|
|
|
if (!drm_drv_uses_atomic_modeset(client->dev))
|
|
return 0;
|
|
|
|
mutex_lock(&client->modeset_mutex);
|
|
ret = drm_client_modeset_commit_atomic(client, true, true);
|
|
mutex_unlock(&client->modeset_mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(drm_client_modeset_check);
|
|
|
|
/**
|
|
* drm_client_modeset_commit_locked() - Force commit CRTC configuration
|
|
* @client: DRM client
|
|
*
|
|
* Commit modeset configuration to crtcs without checking if there is a DRM
|
|
* master. The assumption is that the caller already holds an internal DRM
|
|
* master reference acquired with drm_master_internal_acquire().
|
|
*
|
|
* Returns:
|
|
* Zero on success or negative error code on failure.
|
|
*/
|
|
int drm_client_modeset_commit_locked(struct drm_client_dev *client)
|
|
{
|
|
struct drm_device *dev = client->dev;
|
|
int ret;
|
|
|
|
mutex_lock(&client->modeset_mutex);
|
|
if (drm_drv_uses_atomic_modeset(dev))
|
|
ret = drm_client_modeset_commit_atomic(client, true, false);
|
|
else
|
|
ret = drm_client_modeset_commit_legacy(client);
|
|
mutex_unlock(&client->modeset_mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(drm_client_modeset_commit_locked);
|
|
|
|
/**
|
|
* drm_client_modeset_commit() - Commit CRTC configuration
|
|
* @client: DRM client
|
|
*
|
|
* Commit modeset configuration to crtcs.
|
|
*
|
|
* Returns:
|
|
* Zero on success or negative error code on failure.
|
|
*/
|
|
int drm_client_modeset_commit(struct drm_client_dev *client)
|
|
{
|
|
struct drm_device *dev = client->dev;
|
|
int ret;
|
|
|
|
if (!drm_master_internal_acquire(dev))
|
|
return -EBUSY;
|
|
|
|
ret = drm_client_modeset_commit_locked(client);
|
|
|
|
drm_master_internal_release(dev);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(drm_client_modeset_commit);
|
|
|
|
static void drm_client_modeset_dpms_legacy(struct drm_client_dev *client, int dpms_mode)
|
|
{
|
|
struct drm_device *dev = client->dev;
|
|
struct drm_connector *connector;
|
|
struct drm_mode_set *modeset;
|
|
struct drm_modeset_acquire_ctx ctx;
|
|
int j;
|
|
int ret;
|
|
|
|
DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret);
|
|
drm_client_for_each_modeset(modeset, client) {
|
|
if (!modeset->crtc->enabled)
|
|
continue;
|
|
|
|
for (j = 0; j < modeset->num_connectors; j++) {
|
|
connector = modeset->connectors[j];
|
|
connector->funcs->dpms(connector, dpms_mode);
|
|
drm_object_property_set_value(&connector->base,
|
|
dev->mode_config.dpms_property, dpms_mode);
|
|
}
|
|
}
|
|
DRM_MODESET_LOCK_ALL_END(dev, ctx, ret);
|
|
}
|
|
|
|
/**
|
|
* drm_client_modeset_dpms() - Set DPMS mode
|
|
* @client: DRM client
|
|
* @mode: DPMS mode
|
|
*
|
|
* Note: For atomic drivers @mode is reduced to on/off.
|
|
*
|
|
* Returns:
|
|
* Zero on success or negative error code on failure.
|
|
*/
|
|
int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
|
|
{
|
|
struct drm_device *dev = client->dev;
|
|
int ret = 0;
|
|
|
|
if (!drm_master_internal_acquire(dev))
|
|
return -EBUSY;
|
|
|
|
mutex_lock(&client->modeset_mutex);
|
|
if (drm_drv_uses_atomic_modeset(dev))
|
|
ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
|
|
else
|
|
drm_client_modeset_dpms_legacy(client, mode);
|
|
mutex_unlock(&client->modeset_mutex);
|
|
|
|
drm_master_internal_release(dev);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(drm_client_modeset_dpms);
|
|
|
|
#ifdef CONFIG_DRM_KUNIT_TEST
|
|
#include "tests/drm_client_modeset_test.c"
|
|
#endif
|