/*
 * Copyright 2004-2008  Luc Verhaegen <lverhaegen@novell.com>
 * Copyright 2007, 2008 Matthias Hopf <mhopf@novell.com>
 * Copyright 2007, 2008 Egbert Eich   <eich@novell.com>
 * Copyright 2007, 2008 Advanced Micro Devices, Inc.
 *
 * 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 COPYRIGHT HOLDER(S) OR 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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "xf86.h"
#if HAVE_XF86_ANSIC_H
# include "xf86_ansic.h"
#else
# include <stdio.h>
# include <string.h>
#endif


#include "rhd.h"

#include "edid.h"
#include "xf86DDC.h"

#include "rhd_crtc.h"
#include "rhd_pll.h"
#include "rhd_connector.h"
#include "rhd_output.h"
#include "rhd_modes.h"
#include "rhd_monitor.h"

/* For Acceleration FB validation */
//#include "r5xx_accel.h"

/*
 * Define a set of own mode errors.
 */
#define RHD_MODE_STATUS 0x51B00
#ifndef MONREC_HAS_REDUCED
#define MODE_NO_REDUCED     0x01 + RHD_MODE_STATUS
#endif
#define MODE_MEM_BW         0x02 + RHD_MODE_STATUS
#define MODE_OUTPUT_UNDEF   0x03 + RHD_MODE_STATUS
#define MODE_NOT_PAL        0x04 + RHD_MODE_STATUS
#define MODE_NOT_NTSC       0x05 + RHD_MODE_STATUS
#define MODE_HTOTAL_WIDE    0x06 + RHD_MODE_STATUS
#define MODE_HDISPLAY_WIDE  0x07 + RHD_MODE_STATUS
#define MODE_HSYNC_RANGE    0x08 + RHD_MODE_STATUS
#define MODE_HBLANK_RANGE   0x09 + RHD_MODE_STATUS
#define MODE_VTOTAL_WIDE    0x0A + RHD_MODE_STATUS
#define MODE_VDISPLAY_WIDE  0x0B + RHD_MODE_STATUS
#define MODE_VSYNC_RANGE    0x0C + RHD_MODE_STATUS
#define MODE_VBLANK_RANGE   0x0D + RHD_MODE_STATUS
#define MODE_PITCH          0x0E + RHD_MODE_STATUS
#define MODE_OFFSET         0x0F + RHD_MODE_STATUS
#define MODE_MINHEIGHT      0x10 + RHD_MODE_STATUS
#define MODE_FIXED          0x11 + RHD_MODE_STATUS
#define MODE_SCALE          0x12 + RHD_MODE_STATUS

/*
 * Don't bother with checking whether X offers this. Just use the internal one
 * I'm the author of the X side one anyway.
 */

/*
 * Generate a CVT standard mode from HDisplay, VDisplay and VRefresh.
 *
 * These calculations are stolen from the CVT calculation spreadsheet written
 * by Graham Loveridge. He seems to be claiming no copyright and there seems to
 * be no license attached to this. He apparently just wants to see his name
 * mentioned.
 *
 * This file can be found at http://www.vesa.org/Public/CVT/CVTd6r1.xls
 *
 * Comments and structure corresponds to the comments and structure of the xls.
 * This should ease importing of future changes to the standard (not very
 * likely though).
 *
 * About margins; i'm sure that they are to be the bit between HDisplay and
 * HBlankStart, HBlankEnd and HTotal, VDisplay and VBlankStart, VBlankEnd and
 * VTotal, where the overscan colour is shown. FB seems to call _all_ blanking
 * outside sync "margin" for some reason. Since we prefer seeing proper
 * blanking instead of the overscan colour, and since the Crtc* values will
 * probably get altered after us, we will disable margins altogether. With
 * these calculations, Margins will plainly expand H/VDisplay, and we don't
 * want that. -- libv
 *
 */
