linux_dsm_epyc7002/drivers/gpu/drm/drm_client_modeset.c

1231 lines
32 KiB
C
Raw Normal View History

drm/fb-helper: Remove drm_fb_helper_crtc struct drm_fb_helper_crtc is now just a wrapper around drm_mode_set so use that directly instead and attach it as a modeset array onto drm_client_dev. drm_fb_helper will use this array to store its modesets which means it will always initialize a drm_client, but it will not register the client (callbacks) unless it's the generic fbdev emulation. Code will later be moved to drm_client, so add code there in a new file drm_client_modeset.c with MIT license to match drm_fb_helper.c. The modeset connector array size is hardcoded for the cloned case to avoid having to pass in a value from the driver. A value of 8 is chosen to err on the safe side. This means that the max connector argument for drm_fb_helper_init() and drm_fb_helper_fbdev_setup() isn't used anymore, a todo entry for this is added. In pan_display_atomic() restore_fbdev_mode_force() is used instead of restore_fbdev_mode_atomic() because that one will later become internal to drm_client_modeset. Locking order: 1. drm_fb_helper->lock 2. drm_master_internal_acquire 3. drm_client_dev->modeset_mutex v6: Improve commit message (Sam Ravnborg) v3: - Use full drm_client_init/release for the modesets (Daniel Vetter) - drm_client_for_each_modeset: use lockdep_assert_held (Daniel Vetter) - Hook up to Documentation/gpu/drm-client.rst (Daniel Vetter) v2: - Add modesets array to drm_client (Daniel Vetter) - Use a new file for the modeset code (Daniel Vetter) - File has to be MIT licensed (Emmanuel Vadot) - Add copyrights from drm_fb_helper.c Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190531140117.37751-3-noralf@tronnes.org
2019-05-31 21:01:11 +07:00
// 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>
drm/fb-helper: Remove drm_fb_helper_crtc struct drm_fb_helper_crtc is now just a wrapper around drm_mode_set so use that directly instead and attach it as a modeset array onto drm_client_dev. drm_fb_helper will use this array to store its modesets which means it will always initialize a drm_client, but it will not register the client (callbacks) unless it's the generic fbdev emulation. Code will later be moved to drm_client, so add code there in a new file drm_client_modeset.c with MIT license to match drm_fb_helper.c. The modeset connector array size is hardcoded for the cloned case to avoid having to pass in a value from the driver. A value of 8 is chosen to err on the safe side. This means that the max connector argument for drm_fb_helper_init() and drm_fb_helper_fbdev_setup() isn't used anymore, a todo entry for this is added. In pan_display_atomic() restore_fbdev_mode_force() is used instead of restore_fbdev_mode_atomic() because that one will later become internal to drm_client_modeset. Locking order: 1. drm_fb_helper->lock 2. drm_master_internal_acquire 3. drm_client_dev->modeset_mutex v6: Improve commit message (Sam Ravnborg) v3: - Use full drm_client_init/release for the modesets (Daniel Vetter) - drm_client_for_each_modeset: use lockdep_assert_held (Daniel Vetter) - Hook up to Documentation/gpu/drm-client.rst (Daniel Vetter) v2: - Add modesets array to drm_client (Daniel Vetter) - Use a new file for the modeset code (Daniel Vetter) - File has to be MIT licensed (Emmanuel Vadot) - Add copyrights from drm_fb_helper.c Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190531140117.37751-3-noralf@tronnes.org
2019-05-31 21:01:11 +07:00
#include <drm/drm_client.h>
#include <drm/drm_connector.h>
drm/fb-helper: Remove drm_fb_helper_crtc struct drm_fb_helper_crtc is now just a wrapper around drm_mode_set so use that directly instead and attach it as a modeset array onto drm_client_dev. drm_fb_helper will use this array to store its modesets which means it will always initialize a drm_client, but it will not register the client (callbacks) unless it's the generic fbdev emulation. Code will later be moved to drm_client, so add code there in a new file drm_client_modeset.c with MIT license to match drm_fb_helper.c. The modeset connector array size is hardcoded for the cloned case to avoid having to pass in a value from the driver. A value of 8 is chosen to err on the safe side. This means that the max connector argument for drm_fb_helper_init() and drm_fb_helper_fbdev_setup() isn't used anymore, a todo entry for this is added. In pan_display_atomic() restore_fbdev_mode_force() is used instead of restore_fbdev_mode_atomic() because that one will later become internal to drm_client_modeset. Locking order: 1. drm_fb_helper->lock 2. drm_master_internal_acquire 3. drm_client_dev->modeset_mutex v6: Improve commit message (Sam Ravnborg) v3: - Use full drm_client_init/release for the modesets (Daniel Vetter) - drm_client_for_each_modeset: use lockdep_assert_held (Daniel Vetter) - Hook up to Documentation/gpu/drm-client.rst (Daniel Vetter) v2: - Add modesets array to drm_client (Daniel Vetter) - Use a new file for the modeset code (Daniel Vetter) - File has to be MIT licensed (Emmanuel Vadot) - Add copyrights from drm_fb_helper.c Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190531140117.37751-3-noralf@tronnes.org
2019-05-31 21:01:11 +07:00
#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"
drm/fb-helper: Remove drm_fb_helper_crtc struct drm_fb_helper_crtc is now just a wrapper around drm_mode_set so use that directly instead and attach it as a modeset array onto drm_client_dev. drm_fb_helper will use this array to store its modesets which means it will always initialize a drm_client, but it will not register the client (callbacks) unless it's the generic fbdev emulation. Code will later be moved to drm_client, so add code there in a new file drm_client_modeset.c with MIT license to match drm_fb_helper.c. The modeset connector array size is hardcoded for the cloned case to avoid having to pass in a value from the driver. A value of 8 is chosen to err on the safe side. This means that the max connector argument for drm_fb_helper_init() and drm_fb_helper_fbdev_setup() isn't used anymore, a todo entry for this is added. In pan_display_atomic() restore_fbdev_mode_force() is used instead of restore_fbdev_mode_atomic() because that one will later become internal to drm_client_modeset. Locking order: 1. drm_fb_helper->lock 2. drm_master_internal_acquire 3. drm_client_dev->modeset_mutex v6: Improve commit message (Sam Ravnborg) v3: - Use full drm_client_init/release for the modesets (Daniel Vetter) - drm_client_for_each_modeset: use lockdep_assert_held (Daniel Vetter) - Hook up to Documentation/gpu/drm-client.rst (Daniel Vetter) v2: - Add modesets array to drm_client (Daniel Vetter) - Use a new file for the modeset code (Daniel Vetter) - File has to be MIT licensed (Emmanuel Vadot) - Add copyrights from drm_fb_helper.c Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190531140117.37751-3-noralf@tronnes.org
2019-05-31 21:01:11 +07:00
#define DRM_CLIENT_MAX_CLONED_CONNECTORS 8
struct drm_client_offset {
int x, y;
};
drm/fb-helper: Remove drm_fb_helper_crtc struct drm_fb_helper_crtc is now just a wrapper around drm_mode_set so use that directly instead and attach it as a modeset array onto drm_client_dev. drm_fb_helper will use this array to store its modesets which means it will always initialize a drm_client, but it will not register the client (callbacks) unless it's the generic fbdev emulation. Code will later be moved to drm_client, so add code there in a new file drm_client_modeset.c with MIT license to match drm_fb_helper.c. The modeset connector array size is hardcoded for the cloned case to avoid having to pass in a value from the driver. A value of 8 is chosen to err on the safe side. This means that the max connector argument for drm_fb_helper_init() and drm_fb_helper_fbdev_setup() isn't used anymore, a todo entry for this is added. In pan_display_atomic() restore_fbdev_mode_force() is used instead of restore_fbdev_mode_atomic() because that one will later become internal to drm_client_modeset. Locking order: 1. drm_fb_helper->lock 2. drm_master_internal_acquire 3. drm_client_dev->modeset_mutex v6: Improve commit message (Sam Ravnborg) v3: - Use full drm_client_init/release for the modesets (Daniel Vetter) - drm_client_for_each_modeset: use lockdep_assert_held (Daniel Vetter) - Hook up to Documentation/gpu/drm-client.rst (Daniel Vetter) v2: - Add modesets array to drm_client (Daniel Vetter) - Use a new file for the modeset code (Daniel Vetter) - File has to be MIT licensed (Emmanuel Vadot) - Add copyrights from drm_fb_helper.c Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190531140117.37751-3-noralf@tronnes.org
2019-05-31 21:01:11 +07:00
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)
drm/fb-helper: Remove drm_fb_helper_crtc struct drm_fb_helper_crtc is now just a wrapper around drm_mode_set so use that directly instead and attach it as a modeset array onto drm_client_dev. drm_fb_helper will use this array to store its modesets which means it will always initialize a drm_client, but it will not register the client (callbacks) unless it's the generic fbdev emulation. Code will later be moved to drm_client, so add code there in a new file drm_client_modeset.c with MIT license to match drm_fb_helper.c. The modeset connector array size is hardcoded for the cloned case to avoid having to pass in a value from the driver. A value of 8 is chosen to err on the safe side. This means that the max connector argument for drm_fb_helper_init() and drm_fb_helper_fbdev_setup() isn't used anymore, a todo entry for this is added. In pan_display_atomic() restore_fbdev_mode_force() is used instead of restore_fbdev_mode_atomic() because that one will later become internal to drm_client_modeset. Locking order: 1. drm_fb_helper->lock 2. drm_master_internal_acquire 3. drm_client_dev->modeset_mutex v6: Improve commit message (Sam Ravnborg) v3: - Use full drm_client_init/release for the modesets (Daniel Vetter) - drm_client_for_each_modeset: use lockdep_assert_held (Daniel Vetter) - Hook up to Documentation/gpu/drm-client.rst (Daniel Vetter) v2: - Add modesets array to drm_client (Daniel Vetter) - Use a new file for the modeset code (Daniel Vetter) - File has to be MIT licensed (Emmanuel Vadot) - Add copyrights from drm_fb_helper.c Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190531140117.37751-3-noralf@tronnes.org
2019-05-31 21:01:11 +07:00
{
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)
drm/fb-helper: Remove drm_fb_helper_crtc struct drm_fb_helper_crtc is now just a wrapper around drm_mode_set so use that directly instead and attach it as a modeset array onto drm_client_dev. drm_fb_helper will use this array to store its modesets which means it will always initialize a drm_client, but it will not register the client (callbacks) unless it's the generic fbdev emulation. Code will later be moved to drm_client, so add code there in a new file drm_client_modeset.c with MIT license to match drm_fb_helper.c. The modeset connector array size is hardcoded for the cloned case to avoid having to pass in a value from the driver. A value of 8 is chosen to err on the safe side. This means that the max connector argument for drm_fb_helper_init() and drm_fb_helper_fbdev_setup() isn't used anymore, a todo entry for this is added. In pan_display_atomic() restore_fbdev_mode_force() is used instead of restore_fbdev_mode_atomic() because that one will later become internal to drm_client_modeset. Locking order: 1. drm_fb_helper->lock 2. drm_master_internal_acquire 3. drm_client_dev->modeset_mutex v6: Improve commit message (Sam Ravnborg) v3: - Use full drm_client_init/release for the modesets (Daniel Vetter) - drm_client_for_each_modeset: use lockdep_assert_held (Daniel Vetter) - Hook up to Documentation/gpu/drm-client.rst (Daniel Vetter) v2: - Add modesets array to drm_client (Daniel Vetter) - Use a new file for the modeset code (Daniel Vetter) - File has to be MIT licensed (Emmanuel Vadot) - Add copyrights from drm_fb_helper.c Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190531140117.37751-3-noralf@tronnes.org
2019-05-31 21:01:11 +07:00
{
struct drm_mode_set *modeset;
drm_client_for_each_modeset(modeset, client)
if (modeset->crtc == crtc)
return modeset;
return NULL;
}
drm/fbdev: Fallback to non tiled mode if all tiles not present In case of tiled displays, if we hotplug just one connector, fbcon currently just selects the preferred mode and if it is tiled mode then that becomes a problem if rest of the tiles are not present. So in the fbdev driver on hotplug when we probe the client modeset, if we dont find all the connectors for all tiles, then on a connector with one tile, just fallback to the first available non tiled mode to display over a single connector. On the hotplug of the consecutive tiled connectors, if the tiled mode no longer exists because of fbcon size limitation, then return no modes for consecutive tiles but retain the non tiled mode on the 0th tile. Use the same logic in case of connected boot case as well. This has been tested with Dell UP328K tiled monitor. v3: * Check Num tiled conns that are connected (Manasi) v2: * Set the modes on consecutive hotplugged tiles to no mode if tiled mode is pruned (Dave) v1: * Just handle the 1st connector hotplug case * v1 Reviewed-by: Dave Airlie <airlied@redhat.com> Bugzilla: https://gitlab.freedesktop.org/drm/intel/issues/5 Suggested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Suggested-by: Dave Airlie <airlied@redhat.com> Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: Dave Airlie <airlied@redhat.com> Signed-off-by: Manasi Navare <manasi.d.navare@intel.com> Reviewed-by: Dave Airlie <airlied@redhat.com> (v2) Link: https://patchwork.freedesktop.org/patch/msgid/20191211212433.18185-2-manasi.d.navare@intel.com
2019-12-12 04:24:33 +07:00
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;
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 (optional) mode name first */
if (!strcmp(mode->name, cmdline_mode->name))
return mode;
/* check width/height */
if (mode->hdisplay != cmdline_mode->xres ||
mode->vdisplay != cmdline_mode->yres)
continue;
if (cmdline_mode->refresh_specified) {
drm: Nuke mode->vrefresh Get rid of mode->vrefresh and just calculate it on demand. Saves a bit of space and avoids the cached value getting out of sync with reality. Mostly done with cocci, with the following manual fixups: - Remove the now empty loop in drm_helper_probe_single_connector_modes() - Fix __MODE() macro in ch7006_mode.c - Fix DRM_MODE_ARG() macro in drm_modes.h - Remove leftover comment from samsung_s6d16d0_mode - Drop the TODO @@ @@ struct drm_display_mode { ... - int vrefresh; ... }; @@ identifier N; expression E; @@ struct drm_display_mode N = { - .vrefresh = E }; @@ identifier N; expression E; @@ struct drm_display_mode N[...] = { ..., { - .vrefresh = E } ,... }; @@ expression E; @@ { DRM_MODE(...), - .vrefresh = E, } @@ identifier M, R; @@ int drm_mode_vrefresh(const struct drm_display_mode *M) { ... - if (M->vrefresh > 0) - R = M->vrefresh; - else if (...) { ... } ... } @@ struct drm_display_mode *p; expression E; @@ ( - p->vrefresh = E; | - p->vrefresh + drm_mode_vrefresh(p) ) @@ struct drm_display_mode s; expression E; @@ ( - s.vrefresh = E; | - s.vrefresh + drm_mode_vrefresh(&s) ) @@ expression E; @@ - drm_mode_vrefresh(E) ? drm_mode_vrefresh(E) : drm_mode_vrefresh(E) + drm_mode_vrefresh(E) @find_substruct@ identifier X; identifier S; @@ struct X { ... struct drm_display_mode S; ... }; @@ identifier find_substruct.S; expression E; identifier I; @@ { .S = { - .vrefresh = E } } @@ identifier find_substruct.S; identifier find_substruct.X; expression E; identifier I; @@ struct X I[...] = { ..., .S = { - .vrefresh = E } ,... }; v2: Drop TODO v3: Rebase v4: Rebase Cc: Andrzej Hajda <a.hajda@samsung.com> Cc: Neil Armstrong <narmstrong@baylibre.com> Cc: Laurent Pinchart <Laurent.pinchart@ideasonboard.com> Cc: Jonas Karlman <jonas@kwiboo.se> Cc: Jernej Skrabec <jernej.skrabec@siol.net> Cc: Inki Dae <inki.dae@samsung.com> Cc: Joonyoung Shim <jy0922.shim@samsung.com> Cc: Seung-Woo Kim <sw0312.kim@samsung.com> Cc: Kyungmin Park <kyungmin.park@samsung.com> Cc: Linus Walleij <linus.walleij@linaro.org> Cc: CK Hu <ck.hu@mediatek.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Ben Skeggs <bskeggs@redhat.com> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: Sam Ravnborg <sam@ravnborg.org> Cc: Jerry Han <hanxu5@huaqin.corp-partner.google.com> Cc: Icenowy Zheng <icenowy@aosc.io> Cc: Jagan Teki <jagan@amarulasolutions.com> Cc: Stefan Mavrodiev <stefan@olimex.com> Cc: Robert Chiras <robert.chiras@nxp.com> Cc: "Guido Günther" <agx@sigxcpu.org> Cc: Purism Kernel Team <kernel@puri.sm> Cc: Benjamin Gaignard <benjamin.gaignard@linaro.org> Cc: Vincent Abriou <vincent.abriou@st.com> Cc: VMware Graphics <linux-graphics-maintainer@vmware.com> Cc: Thomas Hellstrom <thellstrom@vmware.com> Cc: linux-amlogic@lists.infradead.org Cc: nouveau@lists.freedesktop.org Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Emil Velikov <emil.velikov@collabora.com> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Acked-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200428171940.19552-4-ville.syrjala@linux.intel.com
2020-04-29 00:19:27 +07:00
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;
}
create_mode:
mode = drm_mode_create_from_cmdline_mode(connector->dev, cmdline_mode);
if (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;
drm/fbdev: Fallback to non tiled mode if all tiles not present In case of tiled displays, if we hotplug just one connector, fbcon currently just selects the preferred mode and if it is tiled mode then that becomes a problem if rest of the tiles are not present. So in the fbdev driver on hotplug when we probe the client modeset, if we dont find all the connectors for all tiles, then on a connector with one tile, just fallback to the first available non tiled mode to display over a single connector. On the hotplug of the consecutive tiled connectors, if the tiled mode no longer exists because of fbcon size limitation, then return no modes for consecutive tiles but retain the non tiled mode on the 0th tile. Use the same logic in case of connected boot case as well. This has been tested with Dell UP328K tiled monitor. v3: * Check Num tiled conns that are connected (Manasi) v2: * Set the modes on consecutive hotplugged tiles to no mode if tiled mode is pruned (Dave) v1: * Just handle the 1st connector hotplug case * v1 Reviewed-by: Dave Airlie <airlied@redhat.com> Bugzilla: https://gitlab.freedesktop.org/drm/intel/issues/5 Suggested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Suggested-by: Dave Airlie <airlied@redhat.com> Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: Dave Airlie <airlied@redhat.com> Signed-off-by: Manasi Navare <manasi.d.navare@intel.com> Reviewed-by: Dave Airlie <airlied@redhat.com> (v2) Link: https://patchwork.freedesktop.org/patch/msgid/20191211212433.18185-2-manasi.d.navare@intel.com
2019-12-12 04:24:33 +07:00
int num_tiled_conns = 0;
int i;
drm/fbdev: Fallback to non tiled mode if all tiles not present In case of tiled displays, if we hotplug just one connector, fbcon currently just selects the preferred mode and if it is tiled mode then that becomes a problem if rest of the tiles are not present. So in the fbdev driver on hotplug when we probe the client modeset, if we dont find all the connectors for all tiles, then on a connector with one tile, just fallback to the first available non tiled mode to display over a single connector. On the hotplug of the consecutive tiled connectors, if the tiled mode no longer exists because of fbcon size limitation, then return no modes for consecutive tiles but retain the non tiled mode on the 0th tile. Use the same logic in case of connected boot case as well. This has been tested with Dell UP328K tiled monitor. v3: * Check Num tiled conns that are connected (Manasi) v2: * Set the modes on consecutive hotplugged tiles to no mode if tiled mode is pruned (Dave) v1: * Just handle the 1st connector hotplug case * v1 Reviewed-by: Dave Airlie <airlied@redhat.com> Bugzilla: https://gitlab.freedesktop.org/drm/intel/issues/5 Suggested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Suggested-by: Dave Airlie <airlied@redhat.com> Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: Dave Airlie <airlied@redhat.com> Signed-off-by: Manasi Navare <manasi.d.navare@intel.com> Reviewed-by: Dave Airlie <airlied@redhat.com> (v2) Link: https://patchwork.freedesktop.org/patch/msgid/20191211212433.18185-2-manasi.d.navare@intel.com
2019-12-12 04:24:33 +07:00
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;
}
drm/fbdev: Fallback to non tiled mode if all tiles not present In case of tiled displays, if we hotplug just one connector, fbcon currently just selects the preferred mode and if it is tiled mode then that becomes a problem if rest of the tiles are not present. So in the fbdev driver on hotplug when we probe the client modeset, if we dont find all the connectors for all tiles, then on a connector with one tile, just fallback to the first available non tiled mode to display over a single connector. On the hotplug of the consecutive tiled connectors, if the tiled mode no longer exists because of fbcon size limitation, then return no modes for consecutive tiles but retain the non tiled mode on the 0th tile. Use the same logic in case of connected boot case as well. This has been tested with Dell UP328K tiled monitor. v3: * Check Num tiled conns that are connected (Manasi) v2: * Set the modes on consecutive hotplugged tiles to no mode if tiled mode is pruned (Dave) v1: * Just handle the 1st connector hotplug case * v1 Reviewed-by: Dave Airlie <airlied@redhat.com> Bugzilla: https://gitlab.freedesktop.org/drm/intel/issues/5 Suggested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Suggested-by: Dave Airlie <airlied@redhat.com> Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: Dave Airlie <airlied@redhat.com> Signed-off-by: Manasi Navare <manasi.d.navare@intel.com> Reviewed-by: Dave Airlie <airlied@redhat.com> (v2) Link: https://patchwork.freedesktop.org/patch/msgid/20191211212433.18185-2-manasi.d.navare@intel.com
2019-12-12 04:24:33 +07:00
/*
* 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;
drm/fbdev: Fallback to non tiled mode if all tiles not present In case of tiled displays, if we hotplug just one connector, fbcon currently just selects the preferred mode and if it is tiled mode then that becomes a problem if rest of the tiles are not present. So in the fbdev driver on hotplug when we probe the client modeset, if we dont find all the connectors for all tiles, then on a connector with one tile, just fallback to the first available non tiled mode to display over a single connector. On the hotplug of the consecutive tiled connectors, if the tiled mode no longer exists because of fbcon size limitation, then return no modes for consecutive tiles but retain the non tiled mode on the 0th tile. Use the same logic in case of connected boot case as well. This has been tested with Dell UP328K tiled monitor. v3: * Check Num tiled conns that are connected (Manasi) v2: * Set the modes on consecutive hotplugged tiles to no mode if tiled mode is pruned (Dave) v1: * Just handle the 1st connector hotplug case * v1 Reviewed-by: Dave Airlie <airlied@redhat.com> Bugzilla: https://gitlab.freedesktop.org/drm/intel/issues/5 Suggested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Suggested-by: Dave Airlie <airlied@redhat.com> Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: Dave Airlie <airlied@redhat.com> Signed-off-by: Manasi Navare <manasi.d.navare@intel.com> Reviewed-by: Dave Airlie <airlied@redhat.com> (v2) Link: https://patchwork.freedesktop.org/patch/msgid/20191211212433.18185-2-manasi.d.navare@intel.com
2019-12-12 04:24:33 +07:00
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;
drm/fbdev: Fallback to non tiled mode if all tiles not present In case of tiled displays, if we hotplug just one connector, fbcon currently just selects the preferred mode and if it is tiled mode then that becomes a problem if rest of the tiles are not present. So in the fbdev driver on hotplug when we probe the client modeset, if we dont find all the connectors for all tiles, then on a connector with one tile, just fallback to the first available non tiled mode to display over a single connector. On the hotplug of the consecutive tiled connectors, if the tiled mode no longer exists because of fbcon size limitation, then return no modes for consecutive tiles but retain the non tiled mode on the 0th tile. Use the same logic in case of connected boot case as well. This has been tested with Dell UP328K tiled monitor. v3: * Check Num tiled conns that are connected (Manasi) v2: * Set the modes on consecutive hotplugged tiles to no mode if tiled mode is pruned (Dave) v1: * Just handle the 1st connector hotplug case * v1 Reviewed-by: Dave Airlie <airlied@redhat.com> Bugzilla: https://gitlab.freedesktop.org/drm/intel/issues/5 Suggested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Suggested-by: Dave Airlie <airlied@redhat.com> Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: Dave Airlie <airlied@redhat.com> Signed-off-by: Manasi Navare <manasi.d.navare@intel.com> Reviewed-by: Dave Airlie <airlied@redhat.com> (v2) Link: https://patchwork.freedesktop.org/patch/msgid/20191211212433.18185-2-manasi.d.navare@intel.com
2019-12-12 04:24:33 +07:00
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;
}
drm/fbdev: Fallback to non tiled mode if all tiles not present In case of tiled displays, if we hotplug just one connector, fbcon currently just selects the preferred mode and if it is tiled mode then that becomes a problem if rest of the tiles are not present. So in the fbdev driver on hotplug when we probe the client modeset, if we dont find all the connectors for all tiles, then on a connector with one tile, just fallback to the first available non tiled mode to display over a single connector. On the hotplug of the consecutive tiled connectors, if the tiled mode no longer exists because of fbcon size limitation, then return no modes for consecutive tiles but retain the non tiled mode on the 0th tile. Use the same logic in case of connected boot case as well. This has been tested with Dell UP328K tiled monitor. v3: * Check Num tiled conns that are connected (Manasi) v2: * Set the modes on consecutive hotplugged tiles to no mode if tiled mode is pruned (Dave) v1: * Just handle the 1st connector hotplug case * v1 Reviewed-by: Dave Airlie <airlied@redhat.com> Bugzilla: https://gitlab.freedesktop.org/drm/intel/issues/5 Suggested-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Suggested-by: Dave Airlie <airlied@redhat.com> Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Cc: Dave Airlie <airlied@redhat.com> Signed-off-by: Manasi Navare <manasi.d.navare@intel.com> Reviewed-by: Dave Airlie <airlied@redhat.com> (v2) Link: https://patchwork.freedesktop.org/patch/msgid/20191211212433.18185-2-manasi.d.navare@intel.com
2019-12-12 04:24:33 +07:00
/*
* 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;
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, 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);