xf86-video-intel/src/i830_modes.c

384 lines
11 KiB
C

/* -*- c-basic-offset: 4 -*- */
#define DEBUG_VERB 2
/*
* Copyright © 2002 David Dawes
* Copyright © 2006 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the author(s) shall
* not be used in advertising or otherwise to promote the sale, use or other
* dealings in this Software without prior written authorization from
* the author(s).
*
* Authors: David Dawes <dawes@xfree86.org>
* Eric Anholt <eric.anholt@intel.com>
*
* $XFree86: xc/programs/Xserver/hw/xfree86/os-support/vbe/vbeModes.c,v 1.6 2002/11/02 01:38:25 dawes Exp $
*/
/*
* Modified by Alan Hourihane <alanh@tungstengraphics.com>
* to support extended BIOS modes for the Intel chipsets
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "xf86.h"
#include "xf86DDC.h"
#include "X11/Xatom.h"
#include "i830.h"
#include "i830_display.h"
#include "i830_xf86Modes.h"
#include <randrstr.h>
#define DEBUG_REPROBE 1
static DisplayModePtr
i830GetModeListTail(DisplayModePtr pModeList)
{
DisplayModePtr last;
if (pModeList == NULL)
return NULL;
for (last = pModeList; last->next != NULL; last = last->next)
;
return last;
}
/**
* This function removes a mode from a list of modes. It should probably be
* moved to xf86Mode.c.
*
* There are different types of mode lists:
*
* - singly linked linear lists, ending in NULL
* - doubly linked linear lists, starting and ending in NULL
* - doubly linked circular lists
*
*/
static void
I830xf86DeleteModeFromList(DisplayModePtr *modeList, DisplayModePtr mode)
{
/* Catch the easy/insane cases */
if (modeList == NULL || *modeList == NULL || mode == NULL)
return;
/* If the mode is at the start of the list, move the start of the list */
if (*modeList == mode)
*modeList = mode->next;
/* If mode is the only one on the list, set the list to NULL */
if ((mode == mode->prev) && (mode == mode->next)) {
*modeList = NULL;
} else {
if ((mode->prev != NULL) && (mode->prev->next == mode))
mode->prev->next = mode->next;
if ((mode->next != NULL) && (mode->next->prev == mode))
mode->next->prev = mode->prev;
}
}
void
i830_reprobe_output_modes(ScrnInfoPtr pScrn)
{
I830Ptr pI830 = I830PTR(pScrn);
Bool properties_set = FALSE;
int i;
/* Re-probe the list of modes for each output. */
for (i = 0; i < pI830->xf86_config.num_output; i++)
{
xf86OutputPtr output = pI830->xf86_config.output[i];
DisplayModePtr mode;
while (output->probed_modes != NULL)
xf86DeleteMode(&output->probed_modes, output->probed_modes);
output->probed_modes = (*output->funcs->get_modes) (output);
/* Set the DDC properties to whatever first output has DDC information.
*/
if (output->MonInfo != NULL && !properties_set) {
xf86SetDDCproperties(pScrn, output->MonInfo);
properties_set = TRUE;
}
if (output->probed_modes != NULL)
{
/* silently prune modes down to ones matching the user's
* configuration.
*/
i830xf86ValidateModesUserConfig(pScrn, output->probed_modes);
i830xf86PruneInvalidModes(pScrn, &output->probed_modes, FALSE);
}
#ifdef DEBUG_REPROBE
if (output->probed_modes != NULL) {
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
"Printing probed modes for output %s\n",
output->name);
} else {
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
"No remaining probed modes for output %s\n",
output->name);
}
#endif
for (mode = output->probed_modes; mode != NULL; mode = mode->next)
{
/* The code to choose the best mode per pipe later on will require
* VRefresh to be set.
*/
mode->VRefresh = xf86ModeVRefresh(mode);
xf86SetModeCrtc(mode, INTERLACE_HALVE_V);
#ifdef DEBUG_REPROBE
xf86PrintModeline(pScrn->scrnIndex, mode);
#endif
}
}
}
/**
* Constructs pScrn->modes from the output mode lists.
*
* Currently it only takes one output's mode list and stuffs it into the
* XFree86 DDX mode list while trimming it for root window size.
*
* This should be obsoleted by RandR 1.2 hopefully.
*/
void
i830_set_xf86_modes_from_outputs(ScrnInfoPtr pScrn)
{
I830Ptr pI830 = I830PTR(pScrn);
DisplayModePtr saved_mode, last;
int originalVirtualX, originalVirtualY;
int i;
/* Remove the current mode from the modelist if we're re-validating, so we
* can find a new mode to map ourselves to afterwards.
*/
saved_mode = pI830->currentMode;
if (saved_mode != NULL) {
I830xf86DeleteModeFromList(&pScrn->modes, saved_mode);
}
/* Clear any existing modes from pScrn->modes */
while (pScrn->modes != NULL)
xf86DeleteMode(&pScrn->modes, pScrn->modes);
/* Set pScrn->modes to the mode list for an arbitrary output.
* pScrn->modes should only be used for XF86VidMode now, which we don't
* care about enough to make some sort of unioned list.
*/
for (i = 0; i < pI830->xf86_config.num_output; i++) {
xf86OutputPtr output = pI830->xf86_config.output[i];
if (output->probed_modes != NULL) {
pScrn->modes = xf86DuplicateModes(pScrn, output->probed_modes);
break;
}
}
xf86RandR12GetOriginalVirtualSize(pScrn, &originalVirtualX, &originalVirtualY);
/* Disable modes in the XFree86 DDX list that are larger than the current
* virtual size.
*/
i830xf86ValidateModesSize(pScrn, pScrn->modes,
originalVirtualX, originalVirtualY,
pScrn->displayWidth);
/* Strip out anything that we threw out for virtualX/Y. */
i830xf86PruneInvalidModes(pScrn, &pScrn->modes, TRUE);
if (pScrn->modes == NULL) {
FatalError("No modes left for XFree86 DDX\n");
}
/* For some reason, pScrn->modes is circular, unlike the other mode lists.
* How great is that?
*/
last = i830GetModeListTail(pScrn->modes);
last->next = pScrn->modes;
pScrn->modes->prev = last;
/* Save a pointer to the previous current mode. We can't reset
* pScrn->currentmode, because we rely on xf86SwitchMode's shortcut not
* happening so we can hot-enable devices at SwitchMode. We'll notice this
* case at SwitchMode and free the saved mode.
*/
pI830->savedCurrentMode = saved_mode;
}
/**
* Takes the output mode lists and decides the default root window size
* and framebuffer pitch.
*/
void
i830_set_default_screen_size(ScrnInfoPtr pScrn)
{
I830Ptr pI830 = I830PTR(pScrn);
int maxX = -1, maxY = -1;
int i;
/* Set up a virtual size that will cover any clone mode we'd want to
* set for the currently-connected outputs.
*/
for (i = 0; i < pI830->xf86_config.num_output; i++) {
xf86OutputPtr output = pI830->xf86_config.output[i];
DisplayModePtr mode;
for (mode = output->probed_modes; mode != NULL; mode = mode->next)
{
if (mode->HDisplay > maxX)
maxX = mode->HDisplay;
if (mode->VDisplay > maxY)
maxY = mode->VDisplay;
}
}
/* let the user specify a bigger virtual size if they like */
if (pScrn->display->virtualX > maxX)
maxX = pScrn->display->virtualX;
if (pScrn->display->virtualY > maxY)
maxY = pScrn->display->virtualY;
pScrn->virtualX = maxX;
pScrn->virtualY = maxY;
pScrn->displayWidth = (maxX + 63) & ~63;
}
/**
* Probes for video modes on attached otuputs, and assembles a list to insert
* into pScrn.
*
* \param first_time indicates that the memory layout has already been set up,
* so displayWidth, virtualX, and virtualY shouldn't be touched.
*
* A SetMode must follow this call in order for operatingDevices to match the
* hardware's state, in case we detect a new output device.
*/
int
I830ValidateXF86ModeList(ScrnInfoPtr pScrn, Bool first_time)
{
i830_reprobe_output_modes(pScrn);
if (first_time) {
i830_set_default_screen_size(pScrn);
}
i830_set_xf86_modes_from_outputs(pScrn);
return 1; /* XXX */
}
#ifdef RANDR_12_INTERFACE
#define EDID_ATOM_NAME "EDID_DATA"
static void
i830_ddc_set_edid_property(xf86OutputPtr output, void *data, int data_len)
{
Atom edid_atom = MakeAtom(EDID_ATOM_NAME, sizeof(EDID_ATOM_NAME), TRUE);
/* This may get called before the RandR resources have been created */
if (output->randr_output == NULL)
return;
if (data_len != 0) {
RRChangeOutputProperty(output->randr_output, edid_atom, XA_INTEGER, 8,
PropModeReplace, data_len, data, FALSE);
} else {
RRDeleteOutputProperty(output->randr_output, edid_atom);
}
}
#endif
/**
* Generic get_modes function using DDC, used by many outputs.
*/
DisplayModePtr
i830_ddc_get_modes(xf86OutputPtr output)
{
ScrnInfoPtr pScrn = output->scrn;
I830OutputPrivatePtr intel_output = output->driver_private;
xf86MonPtr ddc_mon;
DisplayModePtr ddc_modes, mode;
int i;
ddc_mon = xf86DoEDID_DDC2(pScrn->scrnIndex, intel_output->pDDCBus);
if (ddc_mon == NULL) {
#ifdef RANDR_12_INTERFACE
i830_ddc_set_edid_property(output, NULL, 0);
#endif
return NULL;
}
if (output->MonInfo != NULL)
xfree(output->MonInfo);
output->MonInfo = ddc_mon;
#ifdef RANDR_12_INTERFACE
if (output->MonInfo->ver.version == 1) {
i830_ddc_set_edid_property(output, ddc_mon->rawData, 128);
} else if (output->MonInfo->ver.version == 2) {
i830_ddc_set_edid_property(output, ddc_mon->rawData, 256);
} else {
i830_ddc_set_edid_property(output, NULL, 0);
}
#endif
/* Debug info for now, at least */
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "EDID for output %s\n", output->name);
xf86PrintEDID(output->MonInfo);
ddc_modes = xf86DDCGetModes(pScrn->scrnIndex, ddc_mon);
/* Strip out any modes that can't be supported on this output. */
for (mode = ddc_modes; mode != NULL; mode = mode->next) {
int status = (*output->funcs->mode_valid)(output, mode);
if (status != MODE_OK)
mode->status = status;
}
i830xf86PruneInvalidModes(pScrn, &ddc_modes, TRUE);
/* Pull out a phyiscal size from a detailed timing if available. */
for (i = 0; i < 4; i++) {
if (ddc_mon->det_mon[i].type == DT &&
ddc_mon->det_mon[i].section.d_timings.h_size != 0 &&
ddc_mon->det_mon[i].section.d_timings.v_size != 0)
{
output->mm_width = ddc_mon->det_mon[i].section.d_timings.h_size;
output->mm_height = ddc_mon->det_mon[i].section.d_timings.v_size;
break;
}
}
return ddc_modes;
}