DisplayModePtr
RHDCVTMode(int HDisplay, int VDisplay, float VRefresh, Bool Reduced,
           Bool Interlaced)
{
    DisplayModeRec  *Mode = xnfalloc(sizeof(DisplayModeRec));

    /* 1) top/bottom margin size (% of height) - default: 1.8 */
#define CVT_MARGIN_PERCENTAGE 1.8

    /* 2) character cell horizontal granularity (pixels) - default 8 */
#define CVT_H_GRANULARITY 1

    /* 4) Minimum vertical porch (lines) - default 3 */
#define CVT_MIN_V_PORCH 3

    /* 4) Minimum number of vertical back porch lines - default 6 */
#define CVT_MIN_V_BPORCH 6

    /* Pixel Clock step (kHz) */
#define CVT_CLOCK_STEP 250

    Bool Margins = FALSE;
    float  VFieldRate, HPeriod;
    int  HDisplayRnd, HMargin;
    int  VDisplayRnd, VMargin, VSync;
    float  Interlace; /* Please rename this */

    memset(Mode, 0, sizeof(DisplayModeRec));

    /* CVT default is 60.0Hz */
    if (!VRefresh)
        VRefresh = 60.0;

    /* 1. Required field rate */
    if (Interlaced)
        VFieldRate = VRefresh * 2;
    else
        VFieldRate = VRefresh;

    /* 2. Horizontal pixels */
    HDisplayRnd = HDisplay - (HDisplay % CVT_H_GRANULARITY);

    /* 3. Determine left and right borders */
    if (Margins) {
        /* right margin is actually exactly the same as left */
        HMargin = (((float) HDisplayRnd) * CVT_MARGIN_PERCENTAGE / 100.0);
        HMargin -= HMargin % CVT_H_GRANULARITY;
    } else
        HMargin = 0;

    /* 4. Find total active pixels */
    Mode->HDisplay = HDisplayRnd + 2*HMargin;

    /* 5. Find number of lines per field */
    if (Interlaced)
        VDisplayRnd = VDisplay / 2;
    else
        VDisplayRnd = VDisplay;

    /* 6. Find top and bottom margins */
    /* nope. */
    if (Margins)
        /* top and bottom margins are equal again. */
        VMargin = (((float) VDisplayRnd) * CVT_MARGIN_PERCENTAGE / 100.0);
    else
        VMargin = 0;

    Mode->VDisplay = VDisplay + 2*VMargin;

    /* 7. Interlace */
    if (Interlaced)
        Interlace = 0.5;
    else
        Interlace = 0.0;

    /* Determine VSync Width from aspect ratio */
    if (!(VDisplay % 3) && ((VDisplay * 4 / 3) == HDisplay))
        VSync = 4;
    else if (!(VDisplay % 9) && ((VDisplay * 16 / 9) == HDisplay))
        VSync = 5;
    else if (!(VDisplay % 10) && ((VDisplay * 16 / 10) == HDisplay))
        VSync = 6;
    else if (!(VDisplay % 4) && ((VDisplay * 5 / 4) == HDisplay))
        VSync = 7;
    else if (!(VDisplay % 9) && ((VDisplay * 15 / 9) == HDisplay))
        VSync = 7;
    else /* Custom */
        VSync = 10;

    if (!Reduced) { /* simplified GTF calculation */

        /* 4) Minimum time of vertical sync + back porch interval (µs)
         * default 550.0 */
#define CVT_MIN_VSYNC_BP 550.0

        /* 3) Nominal HSync width (% of line period) - default 8 */
#define CVT_HSYNC_PERCENTAGE 8

        float  HBlankPercentage;
        int  VSyncAndBackPorch, VBackPorch;
        int  HBlank;

        /* 8. Estimated Horizontal period */
        HPeriod = ((float) (1000000.0 / VFieldRate - CVT_MIN_VSYNC_BP)) /
            (VDisplayRnd + 2 * VMargin + CVT_MIN_V_PORCH + Interlace);

        /* 9. Find number of lines in sync + backporch */
        if (((int)(CVT_MIN_VSYNC_BP / HPeriod) + 1) < (VSync + CVT_MIN_V_PORCH))
            VSyncAndBackPorch = VSync + CVT_MIN_V_PORCH;
        else
            VSyncAndBackPorch = (int)(CVT_MIN_VSYNC_BP / HPeriod) + 1;

        /* 10. Find number of lines in back porch */
        VBackPorch = VSyncAndBackPorch - VSync;

        /* 11. Find total number of lines in vertical field */
        Mode->VTotal = VDisplayRnd + 2 * VMargin + VSyncAndBackPorch + Interlace
            + CVT_MIN_V_PORCH;

        /* 5) Definition of Horizontal blanking time limitation */
        /* Gradient (%/kHz) - default 600 */
#define CVT_M_FACTOR 600

        /* Offset (%) - default 40 */
#define CVT_C_FACTOR 40

        /* Blanking time scaling factor - default 128 */
#define CVT_K_FACTOR 128

        /* Scaling factor weighting - default 20 */
#define CVT_J_FACTOR 20

#define CVT_M_PRIME CVT_M_FACTOR * CVT_K_FACTOR / 256
#define CVT_C_PRIME (CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256 + \
        CVT_J_FACTOR

        /* 12. Find ideal blanking duty cycle from formula */
        HBlankPercentage = CVT_C_PRIME - CVT_M_PRIME * HPeriod/1000.0;

        /* 13. Blanking time */
        if (HBlankPercentage < 20)
            HBlankPercentage = 20;

        HBlank = Mode->HDisplay * HBlankPercentage/(100.0 - HBlankPercentage);
        HBlank -= HBlank % (2*CVT_H_GRANULARITY);

        /* 14. Find total number of pixels in a line. */
        Mode->HTotal = Mode->HDisplay + HBlank;

        /* Fill in HSync values */
        Mode->HSyncEnd = Mode->HDisplay + HBlank / 2;

        Mode->HSyncStart = Mode->HSyncEnd -
            (Mode->HTotal * CVT_HSYNC_PERCENTAGE) / 100;
        Mode->HSyncStart += CVT_H_GRANULARITY -
            Mode->HSyncStart % CVT_H_GRANULARITY;

        /* Fill in VSync values */
        Mode->VSyncStart = Mode->VDisplay + CVT_MIN_V_PORCH;
        Mode->VSyncEnd = Mode->VSyncStart + VSync;

    } else { /* Reduced blanking */
        /* Minimum vertical blanking interval time (µs) - default 460 */
#define CVT_RB_MIN_VBLANK 460.0

        /* Fixed number of clocks for horizontal sync */
#define CVT_RB_H_SYNC 32.0

        /* Fixed number of clocks for horizontal blanking */
#define CVT_RB_H_BLANK 160.0

        /* Fixed number of lines for vertical front porch - default 3 */
#define CVT_RB_VFPORCH 3

        int  VBILines;

        /* 8. Estimate Horizontal period. */
        HPeriod = ((float) (1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) /
            (VDisplayRnd + 2*VMargin);

        /* 9. Find number of lines in vertical blanking */
        VBILines = ((float) CVT_RB_MIN_VBLANK) / HPeriod + 1;

        /* 10. Check if vertical blanking is sufficient */
        if (VBILines < (CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH))
            VBILines = CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH;

        /* 11. Find total number of lines in vertical field */
        Mode->VTotal = VDisplayRnd + 2 * VMargin + Interlace + VBILines;

        /* 12. Find total number of pixels in a line */
        Mode->HTotal = Mode->HDisplay + CVT_RB_H_BLANK;

        /* Fill in HSync values */
        Mode->HSyncEnd = Mode->HDisplay + CVT_RB_H_BLANK / 2;
        Mode->HSyncStart = Mode->HSyncEnd - CVT_RB_H_SYNC;

        /* Fill in VSync values */
        Mode->VSyncStart = Mode->VDisplay + CVT_RB_VFPORCH;
        Mode->VSyncEnd = Mode->VSyncStart + VSync;
    }

    /* 15/13. Find pixel clock frequency (kHz for xf86) */
    Mode->Clock = Mode->HTotal * 1000.0 / HPeriod;
    Mode->Clock -= Mode->Clock % CVT_CLOCK_STEP;

    /* 16/14. Find actual Horizontal Frequency (kHz) */
    Mode->HSync = ((float) Mode->Clock) / ((float) Mode->HTotal);

    /* 17/15. Find actual Field rate */
    Mode->VRefresh = (1000.0 * ((float) Mode->Clock)) /
        ((float) (Mode->HTotal * Mode->VTotal));

    /* 18/16. Find actual vertical frame frequency */
    /* ignore - just set the mode flag for interlaced */
    if (Interlaced)
        Mode->VTotal *= 2;

    {
        char  Name[256];
        Name[0] = 0;

        snprintf(Name, 256, "%dx%d", HDisplay, VDisplay);
        Mode->name = strdup(Name);
    }

    if (Reduced)
        Mode->Flags |= V_PHSYNC | V_NVSYNC;
    else
        Mode->Flags |= V_NHSYNC | V_PVSYNC;

    if (Interlaced)
        Mode->Flags |= V_INTERLACE;

    return Mode;
}

/*
 * Temporary.
 */
static void
add(char **p, char *new)
{
//  char *tmp = kmalloc(strlen(*p) + strlen(new) + 2);

  *p = (char*)realloc(*p, strlen(*p) + strlen(new) + 2);
  strcat(*p, " ");
  strcat(*p, new);
}

/*
 *
 */
Bool
rhdModesEqual(DisplayModePtr mode1, DisplayModePtr mode2)
{
    if (mode1->Clock == mode2->Clock
	&& mode1->HDisplay == mode2->HDisplay
	&& mode1->HSyncStart == mode2->HSyncStart
	&& mode1->HSyncEnd == mode2->HSyncEnd
	&& mode1->HTotal == mode2->HTotal
	&& mode1->HSkew == mode2->HSkew
	&& mode1->VDisplay == mode2->VDisplay
	&& mode1->VSyncStart == mode2->VSyncStart
	&& mode1->VSyncEnd == mode2->VSyncEnd
	&& mode1->VTotal == mode2->VTotal
	&& mode1->VScan == mode2->VScan
	&& mode1->Flags == mode2->Flags)
	return TRUE;

    return FALSE;
}

/*
 *
 */
void
RHDPrintModeline(DisplayModePtr mode)
{
    char tmp[256];
    char *flags = xnfcalloc(1, 1);

    if (mode->HSkew) {
	snprintf(tmp, 256, "hskew %i", mode->HSkew);
	add(&flags, tmp);
    }
    if (mode->VScan) {
	snprintf(tmp, 256, "vscan %i", mode->VScan);
	add(&flags, tmp);
    }
    if (mode->Flags & V_INTERLACE) add(&flags, "interlace");
    if (mode->Flags & V_CSYNC) add(&flags, "composite");
    if (mode->Flags & V_DBLSCAN) add(&flags, "doublescan");
    if (mode->Flags & V_BCAST) add(&flags, "bcast");
    if (mode->Flags & V_PHSYNC) add(&flags, "+hsync");
    if (mode->Flags & V_NHSYNC) add(&flags, "-hsync");
    if (mode->Flags & V_PVSYNC) add(&flags, "+vsync");
    if (mode->Flags & V_NVSYNC) add(&flags, "-vsync");
    if (mode->Flags & V_PCSYNC) add(&flags, "+csync");
    if (mode->Flags & V_NCSYNC) add(&flags, "-csync");
#if 0
    if (mode->Flags & V_CLKDIV2) add(&flags, "vclk/2");
#endif
    xf86Msg(X_NONE, "Modeline \"%s\"  %6.2f  %i %i %i %i  %i %i %i %i%s\n",
	    mode->name, mode->Clock/1000.,
	    mode->HDisplay, mode->HSyncStart, mode->HSyncEnd, mode->HTotal,
	    mode->VDisplay, mode->VSyncStart, mode->VSyncEnd, mode->VTotal,
	    flags);
    xfree(flags);
}

/*
 * xf86Mode.c should have a some more DisplayModePtr list handling.
 */
DisplayModePtr
RHDModesAdd(DisplayModePtr Modes, DisplayModePtr Additions)
{
    if (!Modes) {
        if (Additions)
            return Additions;
        else
            return NULL;
    }

    if (Additions) {
        DisplayModePtr Mode = Modes;

        while (Mode->next)
            Mode = Mode->next;

        Mode->next = Additions;
        Additions->prev = Mode;
    }

    return Modes;
}

/*
 *
 */
static DisplayModePtr
rhdModeDelete(DisplayModePtr Modes, DisplayModePtr Delete)
{
    DisplayModePtr Next, Previous;

    if (!Delete)
	return Modes;

    if (Modes == Delete)
	Modes = NULL;

    if (Delete->next == Delete)
	Delete->next = NULL;

    if (Delete->prev == Delete)
	Delete->next = NULL;

    Next = Delete->next;
    Previous = Delete->prev;

    if (Next)
	Next->prev = Previous;

    if (Previous)
	Previous->next = Next;

    xfree(Delete->name);
    xfree(Delete);

    if (Modes)
	return Modes;

    if (Next)
	return Next;

    if (Previous)
	while (Previous->prev)
	    Previous = Previous->prev;

    return Previous;
}

/*
 *
 */
DisplayModePtr
RHDModeCopy(DisplayModePtr Mode)
{
    DisplayModePtr New;

    if (!Mode)
        return NULL;

    New = xnfalloc(sizeof(DisplayModeRec));
    memcpy(New, Mode, sizeof(DisplayModeRec)); /* re-use private */
    New->name = strdup(Mode->name);
    New->prev = NULL;
    New->next = NULL;
    New->Private = Mode->Private;
    New->PrivSize = Mode->PrivSize;

    return New;
}

/*
 *
 */
static void
rhdModesDestroy(DisplayModePtr Modes)
{
    DisplayModePtr mode = Modes, next;

    while (mode) {
        next = mode->next;
        xfree(mode->name);
        xfree(mode);
        mode = next;
    }
}

/*
 * Basic sanity checks.
 */
static int
rhdModeSanity(RHDPtr rhdPtr, DisplayModePtr Mode)
{
    /* do we need to bother at all? */
    if (Mode->status != MODE_OK)
        return Mode->status;

    if (!Mode->name) {
	xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR,
		   "Validation found mode without name.\n");
        return MODE_ERROR;
    }

    if (Mode->Clock <= 0)
        return MODE_NOCLOCK;

    if ((Mode->HDisplay <= 0) || (Mode->HSyncStart <= 0) ||
        (Mode->HSyncEnd <= 0) || (Mode->HTotal <= 0))
        return MODE_H_ILLEGAL;

    if ((Mode->HTotal <= Mode->HSyncEnd) ||
        (Mode->HSyncEnd <= Mode->HSyncStart) ||
        (Mode->HSyncStart < Mode->HDisplay))
        return MODE_H_ILLEGAL;

    /* HSkew? */

    if ((Mode->VDisplay <= 0) || (Mode->VSyncStart <= 0) ||
        (Mode->VSyncEnd <= 0) || (Mode->VTotal <= 0))
        return MODE_V_ILLEGAL;

    if ((Mode->VTotal <= Mode->VSyncEnd) ||
        (Mode->VSyncEnd <= Mode->VSyncStart) ||
        (Mode->VSyncStart < Mode->VDisplay))
        return MODE_V_ILLEGAL;

    if ((Mode->VScan != 0) && (Mode->VScan != 1))
        return MODE_NO_VSCAN;

    if (Mode->Flags & V_DBLSCAN)
        return MODE_NO_DBLESCAN;

    /* Flags */
    return MODE_OK;
}

