/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/ati/ativalid.c,v 1.17 2003/10/30 17:36:58 tsi Exp $ */
/*
 * Copyright 1997 through 2004 by Marc Aurele La France (TSI @ UQV), tsi@xfree86.org
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of Marc Aurele La France not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  Marc Aurele La France makes no representations
 * about the suitability of this software for any purpose.  It is provided
 * "as-is" without express or implied warranty.
 *
 * MARC AURELE LA FRANCE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO
 * EVENT SHALL MARC AURELE LA FRANCE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include "atiadapter.h"
#include "atichip.h"
#include "aticrtc.h"
#include "atistruct.h"
#include "ativalid.h"

#include "xf86.h"

/*
 * ATIValidMode --
 *
 * This checks for hardware-related limits on mode timings.
 */
ModeStatus
ATIValidMode
(
    int iScreen,
    DisplayModePtr pMode,
    Bool Verbose,
    int flags
)
{
    ScrnInfoPtr pScreenInfo = xf86Screens[iScreen];
    ATIPtr      pATI        = ATIPTR(pScreenInfo);
    Bool        InterlacedSeen;
    int         HBlankWidth, HAdjust, VScan, VInterlace;

#ifndef AVOID_CPIO

    int VDisplay, VTotal;

#endif /* AVOID_CPIO */

    if (flags & MODECHECK_FINAL)
    {
        /*
         * This is the final check before the common layer accepts a mode.
         * pScreenInfo->displayWidth is set to the proposed virtual pitch
         * should the mode be accepted.  The only check needed here is for
         * 18800's and 28800's, which don't support interlaced modes if the
         * pitch is over half the chipset's maximum pitch.
         */
        if (pATI->MaximumInterlacedPitch)
        {
            /*
             * Ensure no interlaced modes have a scanline pitch larger than the
             * limit.
             */
            if (pMode->Flags & V_INTERLACE)
                InterlacedSeen = TRUE;
            else
                InterlacedSeen = pATI->InterlacedSeen;

            if (InterlacedSeen &&
                (pScreenInfo->displayWidth > pATI->MaximumInterlacedPitch))
                return MODE_INTERLACE_WIDTH;

            pATI->InterlacedSeen = InterlacedSeen;
        }

        return MODE_OK;
    }

    /*
     * The following is done for every mode in the monitor section that
     * survives the common layer's basic checks.
     */
    if (pMode->VScan <= 1)
        VScan = 1;
    else
        VScan = pMode->VScan;

    if (pMode->Flags & V_DBLSCAN)
        VScan <<= 1;

    if (pATI->OptionPanelDisplay && (pATI->LCDPanelID >= 0))
    {
        if ((pMode->CrtcHDisplay > pATI->LCDHorizontal) ||
            (pMode->CrtcVDisplay > pATI->LCDVertical))
            return MODE_PANEL;

        if (!pATI->OptionLCDSync || (pMode->type & M_T_BUILTIN))
        {
            if ((pMode->HDisplay > pATI->LCDHorizontal) ||
                (pMode->VDisplay > pATI->LCDVertical))
                return MODE_PANEL;

            return MODE_OK;
        }

        /*
         * Adjust effective timings for monitor checks.  Here the modeline
         * clock is ignored.  Horizontal timings are scaled by the stretch
         * ratio used for the displayed area.  The vertical porch is scaled by
         * the native resolution's aspect ratio.  This seems rather arbitrary,
         * and it is, but it does make all applicable VESA modes sync on a
         * panel after stretching.  This has the unfortunate, but necessary,
         * side-effect of changing the mode's horizontal sync and vertical
         * refresh rates.  With some exceptions, this tends to increase the
         * mode's horizontal sync rate, and decrease its vertical refresh rate.
         */
        pMode->SynthClock = pATI->LCDClock;

        pMode->CrtcHTotal = pMode->CrtcHBlankEnd =
            ATIDivide(pMode->CrtcHTotal * pATI->LCDHorizontal,
                pMode->CrtcHDisplay, -3, 1) << 3;
        pMode->CrtcHSyncEnd =
            ATIDivide(pMode->CrtcHSyncEnd * pATI->LCDHorizontal,
                pMode->CrtcHDisplay, -3, 1) << 3;
        pMode->CrtcHSyncStart =
            ATIDivide(pMode->CrtcHSyncStart * pATI->LCDHorizontal,
                pMode->CrtcHDisplay, -3, -1) << 3;
        pMode->CrtcHDisplay = pMode->CrtcHBlankStart = pATI->LCDHorizontal;

        pMode->CrtcVTotal = pMode->CrtcVBlankEnd =
            ATIDivide((pMode->CrtcVTotal - pMode->CrtcVDisplay) *
                pATI->LCDVertical, pATI->LCDHorizontal, 0, 1) +
                pATI->LCDVertical;
        pMode->CrtcVSyncEnd =
            ATIDivide((pMode->CrtcVSyncEnd - pMode->CrtcVDisplay) *
                pATI->LCDVertical, pATI->LCDHorizontal, 0, 1) +
                pATI->LCDVertical;
        pMode->CrtcVSyncStart =
            ATIDivide((pMode->CrtcVSyncStart - pMode->CrtcVDisplay) *
                pATI->LCDVertical, pATI->LCDHorizontal, 0, -1) +
                pATI->LCDVertical;
        pMode->CrtcVDisplay = pMode->CrtcVBlankStart = pATI->LCDVertical;

        /*
         * The CRTC only stretches the mode's displayed area, not its porches.
         * Reverse-engineer the mode's timings back into the user specified
         * values so that the stretched mode is produced when the CRTC is
         * eventually programmed.  The reverse-engineered mode is then checked
         * against CRTC limits below.
         */
        pMode->Clock = pATI->LCDClock;

        HAdjust = pATI->LCDHorizontal - pMode->HDisplay;
#       define ATIReverseHorizontal(_x) \
            (pMode->_x - HAdjust)

        pMode->HSyncStart = ATIReverseHorizontal(CrtcHSyncStart);
        pMode->HSyncEnd = ATIReverseHorizontal(CrtcHSyncEnd);
        pMode->HTotal = ATIReverseHorizontal(CrtcHTotal);

        VInterlace = GetBits(pMode->Flags, V_INTERLACE) + 1;
#       define ATIReverseVertical(_y) \
            ((((pMode->_y - pATI->LCDVertical) * VInterlace) / VScan) + \
             pMode->VDisplay)

        pMode->VSyncStart = ATIReverseVertical(CrtcVSyncStart);
        pMode->VSyncEnd = ATIReverseVertical(CrtcVSyncEnd);
        pMode->VTotal = ATIReverseVertical(CrtcVTotal);

#       undef ATIReverseHorizontal
#       undef ATIReverseVertical
    }

    HBlankWidth = (pMode->HTotal >> 3) - (pMode->HDisplay >> 3);
    if (!HBlankWidth)
        return MODE_HBLANK_NARROW;

    switch (pATI->NewHW.crtc)
    {

#ifndef AVOID_CPIO

        case ATI_CRTC_VGA:
            /* Prevent overscans */
            if (HBlankWidth > 63)
                return MODE_HBLANK_WIDE;

            if (pMode->HDisplay > 2048)
                return MODE_BAD_HVALUE;

            if (VScan > 64)
                return MODE_BAD_VSCAN;

            VDisplay = pMode->VDisplay * VScan;
            VTotal = pMode->VTotal * VScan;

            if ((pMode->Flags & V_INTERLACE) && (pATI->Chip < ATI_CHIP_264CT))
            {
                VDisplay >>= 1;
                VTotal >>= 1;
            }

            if ((VDisplay > 2048) || (VTotal > 2050))
                return MODE_BAD_VVALUE;

            if (pATI->Adapter != ATI_ADAPTER_VGA)
                break;

            if ((VDisplay > 1024) || (VTotal > 1025))
                return MODE_BAD_VVALUE;

            break;

#endif /* AVOID_CPIO */

        case ATI_CRTC_MACH64:
            if (VScan > 2)
                return MODE_NO_VSCAN;

            break;

        default:
            break;
    }

    return MODE_OK;
}
