931 lines
27 KiB
C
931 lines
27 KiB
C
#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 "xf86.h"
|
|
#include "i830.h"
|
|
|
|
#include <math.h>
|
|
|
|
#define rint(x) floor(x)
|
|
|
|
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
|
|
|
#define MARGIN_PERCENT 1.8 /* % of active vertical image */
|
|
#define CELL_GRAN 8.0 /* assumed character cell granularity */
|
|
#define MIN_PORCH 1 /* minimum front porch */
|
|
#define V_SYNC_RQD 3 /* width of vsync in lines */
|
|
#define H_SYNC_PERCENT 8.0 /* width of hsync as % of total line */
|
|
#define MIN_VSYNC_PLUS_BP 550.0 /* min time of vsync + back porch (microsec) */
|
|
#define M 600.0 /* blanking formula gradient */
|
|
#define C 40.0 /* blanking formula offset */
|
|
#define K 128.0 /* blanking formula scaling factor */
|
|
#define J 20.0 /* blanking formula scaling factor */
|
|
|
|
/* C' and M' are part of the Blanking Duty Cycle computation */
|
|
|
|
#define C_PRIME (((C - J) * K/256.0) + J)
|
|
#define M_PRIME (K/256.0 * M)
|
|
/* Established timings from EDID standard */
|
|
static struct
|
|
{
|
|
int hsize;
|
|
int vsize;
|
|
int refresh;
|
|
} est_timings[] = {
|
|
{1280, 1024, 75},
|
|
{1024, 768, 75},
|
|
{1024, 768, 70},
|
|
{1024, 768, 60},
|
|
{1024, 768, 87},
|
|
{832, 624, 75},
|
|
{800, 600, 75},
|
|
{800, 600, 72},
|
|
{800, 600, 60},
|
|
{800, 600, 56},
|
|
{640, 480, 75},
|
|
{640, 480, 72},
|
|
{640, 480, 67},
|
|
{640, 480, 60},
|
|
{720, 400, 88},
|
|
{720, 400, 70},
|
|
};
|
|
|
|
extern const int i830refreshes[];
|
|
|
|
void
|
|
I830PrintModes(ScrnInfoPtr scrp)
|
|
{
|
|
DisplayModePtr p;
|
|
float hsync, refresh = 0;
|
|
char *desc, *desc2, *prefix, *uprefix;
|
|
|
|
if (scrp == NULL)
|
|
return;
|
|
|
|
xf86DrvMsg(scrp->scrnIndex, scrp->virtualFrom, "Virtual size is %dx%d "
|
|
"(pitch %d)\n", scrp->virtualX, scrp->virtualY,
|
|
scrp->displayWidth);
|
|
|
|
p = scrp->modes;
|
|
if (p == NULL)
|
|
return;
|
|
|
|
do {
|
|
desc = desc2 = "";
|
|
if (p->HSync > 0.0)
|
|
hsync = p->HSync;
|
|
else if (p->HTotal > 0)
|
|
hsync = (float)p->Clock / (float)p->HTotal;
|
|
else
|
|
hsync = 0.0;
|
|
if (p->VTotal > 0)
|
|
refresh = hsync * 1000.0 / p->VTotal;
|
|
if (p->Flags & V_INTERLACE) {
|
|
refresh *= 2.0;
|
|
desc = " (I)";
|
|
}
|
|
if (p->Flags & V_DBLSCAN) {
|
|
refresh /= 2.0;
|
|
desc = " (D)";
|
|
}
|
|
if (p->VScan > 1) {
|
|
refresh /= p->VScan;
|
|
desc2 = " (VScan)";
|
|
}
|
|
if (p->VRefresh > 0.0)
|
|
refresh = p->VRefresh;
|
|
if (p->type & M_T_BUILTIN)
|
|
prefix = "Built-in mode";
|
|
else if (p->type & M_T_DEFAULT)
|
|
prefix = "Default mode";
|
|
else
|
|
prefix = "Mode";
|
|
if (p->type & M_T_USERDEF)
|
|
uprefix = "*";
|
|
else
|
|
uprefix = " ";
|
|
if (p->name)
|
|
xf86DrvMsg(scrp->scrnIndex, X_CONFIG,
|
|
"%s%s \"%s\"\n", uprefix, prefix, p->name);
|
|
else
|
|
xf86DrvMsg(scrp->scrnIndex, X_PROBED,
|
|
"%s%s %dx%d (unnamed)\n",
|
|
uprefix, prefix, p->HDisplay, p->VDisplay);
|
|
p = p->next;
|
|
} while (p != NULL && p != scrp->modes);
|
|
}
|
|
|
|
/* This function will sort all modes according to their resolution.
|
|
* Highest resolution first.
|
|
*/
|
|
void
|
|
I830xf86SortModes(DisplayModePtr *new, DisplayModePtr *first,
|
|
DisplayModePtr *last)
|
|
{
|
|
DisplayModePtr p;
|
|
|
|
p = *last;
|
|
while (p) {
|
|
if ((((*new)->HDisplay < p->HDisplay) &&
|
|
((*new)->VDisplay < p->VDisplay)) ||
|
|
(((*new)->HDisplay == p->HDisplay) &&
|
|
((*new)->VDisplay == p->VDisplay) &&
|
|
((*new)->Clock < p->Clock))) {
|
|
|
|
if (p->next) p->next->prev = *new;
|
|
(*new)->prev = p;
|
|
(*new)->next = p->next;
|
|
p->next = *new;
|
|
if (!((*new)->next)) *last = *new;
|
|
break;
|
|
}
|
|
if (!p->prev) {
|
|
(*new)->prev = NULL;
|
|
(*new)->next = p;
|
|
p->prev = *new;
|
|
*first = *new;
|
|
break;
|
|
}
|
|
p = p->prev;
|
|
}
|
|
|
|
if (!*first) {
|
|
*first = *new;
|
|
(*new)->prev = NULL;
|
|
(*new)->next = NULL;
|
|
*last = *new;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the vertical refresh of a mode.
|
|
*
|
|
* Taken directly from xf86Mode.c, and should be put back there --Eric Anholt
|
|
*/
|
|
static double
|
|
I830ModeVRefresh(DisplayModePtr mode)
|
|
{
|
|
double refresh = 0.0;
|
|
|
|
if (mode->VRefresh > 0.0)
|
|
refresh = mode->VRefresh;
|
|
else if (mode->HTotal > 0 && mode->VTotal > 0) {
|
|
refresh = mode->Clock * 1000.0 / mode->HTotal / mode->VTotal;
|
|
if (mode->Flags & V_INTERLACE)
|
|
refresh *= 2.0;
|
|
if (mode->Flags & V_DBLSCAN)
|
|
refresh /= 2.0;
|
|
if (mode->VScan > 1)
|
|
refresh /= (float)(mode->VScan);
|
|
}
|
|
return refresh;
|
|
}
|
|
|
|
DisplayModePtr I830GetDDCModes(ScrnInfoPtr pScrn, xf86MonPtr ddc)
|
|
{
|
|
DisplayModePtr p;
|
|
DisplayModePtr last = NULL;
|
|
DisplayModePtr new = NULL;
|
|
DisplayModePtr first = NULL;
|
|
int count = 0;
|
|
int j, tmp;
|
|
char stmp[32];
|
|
|
|
if (ddc == NULL)
|
|
return NULL;
|
|
|
|
/* Go thru detailed timing table first */
|
|
for (j = 0; j < 4; j++) {
|
|
if (ddc->det_mon[j].type == 0) {
|
|
struct detailed_timings *d_timings =
|
|
&ddc->det_mon[j].section.d_timings;
|
|
|
|
if (d_timings->h_active == 0 || d_timings->v_active == 0) break;
|
|
|
|
new = xnfcalloc(1, sizeof (DisplayModeRec));
|
|
memset(new, 0, sizeof (DisplayModeRec));
|
|
|
|
new->HDisplay = d_timings->h_active;
|
|
new->VDisplay = d_timings->v_active;
|
|
|
|
sprintf(stmp, "%dx%d", new->HDisplay, new->VDisplay);
|
|
new->name = xnfalloc(strlen(stmp) + 1);
|
|
strcpy(new->name, stmp);
|
|
|
|
new->HTotal = new->HDisplay + d_timings->h_blanking;
|
|
new->HSyncStart = new->HDisplay + d_timings->h_sync_off;
|
|
new->HSyncEnd = new->HSyncStart + d_timings->h_sync_width;
|
|
new->VTotal = new->VDisplay + d_timings->v_blanking;
|
|
new->VSyncStart = new->VDisplay + d_timings->v_sync_off;
|
|
new->VSyncEnd = new->VSyncStart + d_timings->v_sync_width;
|
|
new->Clock = d_timings->clock / 1000;
|
|
new->Flags = (d_timings->interlaced ? V_INTERLACE : 0);
|
|
new->status = MODE_OK;
|
|
new->type = M_T_DEFAULT;
|
|
|
|
if (d_timings->sync == 3) {
|
|
switch (d_timings->misc) {
|
|
case 0: new->Flags |= V_NHSYNC | V_NVSYNC; break;
|
|
case 1: new->Flags |= V_PHSYNC | V_NVSYNC; break;
|
|
case 2: new->Flags |= V_NHSYNC | V_PVSYNC; break;
|
|
case 3: new->Flags |= V_PHSYNC | V_PVSYNC; break;
|
|
}
|
|
}
|
|
count++;
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Valid Mode from Detailed timing table: %s (ht %d hss %d hse %d vt %d vss %d vse %d)\n",
|
|
new->name,
|
|
new->HTotal, new->HSyncStart, new->HSyncEnd,
|
|
new->VTotal, new->VSyncStart, new->VSyncEnd);
|
|
|
|
I830xf86SortModes(&new, &first, &last);
|
|
}
|
|
}
|
|
|
|
/* Search thru standard VESA modes from EDID */
|
|
for (j = 0; j < 8; j++) {
|
|
if (ddc->timings2[j].hsize == 0 || ddc->timings2[j].vsize == 0)
|
|
continue;
|
|
for (p = pScrn->monitor->Modes; p && p->next; p = p->next->next) {
|
|
/* Ignore all double scan modes */
|
|
if ((ddc->timings2[j].hsize == p->HDisplay) &&
|
|
(ddc->timings2[j].vsize == p->VDisplay)) {
|
|
float refresh =
|
|
(float)p->Clock * 1000.0 / p->HTotal / p->VTotal;
|
|
float err = (float)ddc->timings2[j].refresh - refresh;
|
|
|
|
if (err < 0) err = -err;
|
|
if (err < 1.0) {
|
|
/* Is this good enough? */
|
|
new = xnfcalloc(1, sizeof (DisplayModeRec));
|
|
memcpy(new, p, sizeof(DisplayModeRec));
|
|
new->name = xnfalloc(strlen(p->name) + 1);
|
|
strcpy(new->name, p->name);
|
|
new->status = MODE_OK;
|
|
new->type = M_T_DEFAULT;
|
|
|
|
count++;
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Valid Mode from standard timing table: %s\n",
|
|
new->name);
|
|
|
|
I830xf86SortModes(&new, &first, &last);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Search thru established modes from EDID */
|
|
tmp = (ddc->timings1.t1 << 8) | ddc->timings1.t2;
|
|
for (j = 0; j < 16; j++) {
|
|
if (tmp & (1 << j)) {
|
|
for (p = pScrn->monitor->Modes; p && p->next; p = p->next->next) {
|
|
if ((est_timings[j].hsize == p->HDisplay) &&
|
|
(est_timings[j].vsize == p->VDisplay)) {
|
|
float refresh =
|
|
(float)p->Clock * 1000.0 / p->HTotal / p->VTotal;
|
|
float err = (float)est_timings[j].refresh - refresh;
|
|
|
|
if (err < 1.0) {
|
|
/* Is this good enough? */
|
|
new = xnfcalloc(1, sizeof (DisplayModeRec));
|
|
memcpy(new, p, sizeof(DisplayModeRec));
|
|
new->name = xnfalloc(strlen(p->name) + 1);
|
|
strcpy(new->name, p->name);
|
|
new->status = MODE_OK;
|
|
new->type = M_T_DEFAULT;
|
|
|
|
count++;
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Valid Mode from established timing "
|
|
"table: %s\n", new->name);
|
|
|
|
I830xf86SortModes(&new, &first, &last);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Total of %d DDC mode(s) found.\n", count);
|
|
|
|
return first;
|
|
}
|
|
|
|
/**
|
|
* This function returns a default mode for flat panels using the timing
|
|
* information provided by the BIOS.
|
|
*/
|
|
static DisplayModePtr
|
|
i830FPNativeMode(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
DisplayModePtr new;
|
|
char stmp[32];
|
|
|
|
if (pI830->PanelXRes == 0 || pI830->PanelYRes == 0)
|
|
return NULL;
|
|
|
|
/* Add native panel size */
|
|
new = xnfcalloc(1, sizeof (DisplayModeRec));
|
|
sprintf(stmp, "%dx%d", pI830->PanelXRes, pI830->PanelYRes);
|
|
new->name = xnfalloc(strlen(stmp) + 1);
|
|
strcpy(new->name, stmp);
|
|
new->HDisplay = pI830->PanelXRes;
|
|
new->VDisplay = pI830->PanelYRes;
|
|
new->HSyncStart = pI830->panel_fixed_hactive + pI830->panel_fixed_hsyncoff;
|
|
new->HSyncEnd = new->HSyncStart + pI830->panel_fixed_hsyncwidth;
|
|
new->HTotal = new->HSyncEnd + 1;
|
|
new->VSyncStart = pI830->panel_fixed_vactive + pI830->panel_fixed_vsyncoff;
|
|
new->VSyncEnd = new->VSyncStart + pI830->panel_fixed_vsyncwidth;
|
|
new->VTotal = new->VSyncEnd + 1;
|
|
new->Clock = pI830->panel_fixed_clock;
|
|
|
|
new->type = M_T_USERDEF;
|
|
|
|
pScrn->virtualX = MAX(pScrn->virtualX, pI830->PanelXRes);
|
|
pScrn->virtualY = MAX(pScrn->virtualY, pI830->PanelYRes);
|
|
pScrn->display->virtualX = pScrn->virtualX;
|
|
pScrn->display->virtualY = pScrn->virtualY;
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"No valid mode specified, force to native mode\n");
|
|
|
|
return new;
|
|
}
|
|
|
|
/**
|
|
* FP automatic modelist creation routine for using panel fitting.
|
|
*
|
|
* Constructs modes for any resolution less than the panel size specified by the
|
|
* user, with the user flag set, plus standard VESA mode sizes without the user
|
|
* flag set (for randr).
|
|
*
|
|
* Modes will be faked to use GTF parameters, even though at the time of being
|
|
* programmed into the LVDS they'll end up being forced to the panel's fixed
|
|
* mode.
|
|
*
|
|
* \return doubly-linked list of modes.
|
|
*/
|
|
DisplayModePtr
|
|
i830GetLVDSModes(ScrnInfoPtr pScrn, char **ppModeName)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
DisplayModePtr last = NULL;
|
|
DisplayModePtr new = NULL;
|
|
DisplayModePtr first = NULL;
|
|
DisplayModePtr p, tmp;
|
|
int count = 0;
|
|
int i, width, height;
|
|
|
|
pScrn->virtualX = pScrn->display->virtualX;
|
|
pScrn->virtualY = pScrn->display->virtualY;
|
|
|
|
/* We have a flat panel connected to the primary display, and we
|
|
* don't have any DDC info.
|
|
*/
|
|
for (i = 0; ppModeName[i] != NULL; i++) {
|
|
|
|
if (sscanf(ppModeName[i], "%dx%d", &width, &height) != 2)
|
|
continue;
|
|
|
|
/* Note: We allow all non-standard modes as long as they do not
|
|
* exceed the native resolution of the panel. Since these modes
|
|
* need the internal RMX unit in the video chips (and there is
|
|
* only one per card), this will only apply to the primary head.
|
|
*/
|
|
if (width < 320 || width > pI830->PanelXRes ||
|
|
height < 200 || height > pI830->PanelYRes) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Mode %s is out of range.\n",
|
|
ppModeName[i]);
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"Valid modes must be between 320x200-%dx%d\n",
|
|
pI830->PanelXRes, pI830->PanelYRes);
|
|
continue;
|
|
}
|
|
|
|
new = i830GetGTF(width, height, 60.0, FALSE, FALSE);
|
|
new->type |= M_T_USERDEF;
|
|
|
|
new->next = NULL;
|
|
new->prev = last;
|
|
|
|
if (last)
|
|
last->next = new;
|
|
last = new;
|
|
if (!first)
|
|
first = new;
|
|
|
|
count++;
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Valid mode using panel fitting: %s\n", new->name);
|
|
}
|
|
|
|
/* If the user hasn't specified modes, add the native mode */
|
|
if (!count) {
|
|
first = last = i830FPNativeMode(pScrn);
|
|
if (first)
|
|
count = 1;
|
|
}
|
|
|
|
/* add in all default vesa modes smaller than panel size, used for randr */
|
|
for (p = pScrn->monitor->Modes; p && p->next; p = p->next->next) {
|
|
if ((p->HDisplay <= pI830->PanelXRes) && (p->VDisplay <= pI830->PanelYRes)) {
|
|
tmp = first;
|
|
while (tmp) {
|
|
if ((p->HDisplay == tmp->HDisplay) && (p->VDisplay == tmp->VDisplay)) break;
|
|
tmp = tmp->next;
|
|
}
|
|
if (!tmp) {
|
|
new = i830GetGTF(p->HDisplay, p->VDisplay, 60.0, FALSE, FALSE);
|
|
if (ppModeName[i] == NULL)
|
|
new->type |= M_T_USERDEF;
|
|
else
|
|
new->type |= M_T_DEFAULT;
|
|
|
|
new->next = NULL;
|
|
new->prev = last;
|
|
|
|
if (last)
|
|
last->next = new;
|
|
last = new;
|
|
if (!first)
|
|
first = new;
|
|
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Total number of valid FP mode(s) found: %d\n", count);
|
|
|
|
return first;
|
|
}
|
|
|
|
/**
|
|
* Allocates and returns a copy of pMode, including pointers within pMode.
|
|
*/
|
|
static DisplayModePtr
|
|
I830DuplicateMode(DisplayModePtr pMode)
|
|
{
|
|
DisplayModePtr pNew;
|
|
|
|
pNew = xnfalloc(sizeof(DisplayModeRec));
|
|
*pNew = *pMode;
|
|
pNew->next = NULL;
|
|
pNew->prev = NULL;
|
|
pNew->name = xnfstrdup(pMode->name);
|
|
|
|
return pNew;
|
|
}
|
|
|
|
/**
|
|
* Injects a list of probed modes into another mode list.
|
|
*
|
|
* Take the doubly-linked list of modes we've probed for the device, and injects
|
|
* it into the doubly-linked modeList. We don't need to filter, because the
|
|
* eventual call to xf86ValidateModes will do this for us. I think.
|
|
*/
|
|
int
|
|
I830InjectProbedModes(ScrnInfoPtr pScrn, DisplayModePtr modeList,
|
|
char **ppModeName, DisplayModePtr addModes)
|
|
{
|
|
DisplayModePtr last = modeList;
|
|
DisplayModePtr first = modeList;
|
|
DisplayModePtr addMode;
|
|
int count = 0;
|
|
|
|
for (addMode = addModes; addMode != NULL; addMode = addMode->next) {
|
|
DisplayModePtr pNew;
|
|
|
|
/* XXX: Do we need to check if modeList already contains the same mode?
|
|
*/
|
|
|
|
pNew = I830DuplicateMode(addMode);
|
|
/* If the user didn't specify any modes, mark all modes as M_T_USERDEF
|
|
* so that we can cycle through them, etc. XXX: really need to?
|
|
*/
|
|
if (ppModeName[0] == NULL) {
|
|
pNew->type |= M_T_USERDEF;
|
|
}
|
|
|
|
/* Insert pNew into modeList */
|
|
if (last) {
|
|
last->next = pNew;
|
|
pNew->prev = last;
|
|
} else {
|
|
first = pNew;
|
|
pNew->prev = NULL;
|
|
}
|
|
pNew->next = NULL;
|
|
last = pNew;
|
|
|
|
count++;
|
|
}
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Injected %d modes detected from the monitor\n", count);
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* I830xf86SetModeCrtc
|
|
*
|
|
* Initialises the Crtc parameters for a mode. The initialisation includes
|
|
* adjustments for interlaced and double scan modes.
|
|
*
|
|
* Taken directly from xf86Mode.c:xf86SetModeCrtc -- Eric Anholt
|
|
* (and it should be put back there!)
|
|
*/
|
|
static void
|
|
I830xf86SetModeCrtc(DisplayModePtr p, int adjustFlags)
|
|
{
|
|
if ((p == NULL) || ((p->type & M_T_CRTC_C) == M_T_BUILTIN))
|
|
return;
|
|
|
|
p->CrtcHDisplay = p->HDisplay;
|
|
p->CrtcHSyncStart = p->HSyncStart;
|
|
p->CrtcHSyncEnd = p->HSyncEnd;
|
|
p->CrtcHTotal = p->HTotal;
|
|
p->CrtcHSkew = p->HSkew;
|
|
p->CrtcVDisplay = p->VDisplay;
|
|
p->CrtcVSyncStart = p->VSyncStart;
|
|
p->CrtcVSyncEnd = p->VSyncEnd;
|
|
p->CrtcVTotal = p->VTotal;
|
|
if (p->Flags & V_INTERLACE) {
|
|
if (adjustFlags & INTERLACE_HALVE_V) {
|
|
p->CrtcVDisplay /= 2;
|
|
p->CrtcVSyncStart /= 2;
|
|
p->CrtcVSyncEnd /= 2;
|
|
p->CrtcVTotal /= 2;
|
|
}
|
|
/* Force interlaced modes to have an odd VTotal */
|
|
/* maybe we should only do this when INTERLACE_HALVE_V is set? */
|
|
p->CrtcVTotal |= 1;
|
|
}
|
|
|
|
if (p->Flags & V_DBLSCAN) {
|
|
p->CrtcVDisplay *= 2;
|
|
p->CrtcVSyncStart *= 2;
|
|
p->CrtcVSyncEnd *= 2;
|
|
p->CrtcVTotal *= 2;
|
|
}
|
|
if (p->VScan > 1) {
|
|
p->CrtcVDisplay *= p->VScan;
|
|
p->CrtcVSyncStart *= p->VScan;
|
|
p->CrtcVSyncEnd *= p->VScan;
|
|
p->CrtcVTotal *= p->VScan;
|
|
}
|
|
p->CrtcHAdjusted = FALSE;
|
|
p->CrtcVAdjusted = FALSE;
|
|
|
|
/*
|
|
* XXX
|
|
*
|
|
* The following is taken from VGA, but applies to other cores as well.
|
|
*/
|
|
p->CrtcVBlankStart = min(p->CrtcVSyncStart, p->CrtcVDisplay);
|
|
p->CrtcVBlankEnd = max(p->CrtcVSyncEnd, p->CrtcVTotal);
|
|
if ((p->CrtcVBlankEnd - p->CrtcVBlankStart) >= 127) {
|
|
/*
|
|
* V Blanking size must be < 127.
|
|
* Moving blank start forward is safer than moving blank end
|
|
* back, since monitors clamp just AFTER the sync pulse (or in
|
|
* the sync pulse), but never before.
|
|
*/
|
|
p->CrtcVBlankStart = p->CrtcVBlankEnd - 127;
|
|
/*
|
|
* If VBlankStart is now > VSyncStart move VBlankStart
|
|
* to VSyncStart using the maximum width that fits into
|
|
* VTotal.
|
|
*/
|
|
if (p->CrtcVBlankStart > p->CrtcVSyncStart) {
|
|
p->CrtcVBlankStart = p->CrtcVSyncStart;
|
|
p->CrtcVBlankEnd = min(p->CrtcHBlankStart + 127, p->CrtcVTotal);
|
|
}
|
|
}
|
|
p->CrtcHBlankStart = min(p->CrtcHSyncStart, p->CrtcHDisplay);
|
|
p->CrtcHBlankEnd = max(p->CrtcHSyncEnd, p->CrtcHTotal);
|
|
|
|
if ((p->CrtcHBlankEnd - p->CrtcHBlankStart) >= 63 * 8) {
|
|
/*
|
|
* H Blanking size must be < 63*8. Same remark as above.
|
|
*/
|
|
p->CrtcHBlankStart = p->CrtcHBlankEnd - 63 * 8;
|
|
if (p->CrtcHBlankStart > p->CrtcHSyncStart) {
|
|
p->CrtcHBlankStart = p->CrtcHSyncStart;
|
|
p->CrtcHBlankEnd = min(p->CrtcHBlankStart + 63 * 8, p->CrtcHTotal);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs probing of modes available on the output connected to the given
|
|
* pipe.
|
|
*
|
|
* We do not support multiple outputs per pipe (since the cases for that are
|
|
* sufficiently rare we can't imagine the complexity being worth it), so
|
|
* the pipe is a sufficient specifier.
|
|
*/
|
|
static void
|
|
I830ReprobePipeModeList(ScrnInfoPtr pScrn, int pipe)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
int output_index = -1;
|
|
int i;
|
|
int outputs;
|
|
DisplayModePtr pMode;
|
|
|
|
while (pI830->pipeModes[pipe] != NULL)
|
|
xf86DeleteMode(&pI830->pipeModes[pipe], pI830->pipeModes[pipe]);
|
|
|
|
if (pipe == 0)
|
|
outputs = pI830->operatingDevices & 0xff;
|
|
else
|
|
outputs = (pI830->operatingDevices >> 8) & 0xff;
|
|
|
|
for (i = 0; i < MAX_OUTPUTS; i++) {
|
|
switch (pI830->output[i].type) {
|
|
case I830_OUTPUT_ANALOG:
|
|
if (outputs & PIPE_CRT) {
|
|
output_index = i;
|
|
}
|
|
break;
|
|
case I830_OUTPUT_LVDS:
|
|
if (outputs & PIPE_LFP) {
|
|
output_index = i;
|
|
}
|
|
break;
|
|
case I830_OUTPUT_SDVO:
|
|
if (outputs & PIPE_DFP) {
|
|
output_index = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* XXX: If there's no output associated with the pipe, bail for now. */
|
|
if (output_index == -1)
|
|
return;
|
|
|
|
if (outputs & PIPE_LFP) {
|
|
pI830->pipeMon[pipe] = NULL; /* XXX */
|
|
pI830->pipeModes[pipe] = i830GetLVDSModes(pScrn,
|
|
pScrn->display->modes);
|
|
} else if (pI830->output[output_index].pDDCBus != NULL) {
|
|
/* XXX: Free the mon */
|
|
pI830->pipeMon[pipe] = xf86DoEDID_DDC2(pScrn->scrnIndex,
|
|
pI830->output[output_index].pDDCBus);
|
|
pI830->pipeModes[pipe] = I830GetDDCModes(pScrn,
|
|
pI830->pipeMon[pipe]);
|
|
|
|
for (pMode = pI830->pipeModes[pipe]; pMode != NULL; pMode = pMode->next)
|
|
{
|
|
I830xf86SetModeCrtc(pMode, INTERLACE_HALVE_V);
|
|
pMode->VRefresh = I830ModeVRefresh(pMode);
|
|
}
|
|
} else {
|
|
ErrorF("don't know how to get modes for this device.\n");
|
|
}
|
|
|
|
/* Set the vertical refresh, which is used by the choose-best-mode-per-pipe
|
|
* code later on.
|
|
*/
|
|
for (pMode = pI830->pipeModes[pipe]; pMode != NULL; pMode = pMode->next) {
|
|
pMode->VRefresh = I830ModeVRefresh(pMode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
ClockRangePtr clockRanges;
|
|
int n, pipe;
|
|
DisplayModePtr saved_mode;
|
|
int saved_virtualX = 0, saved_virtualY = 0, saved_displayWidth = 0;
|
|
|
|
for (pipe = 0; pipe < MAX_DISPLAY_PIPES; pipe++) {
|
|
I830ReprobePipeModeList(pScrn, pipe);
|
|
}
|
|
|
|
/* If we've got a spare pipe, try to detect if a new CRT has been plugged
|
|
* in.
|
|
*/
|
|
if ((pI830->operatingDevices & (PIPE_CRT | (PIPE_CRT << 8))) == 0) {
|
|
if ((pI830->operatingDevices & 0xff) == PIPE_NONE) {
|
|
pI830->operatingDevices |= PIPE_CRT;
|
|
I830ReprobePipeModeList(pScrn, 0);
|
|
if (pI830->pipeModes[0] == NULL) {
|
|
/* No new output found. */
|
|
pI830->operatingDevices &= ~PIPE_CRT;
|
|
} else {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Enabled new CRT on pipe A\n");
|
|
}
|
|
} else if (((pI830->operatingDevices >> 8) & 0xff) == PIPE_NONE) {
|
|
pI830->operatingDevices |= PIPE_CRT << 8;
|
|
I830ReprobePipeModeList(pScrn, 1);
|
|
if (pI830->pipeModes[1] == NULL) {
|
|
/* No new output found. */
|
|
pI830->operatingDevices &= ~(PIPE_CRT << 8);
|
|
} else {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Enabled new CRT on pipe B\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* XXX: Clean out modes previously injected by our driver */
|
|
|
|
if (pI830->pipeModes[0] != NULL) {
|
|
I830InjectProbedModes(pScrn, pScrn->monitor->Modes,
|
|
pScrn->display->modes, pI830->pipeModes[0]);
|
|
}
|
|
if (pI830->pipeModes[1] != NULL) {
|
|
I830InjectProbedModes(pScrn, pScrn->monitor->Modes,
|
|
pScrn->display->modes, pI830->pipeModes[1]);
|
|
}
|
|
|
|
/*
|
|
* Set up the ClockRanges, which describe what clock ranges are available,
|
|
* and what sort of modes they can be used for.
|
|
*/
|
|
clockRanges = xnfcalloc(sizeof(ClockRange), 1);
|
|
clockRanges->next = NULL;
|
|
clockRanges->minClock = 25000;
|
|
clockRanges->maxClock = pI830->MaxClock;
|
|
clockRanges->clockIndex = -1; /* programmable */
|
|
clockRanges->interlaceAllowed = TRUE; /* XXX check this */
|
|
clockRanges->doubleScanAllowed = FALSE; /* XXX check this */
|
|
|
|
/* 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);
|
|
}
|
|
|
|
if (!first_time) {
|
|
saved_virtualX = pScrn->virtualX;
|
|
saved_virtualY = pScrn->virtualY;
|
|
saved_displayWidth = pScrn->displayWidth;
|
|
}
|
|
|
|
/* Take the pScrn->monitor->Modes we've accumulated and validate them into
|
|
* pScrn->modes.
|
|
* XXX: Should set up a scrp->monitor->DDC covering the union of the
|
|
* capabilities of our pipes.
|
|
*/
|
|
n = xf86ValidateModes(pScrn,
|
|
pScrn->monitor->Modes, /* availModes */
|
|
pScrn->display->modes, /* modeNames */
|
|
clockRanges, /* clockRanges */
|
|
!first_time ? &pScrn->displayWidth : NULL, /* linePitches */
|
|
320, /* minPitch */
|
|
MAX_DISPLAY_PITCH, /* maxPitch */
|
|
64 * pScrn->bitsPerPixel, /* pitchInc */
|
|
200, /* minHeight */
|
|
MAX_DISPLAY_HEIGHT, /* maxHeight */
|
|
pScrn->virtualX, /* virtualX */
|
|
pScrn->virtualY, /* virtualY */
|
|
pI830->FbMapSize, /* apertureSize */
|
|
LOOKUP_BEST_REFRESH /* strategy */);
|
|
|
|
if (!first_time) {
|
|
/* Restore things that may have been damaged by xf86ValidateModes. */
|
|
pScrn->virtualX = saved_virtualX;
|
|
pScrn->virtualY = saved_virtualY;
|
|
pScrn->displayWidth = saved_displayWidth;
|
|
}
|
|
|
|
/* Need to do xf86CrtcForModes so any user-configured modes are valid for
|
|
* non-LVDS.
|
|
*/
|
|
xf86SetCrtcForModes(pScrn, INTERLACE_HALVE_V);
|
|
|
|
xf86PruneDriverModes(pScrn);
|
|
|
|
/* Try to find the closest equivalent of the previous mode pointer to switch
|
|
* to.
|
|
*/
|
|
if (saved_mode != NULL) {
|
|
DisplayModePtr pBestMode = NULL, pMode;
|
|
|
|
/* XXX: Is finding a matching x/y res enough? probably not. */
|
|
for (pMode = pScrn->modes; ; pMode = pMode->next) {
|
|
if (pMode->HDisplay == saved_mode->HDisplay &&
|
|
pMode->VDisplay == saved_mode->VDisplay)
|
|
{
|
|
ErrorF("found matching mode %p\n", pMode);
|
|
pBestMode = pMode;
|
|
}
|
|
if (pMode->next == pScrn->modes)
|
|
break;
|
|
}
|
|
|
|
if (pBestMode != NULL)
|
|
xf86SwitchMode(pScrn->pScreen, pBestMode);
|
|
else
|
|
FatalError("No suitable modes after re-probe\n");
|
|
|
|
xfree(saved_mode->name);
|
|
xfree(saved_mode);
|
|
}
|
|
return n;
|
|
}
|