/*
 * After we passed the initial sanity check, we need to fill out the CRTC
 * values.
 */
static void
rhdModeFillOutCrtcValues(DisplayModePtr Mode)
{
    /* do we need to bother at all? */
    if (Mode->status != MODE_OK)
        return;

    Mode->ClockIndex = -1; /* Always! direct non-programmable support must die. */

    if (!Mode->SynthClock)
        Mode->SynthClock = Mode->Clock;

    if (!Mode->CrtcHDisplay)
        Mode->CrtcHDisplay = Mode->HDisplay;

    if (!Mode->CrtcHBlankStart)
        Mode->CrtcHBlankStart = Mode->HDisplay;

    if (!Mode->CrtcHSyncStart)
        Mode->CrtcHSyncStart = Mode->HSyncStart;

    if (!Mode->CrtcHSyncEnd)
        Mode->CrtcHSyncEnd = Mode->HSyncEnd;

    if (!Mode->CrtcHBlankEnd)
        Mode->CrtcHBlankEnd = Mode->HTotal;

    if (!Mode->CrtcHTotal)
        Mode->CrtcHTotal = Mode->HTotal;

    if (!Mode->CrtcHSkew)
        Mode->CrtcHSkew = Mode->HSkew;

    if (!Mode->CrtcVDisplay)
        Mode->CrtcVDisplay = Mode->VDisplay;

    if (!Mode->CrtcVBlankStart)
        Mode->CrtcVBlankStart = Mode->VDisplay;

    if (!Mode->CrtcVSyncStart)
        Mode->CrtcVSyncStart = Mode->VSyncStart;

    if (!Mode->CrtcVSyncEnd)
        Mode->CrtcVSyncEnd = Mode->VSyncEnd;

    if (!Mode->CrtcVBlankEnd)
        Mode->CrtcVBlankEnd = Mode->VTotal;

    if (!Mode->CrtcVTotal)
        Mode->CrtcVTotal = Mode->VTotal;

    /* Always change these */
    Mode->HSync = ((float) Mode->SynthClock) / Mode->CrtcHTotal;
    Mode->VRefresh = (Mode->SynthClock * 1000.0) /
        (Mode->CrtcHTotal * Mode->CrtcVTotal);
    if (Mode->Flags & V_INTERLACE)
	Mode->VRefresh *= 2.0;
    if (Mode->Flags & V_DBLSCAN)
	Mode->VRefresh /= 2.0;

    /* We're usually first in the chain, right after rhdModeSanity. */
    Mode->CrtcHAdjusted = FALSE;
    Mode->CrtcVAdjusted = FALSE;

    /* Steer clear of PrivSize, Private and PrivFlags */
}

/*
 * Basic sanity checks.
 */
static int
rhdModeCrtcSanity(DisplayModePtr Mode)
{
    if (Mode->SynthClock <= 0)
        return MODE_NOCLOCK;

    if ((Mode->CrtcHDisplay <= 0) || (Mode->CrtcHBlankStart <= 0) ||
        (Mode->CrtcHSyncStart <= 0) || (Mode->CrtcHSyncEnd <= 0) ||
        (Mode->CrtcHBlankEnd <= 0) || (Mode->CrtcHTotal <= 0))
        return MODE_H_ILLEGAL;

    /* there seem to be no alignment constraints on horizontal timing on our
       hardware here */

    if ((Mode->CrtcHTotal < Mode->CrtcHBlankEnd) ||
        (Mode->CrtcHBlankEnd <= Mode->CrtcHSyncEnd) ||
        (Mode->CrtcHSyncEnd <= Mode->CrtcHSyncStart) ||
        (Mode->CrtcHSyncStart < Mode->CrtcHBlankStart) ||
        (Mode->CrtcHBlankStart < Mode->CrtcHDisplay))
        return MODE_H_ILLEGAL;

    /* CrtcHSkew? */

    if ((Mode->CrtcVDisplay <= 0) || (Mode->CrtcVBlankStart <= 0) ||
        (Mode->CrtcVSyncStart <= 0) || (Mode->CrtcVSyncEnd <= 0) ||
        (Mode->CrtcVBlankEnd <= 0) || (Mode->CrtcVTotal <= 0))
        return MODE_V_ILLEGAL;

    if ((Mode->CrtcVTotal < Mode->CrtcVBlankEnd) ||
        (Mode->CrtcVBlankEnd <= Mode->CrtcVSyncEnd) ||
        (Mode->CrtcVSyncEnd <= Mode->CrtcVSyncStart) ||
        (Mode->CrtcVSyncStart < Mode->CrtcVBlankStart) ||
        (Mode->CrtcVBlankStart < Mode->CrtcVDisplay))
        return MODE_V_ILLEGAL;

    return MODE_OK;
}

/*
 *
 */
static Bool
rhdMonitorFixedValid(struct rhdMonitor *Monitor, DisplayModePtr Mode)
{
    DisplayModePtr Fixed;

    for (Fixed = Monitor->Modes; Fixed; Fixed = Fixed->next) {
	if ((Mode->Flags != Fixed->Flags) ||
	    (Mode->Clock != Fixed->Clock) ||
	    (Mode->SynthClock != Fixed->Clock))
	    continue;

	if ((Mode->HDisplay > Fixed->HDisplay) ||
	    (Mode->VDisplay > Fixed->VDisplay))
	    continue;

	if ((Mode->HSyncStart != Fixed->HSyncStart) ||
	    (Mode->HSyncEnd != Fixed->HSyncEnd))
	    continue;

	if ((Mode->VSyncStart != Fixed->VSyncStart) ||
	    (Mode->VSyncEnd != Fixed->VSyncEnd))
	    continue;

	if ((Mode->CrtcHDisplay > Fixed->HDisplay) ||
	    (Mode->CrtcVDisplay > Fixed->VDisplay))
	    continue;

	if ((Mode->CrtcHBlankStart != Fixed->HDisplay) ||
	    (Mode->CrtcHSyncStart != Fixed->HSyncStart) ||
	    (Mode->CrtcHSyncEnd != Fixed->HSyncEnd) ||
	    (Mode->CrtcHBlankEnd != Fixed->HTotal))
	    continue;

	if ((Mode->CrtcVBlankStart != Fixed->VDisplay) ||
	    (Mode->CrtcVSyncStart != Fixed->VSyncStart) ||
	    (Mode->CrtcVSyncEnd != Fixed->VSyncEnd) ||
	    (Mode->CrtcVBlankEnd != Fixed->VTotal))
	    continue;

	return TRUE;
    }

    return FALSE;
}
/*
 * TODO: review fixed modes when doing different modes on both crtcs.
 */
