mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 01:05:08 +07:00
46cc2d76a7
The drm_client_modeset_commit_atomic function uses two times the plane_state variable in inner blocks of code, but the variable has a scope global to this function. This will lead to inadvertent devs to reuse the variable in the second block with the value left by the first, without any warning from the compiler since value would have been initialized. Fix this by moving the variable declaration to the proper scope. Reviewed-by: Noralf Trønnes <noralf@tronnes.org> Signed-off-by: Maxime Ripard <maxime.ripard@bootlin.com> Link: https://patchwork.freedesktop.org/patch/msgid/8bd9696ea915a4ad08be6d93a4d9565e8d6aa2f3.1560783090.git-series.maxime.ripard@bootlin.com
1091 lines
28 KiB
C
1091 lines
28 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 <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.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_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_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;
|
|
|
|
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, if not add a CVT mode that conforms
|
|
*/
|
|
if (cmdline_mode->rb || cmdline_mode->margins)
|
|
goto create_mode;
|
|
|
|
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 (mode->vrefresh != 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;
|
|
}
|
|
|
|
create_mode:
|
|
mode = drm_mode_create_from_cmdline_mode(connector->dev, cmdline_mode);
|
|
list_add(&mode->head, &connector->modes);
|
|
|
|
return mode;
|
|
}
|
|
|
|
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" : enabled[i] ? "yes" : "no");
|
|
|
|
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 i;
|
|
|
|
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;
|
|
}
|
|
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;
|
|
int i;
|
|
|
|
drm_connector_for_each_possible_encoder(connector, encoder, i) {
|
|
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)
|
|
{
|
|
unsigned 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;
|
|
struct drm_modeset_acquire_ctx ctx;
|
|
|
|
if (!drm_drv_uses_atomic_modeset(dev))
|
|
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;
|
|
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;
|
|
}
|
|
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_panel_rotation() - Check panel orientation
|
|
* @modeset: DRM modeset
|
|
* @rotation: Returned rotation value
|
|
*
|
|
* This function checks if the primary plane in @modeset can hw rotate to match
|
|
* the panel orientation 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_panel_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
|
|
{
|
|
struct drm_connector *connector = modeset->connectors[0];
|
|
struct drm_plane *plane = modeset->crtc->primary;
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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_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_panel_rotation);
|
|
|
|
static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active)
|
|
{
|
|
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_panel_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;
|
|
}
|
|
}
|
|
|
|
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_commit_force() - Force commit CRTC configuration
|
|
* @client: DRM client
|
|
*
|
|
* Commit modeset configuration to crtcs without checking if there is a DRM master.
|
|
*
|
|
* Returns:
|
|
* Zero on success or negative error code on failure.
|
|
*/
|
|
int drm_client_modeset_commit_force(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);
|
|
else
|
|
ret = drm_client_modeset_commit_legacy(client);
|
|
mutex_unlock(&client->modeset_mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(drm_client_modeset_commit_force);
|
|
|
|
/**
|
|
* 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_force(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;
|
|
int j;
|
|
|
|
drm_modeset_lock_all(dev);
|
|
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_unlock_all(dev);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
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);
|