/*
*/
/*
* Copyright (c) 2006-2008, 2013, Intel Corporation
* Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
*
* DRM core CRTC related functions
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting documentation, and
* that the name of the copyright holders not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no representations
* about the suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THIS SOFTWARE.
*
* Authors:
* Keith Packard
* Eric Anholt <eric@anholt.net>
* Dave Airlie <airlied@linux.ie>
* Jesse Barnes <jesse.barnes@intel.com>
*/
#include "drmP.h"
#include "drm_crtc.h"
#include "drm_fourcc.h"
#include "drm_crtc_helper.h"
#include "drm_fb_helper.h"
#include "drm_edid.h"
/**
* drm_helper_move_panel_connectors_to_head() - move panels to the front in the
* connector list
* @dev: drm device to operate on
*
* Some userspace presumes that the first connected connector is the main
* display, where it's supposed to display e.g. the login screen. For
* laptops, this should be the main panel. Use this function to sort all
* painstakingly trying to initialize them in the right order.
*/
{
}
}
static bool drm_kms_helper_poll = true;
int flags)
{
return;
!(flags & DRM_MODE_FLAG_INTERLACE))
!(flags & DRM_MODE_FLAG_DBLSCAN))
}
return;
}
/**
* drm_helper_probe_connector_modes - get complete set of display modes
* @dev: DRM device
* @maxX: max width for modes
* @maxY: max height for modes
*
* LOCKING:
* Caller must hold mode config lock.
*
* Based on @dev's mode_config layout, scan all the connectors and try to detect
* modes on them. Modes will first be added to the connector's probed_modes
* list, then culled (based on validity and the @maxX, @maxY parameters) and
* put into the normal modes list.
*
* Intended to be used either at bootup time or when major configuration
* changes have occurred.
*
* FIXME: take into account monitor limits
*
* RETURNS:
* Number of modes found on @connector.
*/
{
int count = 0;
int mode_flags = 0;
bool verbose_prune = true;
/* set all modes to the unverified state */
else
} else {
}
/* Re-enable polling in case the global poll config changed. */
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] disconnected\n",
verbose_prune = false;
goto prune;
}
if (count == 0)
#endif
if (count == 0)
goto prune;
maxY, 0);
if (connector->interlace_allowed)
if (connector->doublescan_allowed)
mode);
}
return 0;
}
return count;
}
/**
* drm_helper_encoder_in_use - check if a given encoder is in use
* @encoder: encoder to check
*
* LOCKING:
* Caller must hold mode config lock.
*
* Walk @encoders's DRM device's mode_config and see if it's in use.
*
* RETURNS:
* True if @encoder is part of the mode_config, false otherwise.
*/
{
return true;
return false;
}
/**
* drm_helper_crtc_in_use - check if a given CRTC is in a mode_config
* @crtc: CRTC to check
*
* LOCKING:
* Caller must hold mode config lock.
*
* Walk @crtc's DRM device's mode_config and see if it's in use.
*
* RETURNS:
* True if @crtc is part of the mode_config, false otherwise.
*/
{
/* FIXME: Locking around list access? */
return true;
return false;
}
static void
{
if (encoder_funcs->disable)
else
}
/**
* drm_helper_disable_unused_functions - disable unused objects
* @dev: DRM device
*
* LOCKING:
* Caller must hold mode config lock.
*
* If an connector or CRTC isn't part of @dev's mode_config, it can be disabled
* by calling its dpms function, which should power it off.
*/
{
continue;
}
if (!drm_helper_encoder_in_use(encoder)) {
/* disconnector encoder from any connector */
}
}
if (crtc_funcs->disable)
else
}
}
}
/**
* drm_encoder_crtc_ok - can a given crtc drive a given encoder?
* @encoder: encoder to test
* @crtc: crtc to test
*
* Return false if @encoder can't be driven by @crtc, true otherwise.
*/
{
DRM_ERROR("checking null crtc?\n");
break;
crtc_mask <<= 1;
}
return true;
return false;
}
/*
* Check the CRTC we're going to map each output to vs. its current
* CRTC. If they don't match, we have to disable the output and the CRTC
* since the driver will have to re-route things.
*/
static void
{
/* Disable unused encoders */
/* Disable encoders whose CRTC is about to change */
if (encoder_funcs->get_crtc &&
}
}
/**
* drm_crtc_set_mode - set a mode
* @crtc: CRTC to program
* @mode: mode to use
* @x: width of mode
* @y: height of mode
* @old_fb: old framebuffer, for cleanup
*
* LOCKING:
* Caller must hold mode config lock.
*
* Try to set @mode on @crtc. Give @crtc and its associated connectors a chance
* to fixup or reject the mode prior to trying to set it. This is an internal
* helper that drivers could e.g. use to update properties that require the
* entire output pipe to be disabled and re-enabled in a new configuration. For
* example for changing whether audio is enabled on a hdmi link or for changing
* panel fitter or dither attributes. It is also called by the
* drm_crtc_helper_set_config() helper function to drive the mode setting
* sequence.
*
* RETURNS:
* True if the mode was set successfully, or false otherwise.
*/
struct drm_display_mode *mode,
int x, int y,
struct drm_framebuffer *old_fb)
{
bool ret = true;
return true;
if (!adjusted_mode)
return false;
/* Update crtc values up front so the driver can rely on them for mode
* setting.
*/
crtc->x = x;
crtc->y = y;
/* Pass our mode to the connectors and the CRTC to give them a chance to
* adjust it according to limitations or connector properties, and also
* a chance to reject the mode entirely.
*/
continue;
adjusted_mode))) {
DRM_DEBUG_KMS("Encoder fixup failed\n");
goto done;
}
}
DRM_DEBUG_KMS("CRTC fixup failed\n");
goto done;
}
/* Prepare the encoders and CRTCs before setting the mode. */
continue;
/* Disable the encoders as the first thing we do. */
}
/* Set up the DPLL and any encoders state that needs to adjust or depend
* on the DPLL.
*/
if (!ret)
goto done;
continue;
DRM_DEBUG_KMS("[ENCODER:%d:%s] set [MODE:%d:%s]\n",
}
/* Now enable the clocks, plane, pipe, and connectors that we set up. */
continue;
}
/* Store real post-adjustment hardware mode. */
/* Calculate and store various constants which
* are later needed by vblank and swap-completion
* timestamping. They are derived from true hwmode.
*/
/* FIXME: add subpixel order */
done:
if (!ret) {
}
return ret;
}
static int
{
/* Decouple all encoders and their attached connectors from this crtc */
continue;
continue;
}
}
return 0;
}
/**
* drm_crtc_helper_set_config - set a new config from userspace
* @set: mode set configuration
*
* LOCKING:
* Caller must hold mode config lock.
*
* Setup a new configuration, provided by the upper layers (either an ioctl call
* from userspace or internally e.g. from the fbdev suppport code) in @set, and
* enable it. This is the main helper functions for drivers that implement
* kernel mode setting with the crtc helper functions and the assorted
* ->prepare(), ->modeset() and ->commit() helper callbacks.
*
* RETURNS:
* Zero. (FIXME)
*/
{
int ret;
int i;
DRM_DEBUG_KMS("\n");
/* Enforce sane interface api - has been abused by the fb helper. */
DRM_DEBUG_KMS("[CRTC:%d] [FB:%d] #connectors=%d (x y) (%i %i)\n",
} else {
}
/* Allocate space for the backup of all (non-pointer) crtc, encoder and
* connector data. */
sizeof(struct drm_crtc), GFP_KERNEL);
if (!save_crtcs)
return -ENOMEM;
sizeof(struct drm_encoder), GFP_KERNEL);
if (!save_encoders) {
return -ENOMEM;
}
sizeof(struct drm_connector), GFP_KERNEL);
if (!save_connectors) {
return -ENOMEM;
}
/* Copy data. Note that driver private data is not affected.
* Should anything bad happen only the expected state is
* restored, not the drivers personal bookkeeping.
*/
count = 0;
}
count = 0;
}
count = 0;
}
/* We should be able to check here if the fb has the same properties
* and then just flip_or_move it */
/* If we have no fb then treat it as a full mode set */
DRM_DEBUG_KMS("crtc has no fb, full mode set\n");
mode_changed = true;
mode_changed = true;
mode_changed = true;
} else
fb_changed = true;
}
fb_changed = true;
DRM_DEBUG_KMS("modes are different, full mode set\n");
mode_changed = true;
}
/* a) traverse passed in connector list and get encoders for them */
count = 0;
/* if we can't get an encoder for a connector
we are setting now - then fail */
if (new_encoder == NULL)
/* don't break so fail path works correct */
fail = 1;
break;
/*
if (connector->dpms != DRM_MODE_DPMS_ON) {
DRM_DEBUG_KMS("connector dpms not on, full mode switch\n");
mode_changed = true;
}
*/
}
}
DRM_DEBUG_KMS("encoder changed, full mode switch\n");
mode_changed = true;
/* If the encoder is reused for another connector, then
* the appropriate crtc will be set later.
*/
}
}
if (fail) {
goto fail;
}
count = 0;
continue;
else
}
/* Make sure the new CRTC will work with the encoder */
if (new_crtc &&
goto fail;
}
DRM_DEBUG_KMS("crtc changed, full mode switch\n");
mode_changed = true;
}
if (new_crtc) {
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] to [CRTC:%d]\n",
} else {
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] to [NOCRTC]\n",
}
}
/* mode_set_base is not a required function */
mode_changed = true;
if (mode_changed) {
DRM_DEBUG_KMS("attempting to set mode from"
" userspace\n");
old_fb)) {
DRM_ERROR("failed to set mode on [CRTC:%d]\n",
goto fail;
}
DRM_DEBUG_KMS("Setting connector DPMS state to on\n");
for (i = 0; i < set->num_connectors; i++) {
}
}
} else if (fb_changed) {
if (ret != 0) {
goto fail;
}
}
return 0;
fail:
/* Restore all previous data. */
count = 0;
}
count = 0;
}
count = 0;
}
/* Try to restore the config */
if (mode_changed &&
DRM_ERROR("failed to restore config after modeset failure\n");
return ret;
}
{
return dpms;
}
{
return dpms;
}
/**
* drm_helper_connector_dpms
* @connector affected connector
* @mode DPMS mode
*
* This is the main helper function provided by the crtc helper framework for
* implementing the DPMS connector attribute. It computes the new desired DPMS
* state for all encoders and crtcs in the output mesh and calls the ->dpms()
* callback provided by the driver appropriately.
*/
{
int old_dpms;
return;
/* from off to on, do crtc then encoder */
if (crtc) {
if (crtc_funcs->dpms)
}
if (encoder) {
if (encoder_funcs->dpms)
}
}
/* from on to off, do encoder then crtc */
if (encoder) {
if (encoder_funcs->dpms)
}
if (crtc) {
if (crtc_funcs->dpms)
}
}
return;
}
struct drm_mode_fb_cmd2 *mode_cmd)
{
int i;
for (i = 0; i < 4; i++) {
}
&fb->bits_per_pixel);
return 0;
}
{
int ret;
continue;
if (ret == false)
/* Turn off outputs that were already powered off */
if (drm_helper_choose_crtc_dpms(crtc)) {
continue;
if (encoder_funcs->dpms)
}
if (crtc_funcs->dpms)
}
}
/* disable the unused connectors while restoring the modesetting */
}
{
}
static void
{
if (!drm_kms_helper_poll)
return;
/* Ignore forced connectors. */
continue;
/* Ignore HPD capable connectors and connectors where we don't
* want any hotplug detection at all for polling. */
continue;
repoll = true;
/* if we are connected and don't want to poll for disconnect
skip it */
if (old_status == connector_status_connected &&
continue;
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] "
"status updated from %s to %s\n",
changed = true;
}
}
if (changed)
if (repoll)
}
void
{
}
{
return;
}
{
bool poll = false;
return;
poll = true;
}
if (poll)
}
{
(void *)dev);
}
{
}
{
return;
/* kill timer and schedule immediate execution, this doesn't block */
if (drm_kms_helper_poll)
}