static int
rhdMonitorValid(struct rhdMonitor *Monitor, DisplayModePtr Mode)
{
    int i;
    Bool isNative = FALSE;

    if (Monitor->NativeMode && rhdModesEqual(Mode, Monitor->NativeMode))
	isNative = TRUE;

    for (i = 0; i < Monitor->numHSync; i++)
        if ((Mode->HSync >= (Monitor->HSync[i].lo * (1.0 - SYNC_TOLERANCE))) &&
            (Mode->HSync <= (Monitor->HSync[i].hi * (1.0 + SYNC_TOLERANCE))))
            break;
    if (Monitor->numHSync && (i == Monitor->numHSync))
        return MODE_HSYNC;

    for (i = 0; i < Monitor->numVRefresh; i++)
        if ((Mode->VRefresh >= (Monitor->VRefresh[i].lo * (1.0 - SYNC_TOLERANCE))) &&
            (Mode->VRefresh <= (Monitor->VRefresh[i].hi * (1.0 + SYNC_TOLERANCE))))
            break;
    if (Monitor->numVRefresh && (i == Monitor->numVRefresh))
        return MODE_VSYNC;

    if (Monitor->Bandwidth &&
	(Mode->SynthClock > (Monitor->Bandwidth * (1 + SYNC_TOLERANCE))))
        return MODE_CLOCK_HIGH;

    if (isNative) { /* if it's this monitor's native mode be less strict on validation */
    if (Monitor->ReducedAllowed) {
	    if ((Mode->CrtcHDisplay * 101) > (Mode->CrtcHTotal * 100)) /* 1% */
	    return MODE_HBLANK_NARROW;
    } else { /* no reduced blanking */
	if ((Mode->CrtcHDisplay * 23) > (Mode->CrtcHTotal * 20)) /* 15% */
	    return MODE_HBLANK_NARROW;
    }
    } else {
	if (((Mode->CrtcHDisplay * 5 / 4) & ~0x07) > Mode->CrtcHTotal) {
	    /* is this a cvt -r Mode, and only a cvt -r Mode? */
	    if (((Mode->CrtcHTotal - Mode->CrtcHDisplay) == 160) &&
		((Mode->CrtcHSyncEnd - Mode->CrtcHDisplay) == 80) &&
		((Mode->CrtcHSyncEnd - Mode->CrtcHSyncStart) == 32) &&
		((Mode->CrtcVSyncStart - Mode->CrtcVDisplay) == 3)) {
		if (!Monitor->ReducedAllowed)
		    return MODE_NO_REDUCED;
	    } else if ((Mode->CrtcHDisplay * 11) > (Mode->CrtcHTotal * 10))
		return MODE_HSYNC_NARROW;
	}
    }

    if (Monitor->UseFixedModes && !rhdMonitorFixedValid(Monitor, Mode))
	return MODE_FIXED;

    return MODE_OK;
}

#define RHD_MODE_VALIDATION_LOOPS 10

enum ValidationKind {
    VALIDATE_SCALE_NONE,
    VALIDATE_SCALE_FROM,
    VALIDATE_SCALE_TO
};

/*
 *
 */
static int
rhdModeValidateCrtc(struct rhdCrtc *Crtc, DisplayModePtr Mode, enum ValidationKind ValidateScaleModeKind)
{
    RHDPtr rhdPtr = RHDPTRI(Crtc);
    ScrnInfoPtr pScrn = rhdPtr->pScrn;
    int Status, i;

    RHDFUNC(Crtc);

    Status = rhdModeSanity(rhdPtr, Mode);
    if (Status != MODE_OK)
        return Status;

    rhdModeFillOutCrtcValues(Mode);

    /* We don't want to loop around this forever */
    for (i = 0; i < RHD_MODE_VALIDATION_LOOPS; i++) {
        struct rhdOutput *Output;

        Mode->CrtcHAdjusted = FALSE;
        Mode->CrtcVAdjusted = FALSE;

        Status = rhdModeCrtcSanity(Mode);
        if (Status != MODE_OK)
            return Status;
        if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
            continue;

	if (ValidateScaleModeKind != VALIDATE_SCALE_TO) {

	Status = Crtc->FBValid(Crtc, Mode->CrtcHDisplay, Mode->CrtcVDisplay,
			       pScrn->bitsPerPixel, rhdPtr->FbScanoutStart,
			       rhdPtr->FbScanoutSize, NULL);
        if (Status != MODE_OK)
            return Status;

	    if (Crtc->ScaleValid) {
		if (ValidateScaleModeKind == VALIDATE_SCALE_NONE)
		    Status = Crtc->ScaleValid(Crtc, RHD_CRTC_SCALE_TYPE_NONE, Mode, NULL);
		else
		    Status = Crtc->ScaleValid(Crtc, Crtc->ScaleType, Mode, Crtc->ScaledToMode);
        if (Status != MODE_OK)
            return Status;
        if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
            continue;
	    }
	}

	if (ValidateScaleModeKind != VALIDATE_SCALE_FROM) {
	    Status = Crtc->ModeValid(Crtc, Mode);
	    if (Status != MODE_OK)
		return Status;
	    if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
		continue;

	    if (Crtc->PLL && Crtc->PLL->Valid) { /* RandR may not have PLL filled out. oh well... */
	    Status = Crtc->PLL->Valid(Crtc->PLL, Mode->Clock);
	    if (Status != MODE_OK)
		return Status;
	    if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
		continue;
	}

        for (Output = rhdPtr->Outputs; Output; Output = Output->Next)
            if (Output->Active && (Output->Crtc == Crtc)) {
		/* Check the output */
                Status = Output->ModeValid(Output, Mode);
                if (Status != MODE_OK)
                    return Status;
                if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
                    break; /* restart. */

		/* Check the monitor attached to this output */
		if (Output->Connector && Output->Connector->Monitor)
		    Status = rhdMonitorValid(Output->Connector->Monitor, Mode);
		if (Status != MODE_OK)
                    return Status;
                if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
                    break; /* restart. */
            }

	    if (Output) /* We're done. This must be a good mode. */
		continue;
	}

            return MODE_OK;
    }

    /* Mode has been bouncing around for ages, on adjustments */
    xf86DrvMsg(Crtc->scrnIndex, X_ERROR, "%s: Mode \"%s\" (%dx%d:%3.1fMhz) was"
               " thrown around for too long.\n", __func__, Mode->name,
               Mode->HDisplay, Mode->VDisplay, Mode->Clock/1000.0);
    return MODE_ERROR;
}

/*
 *
 */
int
RHDValidateScaledToMode(struct rhdCrtc *Crtc, DisplayModePtr Mode)
{
    RHDPtr rhdPtr = RHDPTRI(Crtc);
    int Status;

    RHDFUNC(Crtc);

    Status = rhdModeSanity(rhdPtr, Mode);
    if (Status != MODE_OK)
        return Status;

    rhdModeFillOutCrtcValues(Mode);

    Status = rhdModeValidateCrtc(Crtc, Mode, VALIDATE_SCALE_TO);
    if (Status != MODE_OK)
	return Status;

    /* Do we want to also validate against a configured monitor? */
    if (rhdPtr->ConfigMonitor) {
	Status = rhdMonitorValid(rhdPtr->ConfigMonitor, Mode);
	if (Status != MODE_OK)
	    return Status;
    }

    return MODE_OK;
}

/*
 *
 */
static int
rhdModeValidate(ScrnInfoPtr pScrn, DisplayModePtr Mode)
{
    RHDPtr rhdPtr = RHDPTR(pScrn);
    struct rhdCrtc *Crtc;
    int Status;
    int i;

    Status = rhdModeSanity(rhdPtr, Mode);
    if (Status != MODE_OK)
        return Status;

    rhdModeFillOutCrtcValues(Mode);

    /* now let our modesetting tree have its say */
    for (i = 0; i < 2; i++) {
        Crtc = rhdPtr->Crtc[i];
        if (!Crtc->Active)
            continue;

	if (!Crtc->ScaledToMode) {

	    Status = rhdModeValidateCrtc(Crtc, Mode, VALIDATE_SCALE_NONE);
	if (Status != MODE_OK)
	    return Status;

	} else {
	    Status = rhdModeValidateCrtc(Crtc, Mode, VALIDATE_SCALE_FROM);
	    if (Status != MODE_OK)
		return Status;
    }
    }

    /* throw them at the configured monitor, so that the inadequate
     * conf file at least has some influence. */
    if (rhdPtr->ConfigMonitor) {
	Status = rhdMonitorValid(rhdPtr->ConfigMonitor, Mode);
	if (Status != MODE_OK)
	    return Status;
    }

    /* Did we set up virtual resolution already? */
    if ((pScrn->virtualX > 0) && (pScrn->virtualY > 0)) {
        if (pScrn->virtualX < Mode->CrtcHDisplay)
            return MODE_VIRTUAL_X;
        if (pScrn->virtualY < Mode->CrtcVDisplay)
            return MODE_VIRTUAL_Y;
    }

    return MODE_OK;
}

/*
 * Wrap the limited xf86 Mode statusses with our own message.
 */
struct {
    int Status;
    char *Message;
} rhdModeStatusMessages[] = {
    { MODE_NO_REDUCED,    "Reduced blanking is not supported."},
    { MODE_MEM_BW,        "Memory bandwidth exceeded."},
    { MODE_OUTPUT_UNDEF,  "Mode not defined by output device."},
    { MODE_NOT_PAL,       "This is not a PAL TV mode."},
    { MODE_NOT_NTSC,      "This is not an NTSC TV mode."},
    { MODE_HTOTAL_WIDE,   "Horizontal Total is out of range."},
    { MODE_HDISPLAY_WIDE, "Mode is too wide."},
    { MODE_HSYNC_RANGE,   "Horizontal Sync Start is out of range."},
    { MODE_HBLANK_RANGE,  "Horizontal Blanking Start is out of range."},
    { MODE_VTOTAL_WIDE,   "Vertical Total is out of range.\n"},
    { MODE_VDISPLAY_WIDE, "Mode is too high."},
    { MODE_VSYNC_RANGE,   "Vertical Sync Start is out of range.\n"},
    { MODE_VBLANK_RANGE,  "Vertical Blanking Start is out of range."},
    { MODE_PITCH,         "Scanout buffer Pitch too wide."},
    { MODE_OFFSET,        "Scanout buffer offset too high in FB."},
    { MODE_MINHEIGHT,     "Height too low."},
    { MODE_FIXED,         "Mode not compatible with fixed mode."},
    { MODE_SCALE,         "Mode cannot be scaled to fixed mode."},
    { 0, NULL}
};

const char *
xf86ModeStatusToString(ModeStatus status)
{
    switch (status) {
    case MODE_OK:
	return "Mode OK";
    case MODE_HSYNC:
	return "hsync out of range";
    case MODE_VSYNC:
	return "vrefresh out of range";
    case MODE_H_ILLEGAL:
	return "illegal horizontal timings";
    case MODE_V_ILLEGAL:
	return "illegal vertical timings";
    case MODE_BAD_WIDTH:
	return "width requires unsupported line pitch";
    case MODE_NOMODE:
	return "no mode of this name";
    case MODE_NO_INTERLACE:
	return "interlace mode not supported";
    case MODE_NO_DBLESCAN:
	return "doublescan mode not supported";
    case MODE_NO_VSCAN:
	return "multiscan mode not supported";
    case MODE_MEM:
	return "insufficient memory for mode";
    case MODE_VIRTUAL_X:
	return "width too large for virtual size";
    case MODE_VIRTUAL_Y:
	return "height too large for virtual size";
    case MODE_MEM_VIRT:
	return "insufficient memory given virtual size";
    case MODE_NOCLOCK:
	return "no clock available for mode";
    case MODE_CLOCK_HIGH:
	return "mode clock too high";
    case MODE_CLOCK_LOW:
	return "mode clock too low";
    case MODE_CLOCK_RANGE:
	return "bad mode clock/interlace/doublescan";
    case MODE_BAD_HVALUE:
	return "horizontal timing out of range";
    case MODE_BAD_VVALUE:
	return "vertical timing out of range";
    case MODE_BAD_VSCAN:
	return "VScan value out of range";
    case MODE_HSYNC_NARROW:
	return "horizontal sync too narrow";
    case MODE_HSYNC_WIDE:
	return "horizontal sync too wide";
    case MODE_HBLANK_NARROW:
	return "horizontal blanking too narrow";
    case MODE_HBLANK_WIDE:
	return "horizontal blanking too wide";
    case MODE_VSYNC_NARROW:
	return "vertical sync too narrow";
    case MODE_VSYNC_WIDE:
	return "vertical sync too wide";
    case MODE_VBLANK_NARROW:
	return "vertical blanking too narrow";
    case MODE_VBLANK_WIDE:
	return "vertical blanking too wide";
    case MODE_PANEL:
	return "exceeds panel dimensions";
    case MODE_INTERLACE_WIDTH:
	return "width too large for interlaced mode";
    case MODE_ONE_WIDTH:
        return "all modes must have the same width";
    case MODE_ONE_HEIGHT:
        return "all modes must have the same height";
    case MODE_ONE_SIZE:
        return "all modes must have the same resolution";
    case MODE_BAD:
	return "unknown reason";
    case MODE_ERROR:
	return "internal error";
    default:
	return "unknown";
    }
}

const char *RHDModeStatusToString(int Status)
{
    if ((Status & 0xFFF00) == RHD_MODE_STATUS) {
        int i;

        for (i = 0; rhdModeStatusMessages[i].Message; i++)
            if (rhdModeStatusMessages[i].Status == Status)
                return rhdModeStatusMessages[i].Message;
        ErrorF("%s: unhandled Status type: 0x%X\n", __func__, Status);
        return "Unknown status.";

    } else
        return xf86ModeStatusToString(Status);
}

/*
 *
 */
static DisplayModePtr
rhdModesGrabOnNameAll(DisplayModePtr *Modes, char *name)
{
    DisplayModePtr Mode, Matched = NULL, Temp;

    for (Mode = *Modes; Mode; ) {
        if (!strcmp(Mode->name, name)) {
            Temp = Mode;
            Mode = Mode->next;

            if (Temp->prev)
                Temp->prev->next = Mode;
            else
                *Modes = Mode;

            if (Mode)
                Mode->prev = Temp->prev;

            Temp->prev = NULL;
            Temp->next = Matched;
            if (Matched)
                Matched->prev = Temp;
            Matched = Temp;
        } else
            Mode = Mode->next;
    }

    return Matched;
}

/*
 *
 */
static DisplayModePtr
rhdModesGrabOnTypeAll(DisplayModePtr *Modes, int Type, int Mask)
{
    DisplayModePtr Mode, Matched = NULL, Temp;

    for (Mode = *Modes; Mode; ) {
        if ((Mode->type & Mask) == (Type & Mask)) {
            Temp = Mode;
            Mode = Mode->next;

            if (Temp->prev)
                Temp->prev->next = Mode;
            else
                *Modes = Mode;

            if (Mode)
                Mode->prev = Temp->prev;

            Temp->next = Matched;
            if (Matched)
                Matched->prev = Temp;
            Temp->prev = NULL;
            Matched = Temp;
        } else
            Mode = Mode->next;
    }

    return Matched;
}

/*
 *
 */
static DisplayModePtr
rhdModesGrabBestRefresh(DisplayModePtr *Modes)
{
    DisplayModePtr Mode, Best = NULL;

    if (!*Modes)
        return NULL;

    Best = *Modes;

    for (Mode = Best->next; Mode; Mode = Mode->next)
        if (Best->VRefresh < Mode->VRefresh)
            Best = Mode;
        else if (Best->VRefresh == Mode->VRefresh) {
            /* Same name != same resolution */
            if ((Best->HDisplay * Best->VDisplay) <
                (Mode->HDisplay * Mode->VDisplay))
                Best = Mode;
            else if ((Best->HDisplay * Best->VDisplay) ==
                     (Mode->HDisplay * Mode->VDisplay)) {
                /* Lower bandwidth == better! */
                if (Best->Clock > Mode->Clock)
                    Best = Mode;
            }
        }

    if (Best->next)
        Best->next->prev = Best->prev;
    if (Best->prev)
        Best->prev->next = Best->next;
    if (Best == *Modes)
        *Modes = (*Modes)->next;

    Best->next = NULL;
    Best->prev = NULL;

    return Best;
}

/*
 *
 */
static DisplayModePtr
rhdModesGrabOnHighestType(DisplayModePtr *Modes)
{
    DisplayModePtr Mode;

    /* User provided, but can also have another source. */
    Mode = rhdModesGrabOnTypeAll(Modes, M_T_USERDEF, 0xF0);
    if (Mode)
        return Mode;

    /* Often EDID provided, but can also have another source. */
    Mode = rhdModesGrabOnTypeAll(Modes, M_T_DRIVER, 0xF0);
    if (Mode)
        return Mode;

    /* No reason why we should treat built-in and vesa separately */
    Mode = *Modes;
    *Modes = NULL;
    return Mode;
}

/*
 *
 */
static DisplayModePtr
rhdModesSortOnSize(DisplayModePtr Modes)
{
    DisplayModePtr Sorted, Mode, Temp, Next;

    if (!Modes)
        return NULL;

    Sorted = Modes;
    Modes = Modes->next;

    Sorted->next = NULL;
    Sorted->prev = NULL;

    for (Next = Modes; Next; ) {
        /* since we're taking modelines from in between */
        Mode = Next;
        Next = Next->next;

        for (Temp = Sorted; Temp; Temp = Temp->next) {
            /* nasty ! */
            if (((Temp->CrtcHDisplay * Temp->CrtcVDisplay) <
                (Mode->CrtcHDisplay * Mode->CrtcVDisplay)) ||
                (((Temp->CrtcHDisplay * Temp->CrtcVDisplay) ==
                  (Mode->CrtcHDisplay * Mode->CrtcVDisplay)) &&
                 ((Temp->VRefresh < Mode->VRefresh)  ||
                  ((Temp->VRefresh < Mode->VRefresh) &&
                   (Temp->SynthClock < Mode->SynthClock))))) {
                Mode->next = Temp;
                Mode->prev = Temp->prev;
                Temp->prev = Mode;
                if (Mode->prev)
                    Mode->prev->next = Mode;
                else
                    Sorted = Mode;
                break;
            }

            if (!Temp->next) {
                Temp->next = Mode;
                Mode->prev = Temp;
                Mode->next = NULL;
                break;
            }
        }
    }

    return Sorted;
}

#if 0
/*
 * take a modename, try to parse it, if that works, generate the CVT modeline.
 */
static DisplayModePtr
rhdModeCreateFromName(ScrnInfoPtr pScrn, char *name, Bool Silent)
{
    DisplayModePtr Mode;
    int HDisplay = 0, VDisplay = 0, tmp;
    float VRefresh = 0;
    Bool Reduced;
    int Status;

    sscanf(name, "%dx%d@%f", &HDisplay, &VDisplay, &VRefresh);
    if (!HDisplay || !VDisplay) {
        if (!Silent)
            xf86DrvMsg(pScrn->scrnIndex, X_INFO, "%s: Unable to generate "
                       "Modeline for Mode \"%s\"\n", __func__, name);
        return NULL;
    }

    tmp = strlen(name) - 1;
    if ((name[tmp] == 'r') || (name[tmp] == 'R'))
        Reduced = TRUE;
    else
        Reduced = FALSE;

    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "Generating Modeline for \"%s\"\n", name);

    /* First, try a plain CVT mode */
    Mode = RHDCVTMode(HDisplay, VDisplay, VRefresh, Reduced, FALSE);
    xfree(Mode->name);
    Mode->name = xnfstrdup(name);
    Mode->type = M_T_USERDEF;

    Status = rhdModeValidate(pScrn, Mode);
    if (Status == MODE_OK)
        return Mode;
    rhdModesDestroy(Mode);

#if 0 /* noscale mode */
    /* Now see if we have fixed modes */
    for (i = 0; i < 2; i++) {
        Crtc = rhdPtr->Crtc[i];

        if (!Crtc->Active || !Crtc->FixedMode)
            continue;

        Mode = RHDModeCopy(Crtc->FixedMode);
        xfree(Mode->name);
        Mode->name = xnfstrdup(name);
        Mode->type = M_T_USERDEF;

        Mode->HDisplay = HDisplay;
        Mode->CrtcHDisplay = 0; /* set by validation code */
        Mode->VDisplay = VDisplay;
        Mode->CrtcVDisplay = 0;

	Status = rhdModeValidate(pScrn, Mode);
        if (Status == MODE_OK)
            return Mode;
        rhdModesDestroy(Mode);
    }
#endif

    if (!Silent)
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Rejected mode \"%s\" "
		   "(%dx%d):\n\t %s\n", name, HDisplay, VDisplay,
		   RHDModeStatusToString(Status));
    return NULL;
}
#endif

/*
 *
 */
static DisplayModePtr
rhdModesListValidateAndCopy(ScrnInfoPtr pScrn, DisplayModePtr Modes, Bool Silent)
{
  DisplayModePtr Keepers = NULL, Check, Mode;
  int Status;

    for (Check = Modes; Check; Check = Check->next) {
    Mode = RHDModeCopy(Check);

    Status = rhdModeValidate(pScrn, Mode);
    if (Status == MODE_OK)
	    Keepers = RHDModesAdd(Keepers, Mode);
	else {
	    if (!Silent)
		xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Rejected mode \"%s\" "
			   "(%dx%d:%3.1fMhz): %s\n", Mode->name,
			   Mode->HDisplay, Mode->VDisplay,
			   Mode->Clock / 1000.0, RHDModeStatusToString(Status));
	    xfree(Mode->name);
	    xfree(Mode);
    }
  }

  return Keepers;
}

/*
 * Create the list of all modes that are currently valid
 */
static DisplayModePtr
rhdCreateModesListAndValidate(ScrnInfoPtr pScrn, Bool Silent)
{
    RHDPtr rhdPtr = RHDPTR(pScrn);
  DisplayModePtr Keepers = NULL, Modes;
  struct rhdCrtc *Crtc;
  struct rhdOutput *Output;
  int i;

    RHDFUNC(pScrn);

    /* Cycle through our monitors list, and find a fixed mode one */
    for (i = 0; i < 2; i++) {
    Crtc = rhdPtr->Crtc[i];
	for (Output = rhdPtr->Outputs; Output; Output = Output->Next) {
	    if (Output->Active && (Output->Crtc == Crtc)) {
        if (Output->Connector && Output->Connector->Monitor
		    && Output->Connector->Monitor->UseFixedModes
		    && !Crtc->ScaledToMode) {
          Modes = Output->Connector->Monitor->Modes;
          if (!Silent && Modes)
			xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Validating Fixed"
                      " Modes from Monitor \"%s\"\n\t on Connector"
                      " \"%s\"\n", Output->Connector->Monitor->Name,
				   Output->Connector->Name);

          Modes = rhdModesListValidateAndCopy(pScrn, Modes, Silent);
          Keepers = RHDModesAdd(Keepers, Modes);
          return Keepers;
        }
	    }
    }
  }


    /* Cycle through our actual monitors list */
    for (i = 0; i < 2; i++) {
    Crtc = rhdPtr->Crtc[i];

	for (Output = rhdPtr->Outputs; Output; Output = Output->Next) {
	    if (Output->Active && (Output->Crtc == Crtc)) {
		if (Output->Connector && Output->Connector->Monitor) {
          Modes = Output->Connector->Monitor->Modes;
          if (!Silent && Modes)
			xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Validating Modes "
                      "from Monitor \"%s\" on \"%s\"\n",
                      Output->Connector->Monitor->Name,
				   Output->Connector->Name);

          Modes = rhdModesListValidateAndCopy(pScrn, Modes, Silent);
          Keepers = RHDModesAdd(Keepers, Modes);
        }
	    }
    }
  }

  return Keepers;
}

/*
 *
 */
DisplayModePtr
RHDModesPoolCreate(ScrnInfoPtr pScrn, Bool Silent)
{
    DisplayModePtr Pool = NULL, List, TempList, Temp;
    char **ModeNames = NULL; //pScrn->display->modes;
    int i;

    RHDFUNC(pScrn);

    List = rhdCreateModesListAndValidate(pScrn, Silent);
    if (!List)
        return List;

    /* Reduce our list */
    if (ModeNames && ModeNames[0]) { /* Find the best matching mode for each name */
        for (i = 0; ModeNames[i]; i++) {
        TempList = rhdModesGrabOnNameAll(&List, ModeNames[i]);
            if (TempList) {
          Temp = rhdModesGrabOnHighestType(&TempList);
          rhdModesDestroy(TempList);

          TempList = Temp;
          Temp = rhdModesGrabOnTypeAll(&TempList, M_T_PREFERRED, M_T_PREFERRED);
                if (Temp) {
            rhdModesDestroy(TempList);
            TempList = Temp;
          }

          Temp = rhdModesGrabBestRefresh(&TempList);

          rhdModesDestroy(TempList);
        }
      //  else /* No matching modes found, generate */
      //    Temp = rhdModeCreateFromName(pScrn, ModeNames[i], Silent);

        if (Temp)
          Pool = RHDModesAdd(Pool, Temp);
      }
      rhdModesDestroy(List);
    } else { /* No names, just work the list directly */
      Temp = rhdModesGrabOnHighestType(&List);
      rhdModesDestroy(List);
      List = Temp;

        while (List) {
        TempList = rhdModesGrabOnNameAll(&List, List->name);

        Temp = rhdModesGrabOnTypeAll(&TempList, M_T_PREFERRED, M_T_PREFERRED);
            if (Temp) {
           rhdModesDestroy(TempList);
           TempList = Temp;
        }

        Temp = rhdModesGrabBestRefresh(&TempList);
        rhdModesDestroy(TempList);

        Pool = RHDModesAdd(Pool, Temp);
      }

        /* Sort our list */
      TempList = Pool;

        /* Sort higher priority modes separately */
      Pool = rhdModesGrabOnTypeAll(&TempList, M_T_PREFERRED, M_T_PREFERRED);
      Pool = rhdModesSortOnSize(Pool);

      TempList = rhdModesSortOnSize(TempList);

      Pool = RHDModesAdd(Pool, TempList);
    }

    return Pool;
}

/*
 *
 */
void
RHDModesAttach(ScrnInfoPtr pScrn, DisplayModePtr Modes)
{
    DisplayModePtr Mode = Modes;

    pScrn->modes = Modes;
    pScrn->currentMode = Modes;

    while (Mode->next) {
        Mode->type = M_T_USERDEF; /* satisfy xf86ZoomViewport */
        Mode = Mode->next;
    }

    Mode->type = M_T_USERDEF;

    /* Make our list circular */
    Mode->next = pScrn->modes;
    pScrn->modes->prev = Mode;
}

/*
 *
 */
#if 0
Bool
RHDGetVirtualFromConfig(ScrnInfoPtr pScrn)
{
    RHDPtr rhdPtr = RHDPTR(pScrn);
    struct rhdCrtc *Crtc1 = rhdPtr->Crtc[0], *Crtc2 = rhdPtr->Crtc[1];
    CARD32 VirtualX = pScrn->display->virtualX;
    CARD32 VirtualY = pScrn->display->virtualY;
    CARD32 Pitch1, Pitch2;
    float Ratio = (float) pScrn->display->virtualY / pScrn->display->virtualX;
    int ret = FALSE;

    RHDFUNC(pScrn);

    while (VirtualX && VirtualY) {
	ret = Crtc1->FBValid(Crtc1, VirtualX, VirtualY, pScrn->bitsPerPixel,
			     rhdPtr->FbScanoutStart, rhdPtr->FbScanoutSize, &Pitch1);
	if (ret != MODE_OK)
	    goto shrink;
	ret = Crtc2->FBValid(Crtc2, VirtualX, VirtualY, pScrn->bitsPerPixel,
			     rhdPtr->FbScanoutStart, rhdPtr->FbScanoutSize, &Pitch2);
	if (ret != MODE_OK)
	    goto shrink;

	if (Pitch1 != Pitch2)
	    goto shrink;
#if 0
	/* let 2d acceleration have a say as well */
	if (rhdPtr->AccelMethod >= RHD_ACCEL_XAA)
	    if (rhdPtr->ChipSet < RHD_R600) /* badly abstracted, i know */
		if (!R5xx2DFBValid(rhdPtr, VirtualX, VirtualY, pScrn->bitsPerPixel,
				   rhdPtr->FbScanoutStart, rhdPtr->FbScanoutSize, Pitch1))
		    goto shrink;
#endif
	break; /* must be good then. */
    shrink:
	VirtualX--;
	VirtualY = Ratio * VirtualX;
    }

    if (VirtualX && VirtualY) {
	pScrn->virtualX = VirtualX;
	pScrn->virtualY = VirtualY;
	pScrn->displayWidth = Pitch1;
	return TRUE;
    } else
	return FALSE;
}

/*
 *
 */
void
RHDGetVirtualFromModesAndFilter(ScrnInfoPtr pScrn, DisplayModePtr Modes, Bool Silent)
{
    RHDPtr rhdPtr = RHDPTR(pScrn);
    struct rhdCrtc *Crtc1 = rhdPtr->Crtc[0], *Crtc2 = rhdPtr->Crtc[1];
    DisplayModePtr Mode, Next;
    CARD32 VirtualX = 0;
    CARD32 VirtualY = 0;
    CARD32 Pitch1, Pitch2;
    int ret = FALSE;

    RHDFUNC(pScrn);

    /* assert */
    if (!Modes)
	return;

    Mode = Modes;

    while (Mode) {
	if ((Mode->CrtcHDisplay > pScrn->virtualX) ||
            (Mode->CrtcVDisplay > pScrn->virtualY)) {
            if (Mode->CrtcHDisplay > pScrn->virtualX)
                VirtualX = Mode->CrtcHDisplay;
	    else
		VirtualX = pScrn->virtualX;

            if (Mode->CrtcVDisplay > pScrn->virtualY)
                VirtualY = Mode->CrtcVDisplay;
	    else
		VirtualY = pScrn->virtualY;

	    /* Check what Crtc1 thinks this should be. */
	    ret = Crtc1->FBValid(Crtc1, VirtualX, VirtualY, pScrn->bitsPerPixel,
				 rhdPtr->FbScanoutStart, rhdPtr->FbScanoutSize, &Pitch1);
	    if (ret != MODE_OK) {
		xf86DrvMsg(pScrn->scrnIndex, X_INFO, "%s rejected mode \"%s\" "
			   "(%dx%d): %s\n", Crtc1->Name, Mode->name,
			   Mode->HDisplay, Mode->VDisplay,
			   RHDModeStatusToString(ret));
		goto rejected;
	    }

	    /* Check what Crtc2 thinks this should be. */
	    ret = Crtc2->FBValid(Crtc2, VirtualX, VirtualY, pScrn->bitsPerPixel,
				 rhdPtr->FbScanoutStart, rhdPtr->FbScanoutSize, &Pitch2);
	    if (ret != MODE_OK) {
		xf86DrvMsg(pScrn->scrnIndex, X_INFO, "%s rejected mode \"%s\" "
			   "(%dx%d): %s\n", Crtc2->Name, Mode->name,
			   Mode->HDisplay, Mode->VDisplay,
			   RHDModeStatusToString(ret));
		goto rejected;
	    }

	    /* when needed, check whether this matches our 2D engine as well. */
	    if (rhdPtr->AccelMethod >= RHD_ACCEL_XAA)
		if (rhdPtr->ChipSet < RHD_R600) /* badly abstracted, i know */
#if 0
		    if (!R5xx2DFBValid(rhdPtr, VirtualX, VirtualY,
				       pScrn->bitsPerPixel, rhdPtr->FbScanoutStart,
				       rhdPtr->FbScanoutSize, Pitch1)) {
			xf86DrvMsg(pScrn->scrnIndex, X_INFO, "2D acceleration "
				   "rejected mode \"%s\" (%dx%d).\n",
				   Mode->name, Mode->HDisplay, Mode->VDisplay);
			goto rejected;
		    }
#endif
	    /* mode is perfectly valid FB wise */
	    Mode = Mode->next;
	    pScrn->virtualX = VirtualX;
	    pScrn->virtualY = VirtualY;
	    pScrn->displayWidth = Pitch1;
	    continue;

	rejected:
	    Next = Mode->next;
	    Modes = rhdModeDelete(Modes, Mode);
	    Mode = Next;
	} else
	    Mode = Mode->next;
    }
}

/*
 * RandR entry point: fixup per Crtc and Output (in RandR speech)
 * Due to misconceptions we might end up fixing *everything* here.
 */
int
RHDRRModeFixup(ScrnInfoPtr pScrn, DisplayModePtr Mode, struct rhdCrtc *Crtc,
	       struct rhdConnector *Connector, struct rhdOutput *Output,
	       struct rhdMonitor *Monitor, Bool ScaledMode)
{
    RHDPtr rhdPtr = RHDPTR(pScrn);
    int i, Status;

    ASSERT(Connector);
    ASSERT(Output);
    RHDFUNC(Output);

    Status = rhdModeSanity(rhdPtr, Mode);
    if (Status != MODE_OK)
        return Status;

    rhdModeFillOutCrtcValues(Mode);

    if (!ScaledMode) {
    /* We don't want to loop around this forever */
	for (i = 0; i < RHD_MODE_VALIDATION_LOOPS; i++) {
        Mode->CrtcHAdjusted = FALSE;
        Mode->CrtcVAdjusted = FALSE;

	/* Sanitize */
        Status = rhdModeCrtcSanity(Mode);
        if (Status != MODE_OK)
            return Status;
        if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
            continue;

	if (Crtc) {
	    /* Check FB */
	    Status = Crtc->FBValid(Crtc, Mode->CrtcHDisplay, Mode->CrtcVDisplay,
				   pScrn->bitsPerPixel, rhdPtr->FbScanoutStart,
				   rhdPtr->FbScanoutSize, NULL);
	    if (Status != MODE_OK)
		return Status;

		if (Crtc->ScaleValid) {
		    Status = Crtc->ScaleValid(Crtc, RHD_CRTC_SCALE_TYPE_NONE, Mode, NULL);
	    if (Status != MODE_OK)
		return Status;
	    if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
		continue;
		}

		/* Check Crtc */
		Status = Crtc->ModeValid(Crtc, Mode);
		if (Status != MODE_OK)
		    return Status;
		if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
		    continue;

	    /* Check PLL */
	    if (Crtc->PLL->Valid) {
		Status = Crtc->PLL->Valid(Crtc->PLL, Mode->Clock);
		if (Status != MODE_OK)
		    return Status;
		if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
		    continue;
	    }
	}

	/* Check Output */
	Status = Output->ModeValid(Output, Mode);
	if (Status != MODE_OK)
	    return Status;
	if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
	    continue;

	/* Check the monitor attached to this output */
	if (Connector->Monitor)
	    Status = rhdMonitorValid(Connector->Monitor, Mode);
	if (Status != MODE_OK)
	    return Status;
	if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
	    continue;

	/* Seems to be good */
	break;
    }

	if (i == RHD_MODE_VALIDATION_LOOPS) {
	/* Mode has been bouncing around for ages, on adjustments */
	xf86DrvMsg(Output->scrnIndex, X_ERROR,
		   "%s: Mode \"%s\" (%dx%d:%3.1fMhz) was thrown around"
		   " for too long.\n", __func__, Mode->name,
		   Mode->HDisplay, Mode->VDisplay, Mode->Clock/1000.0);
	return MODE_ERROR;
    }

    /* throw them at the configured monitor */
    if (Monitor) {
	Status = rhdMonitorValid(Monitor, Mode);
	if (Status != MODE_OK)
	    return Status;
    }

    } else {
	if (Crtc) {
	    Status = rhdModeValidateCrtc(Crtc, Mode, VALIDATE_SCALE_FROM);
	    if (Status != MODE_OK)
		return Status;
	}
    }

    /* Did we set up virtual resolution already? */
    if ((pScrn->virtualX > 0) && (pScrn->virtualY > 0)) {
        if (pScrn->virtualX < Mode->CrtcHDisplay)
            return MODE_VIRTUAL_X;
        if (pScrn->virtualY < Mode->CrtcVDisplay)
            return MODE_VIRTUAL_Y;
    }

    return MODE_OK;
}
#endif

/*
 * RHDRRValidateScaledToMode(): like RHDValidateScaledMode() - but we cannot validate against a CRTC
 * as this isn't known when this function is called. So at least validate against the 'output' here.
 */
int
RHDRRValidateScaledToMode(struct rhdOutput *Output, DisplayModePtr Mode)
{
    RHDPtr rhdPtr = RHDPTRI(Output);
    int Status;
    int i;

    RHDFUNC(Output);

    Status = rhdModeSanity(rhdPtr, Mode);
    if (Status != MODE_OK)
        return Status;

    rhdModeFillOutCrtcValues(Mode);

    for (i = 0; i < RHD_MODE_VALIDATION_LOOPS; i++) {

        Mode->CrtcHAdjusted = FALSE;
        Mode->CrtcVAdjusted = FALSE;

        Status = rhdModeCrtcSanity(Mode);
        if (Status != MODE_OK)
            return Status;
        if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
            continue;

	/* Check the output */
	Status = Output->ModeValid(Output, Mode);
	if (Status != MODE_OK)
	    return Status;
	if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
	    continue; /* restart. */

	/* Check the monitor attached to this output */
	if (Output->Connector && Output->Connector->Monitor)
	    Status = rhdMonitorValid(Output->Connector->Monitor, Mode);
	if (Status != MODE_OK)
	    return Status;
	if (Mode->CrtcHAdjusted || Mode->CrtcVAdjusted)
	    continue;

	break;
    }

    if (i == RHD_MODE_VALIDATION_LOOPS) {
	/* Mode has been bouncing around for ages, on adjustments */
	xf86DrvMsg(Output->scrnIndex, X_ERROR,
		   "%s: Mode \"%s\" (%dx%d:%3.1fMhz) was thrown around"
		   " for too long.\n", __func__, Mode->name,
		   Mode->HDisplay, Mode->VDisplay, Mode->Clock/1000.0);
	return MODE_ERROR;
    }

    /* Do we want to also validate against a configured monitor? */
    if (rhdPtr->ConfigMonitor) {
	Status = rhdMonitorValid(rhdPtr->ConfigMonitor, Mode);
	if (Status != MODE_OK)
	    return Status;
    }

    return MODE_OK;
}

/*
 * RHDSynthModes(): synthesize CVT modes for well known resolutions.
 * For now we assume we want reduced modes only.
 */
void
RHDSynthModes(int scrnIndex, DisplayModePtr Mode)
{
    RHDPtr rhdPtr = (RHDPtr)(scrnIndex);
    DisplayModePtr Tmp;
    unsigned int i;

    struct resolution{
	int x;
	int y;
    } resolution_list[] = {
	{  320,  200 },  /* CGA */
	{  320,  240 },  /* QVGA */
	{  640,  480 },  /* VGA */
	{  720,  480 },  /* NTSC */
	{  854,  480 },  /* WVGA */
	{  768,  576 },  /* PAL */
	{  800,  600 },  /* SVGA */
	{ 1024,  768 },  /* XGA */
	{ 1152,  768 },
	{ 1280,  720 },  /* HD720 */
	{ 1280,  960 },
	{ 1280,  854 },
	{ 1280,  960 },
	{ 1280, 1024 },  /* SXGA */
	{ 1440,  960 },
 	{ 1400, 1050 },  /* SXGA+ */
	{ 1680, 1050 },  /* WSXGA+ */
	{ 1600, 1200 },  /* UXGA */
	{ 1920, 1080 },  /* HD1080 */
	{ 1920, 1200 },  /* WUXGA */
	{ 2048, 1536 },  /* QXGA */
	{ 2560, 1600 },  /* WQXGA */
	{ 2560, 2048 }   /* QSXGA */
    };

    RHDFUNC(pScrn);

    for (i = 0; i < (sizeof(resolution_list) / sizeof(struct resolution)); i++) {
	/*
	 *  chances are that the native mode of a display is a CVT mode with 60 Hz.
	 *  This will make RandR share the CRTC which is undesireable for scaling.
	 *  This we 'tweak' the frequency to be slightly higher.
	 *  Don't tell me it's ugly - I know this already.
	 */
	Tmp = RHDCVTMode(resolution_list[i].x, resolution_list[i].y, 60.5, TRUE, FALSE);
	Tmp->status = MODE_OK;
	rhdModeFillOutCrtcValues(Tmp);
	xfree(Tmp->name);
	Tmp->name = xnfalloc(20);
	snprintf(Tmp->name, 20, "%ix%iScaled",resolution_list[i].x,resolution_list[i].y);
	Tmp->type = M_T_BUILTIN;
  //  if (rhdPtr->verbosity > 6) {
  //      xf86DrvMsg(scrnIndex, X_INFO, "%s: Adding Modeline ",__func__);
  //      RHDPrintModeline(Tmp);
  //  }
	RHDModesAdd(Mode, Tmp);
    }
}