/*
 * Copyright 2005-2007 The Openchrome Project [openchrome.org]
 * Copyright 2004-2005 The Unichrome Project  [unichrome.sf.net]
 * Copyright 1998-2003 VIA Technologies, Inc. All Rights Reserved.
 * Copyright 2001-2003 S3 Graphics, Inc. All Rights Reserved.
 *
 * 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, sub license,
 * 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 (including the
 * next paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS 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.
 */

/*
 * via_mode.c
 *
 * Everything to do with setting and changing modes.
 *
 */

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

#include "via.h"
#include "via_driver.h"
#include "via_vgahw.h"
#include "via_id.h"
#include <unistd.h>

/*
 * Modetable nonsense.
 *
 */
#include "via_mode.h"

static void
ViaPrintMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Name: %s\n", mode->name);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Clock: %d\n", mode->Clock);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VRefresh: %f\n", mode->VRefresh);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "HSync: %f\n", mode->HSync);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "HDisplay: %d\n", mode->HDisplay);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "HSyncStart: %d\n", mode->HSyncStart);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "HSyncEnd: %d\n", mode->HSyncEnd);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "HTotal: %d\n", mode->HTotal);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "HSkew: %d\n", mode->HSkew);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VDisplay: %d\n", mode->VDisplay);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VSyncStart: %d\n", mode->VSyncStart);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VSyncEnd: %d\n", mode->VSyncEnd);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VTotal: %d\n", mode->VTotal);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VScan: %d\n", mode->VScan);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Flags: %d\n", mode->Flags);

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcHDisplay: 0x%x\n",
               mode->CrtcHDisplay);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcHBlankStart: 0x%x\n",
               mode->CrtcHBlankStart);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcHSyncStart: 0x%x\n",
               mode->CrtcHSyncStart);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcHSyncEnd: 0x%x\n",
               mode->CrtcHSyncEnd);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcHBlankEnd: 0x%x\n",
               mode->CrtcHBlankEnd);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcHTotal: 0x%x\n",
               mode->CrtcHTotal);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcHSkew: 0x%x\n",
               mode->CrtcHSkew);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcVDisplay: 0x%x\n",
               mode->CrtcVDisplay);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcVBlankStart: 0x%x\n",
               mode->CrtcVBlankStart);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcVSyncStart: 0x%x\n",
               mode->CrtcVSyncStart);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcVSyncEnd: 0x%x\n",
               mode->CrtcVSyncEnd);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcVBlankEnd: 0x%x\n",
               mode->CrtcVBlankEnd);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "CrtcVTotal: 0x%x\n",
               mode->CrtcVTotal);

}

/*
 *
 * TV specific code.
 *
 */

/*
 *
 */
static Bool
ViaTVDetect(ScrnInfoPtr pScrn)
{
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VIATVDetect\n"));

    /* preset some pBIOSInfo TV related values -- move up */
    pBIOSInfo->TVEncoder = VIA_NONETV;
    pBIOSInfo->TVI2CDev = NULL;
    pBIOSInfo->TVSave = NULL;
    pBIOSInfo->TVRestore = NULL;
    pBIOSInfo->TVDACSense = NULL;
    pBIOSInfo->TVModeValid = NULL;
    pBIOSInfo->TVModeI2C = NULL;
    pBIOSInfo->TVModeCrtc = NULL;
    pBIOSInfo->TVPower = NULL;
    pBIOSInfo->TVModes = NULL;
    pBIOSInfo->TVPrintRegs = NULL;
    pBIOSInfo->LCDPower = NULL;
    pBIOSInfo->TVNumRegs = 0;

    /*
     * On an SK43G (KM400/Ch7011), false positive detections at a VT162x
     * chip were observed, so try to detect the Ch7011 first.
     */
    if (pVia->pI2CBus2 && xf86I2CProbeAddress(pVia->pI2CBus2, 0xEC))
        pBIOSInfo->TVI2CDev = ViaCH7xxxDetect(pScrn, pVia->pI2CBus2, 0xEC);
    else if (pVia->pI2CBus2 && xf86I2CProbeAddress(pVia->pI2CBus2, 0x40))
        pBIOSInfo->TVI2CDev = ViaVT162xDetect(pScrn, pVia->pI2CBus2, 0x40);
    else if (pVia->pI2CBus3 && xf86I2CProbeAddress(pVia->pI2CBus3, 0x40))
        pBIOSInfo->TVI2CDev = ViaVT162xDetect(pScrn, pVia->pI2CBus3, 0x40);
    else if (pVia->pI2CBus2 && xf86I2CProbeAddress(pVia->pI2CBus2, 0xEA))
        pBIOSInfo->TVI2CDev = ViaCH7xxxDetect(pScrn, pVia->pI2CBus2, 0xEA);
    else if (pVia->pI2CBus3 && xf86I2CProbeAddress(pVia->pI2CBus3, 0xEA))
        pBIOSInfo->TVI2CDev = ViaCH7xxxDetect(pScrn, pVia->pI2CBus3, 0xEA);

    if (pBIOSInfo->TVI2CDev)
        return TRUE;
    return FALSE;
}

/*
 *
 */
static Bool
ViaTVInit(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaTVInit\n"));

    switch (pBIOSInfo->TVEncoder) {
        case VIA_VT1621:
        case VIA_VT1622:
        case VIA_VT1623:
        case VIA_VT1625:
            ViaVT162xInit(pScrn);
            break;
        case VIA_CH7011:
        case VIA_CH7019A:
        case VIA_CH7019B:
            ViaCH7xxxInit(pScrn);
            break;
        default:
            return FALSE;
            break;
    }

    if (!pBIOSInfo->TVSave || !pBIOSInfo->TVRestore
        || !pBIOSInfo->TVDACSense || !pBIOSInfo->TVModeValid
        || !pBIOSInfo->TVModeI2C || !pBIOSInfo->TVModeCrtc
        || !pBIOSInfo->TVPower || !pBIOSInfo->TVModes
        || !pBIOSInfo->TVPrintRegs) {

        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "ViaTVInit: TVEncoder was not properly initialised.\n");

        xf86DestroyI2CDevRec(pBIOSInfo->TVI2CDev, TRUE);
        pBIOSInfo->TVI2CDev = NULL;
        pBIOSInfo->TVOutput = TVOUTPUT_NONE;
        pBIOSInfo->TVEncoder = VIA_NONETV;
        pBIOSInfo->TVI2CDev = NULL;
        pBIOSInfo->TVSave = NULL;
        pBIOSInfo->TVRestore = NULL;
        pBIOSInfo->TVDACSense = NULL;
        pBIOSInfo->TVModeValid = NULL;
        pBIOSInfo->TVModeI2C = NULL;
        pBIOSInfo->TVModeCrtc = NULL;
        pBIOSInfo->TVPower = NULL;
        pBIOSInfo->TVModes = NULL;
        pBIOSInfo->TVPrintRegs = NULL;
        pBIOSInfo->TVNumRegs = 0;

        return FALSE;
    }

    /* Save now */
    pBIOSInfo->TVSave(pScrn);

#ifdef HAVE_DEBUG
    if (VIAPTR(pScrn)->PrintTVRegs)
        pBIOSInfo->TVPrintRegs(pScrn);
#endif

    return TRUE;
}

void
ViaTVSave(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    if (pBIOSInfo->TVSave)
        pBIOSInfo->TVSave(pScrn);
}

void
ViaTVRestore(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    if (pBIOSInfo->TVRestore)
        pBIOSInfo->TVRestore(pScrn);
}

static Bool
ViaTVDACSense(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    if (pBIOSInfo->TVDACSense)
        return pBIOSInfo->TVDACSense(pScrn);
    return FALSE;
}

static void
ViaTVSetMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    if (pBIOSInfo->TVModeI2C)
        pBIOSInfo->TVModeI2C(pScrn, mode);

    if (pBIOSInfo->TVModeCrtc)
        pBIOSInfo->TVModeCrtc(pScrn, mode);

    /* TV reset. */
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x1D, 0x00);
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x1D, 0x80);
}

void
ViaTVPower(ScrnInfoPtr pScrn, Bool On)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

#ifdef HAVE_DEBUG
    if (On)
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaTVPower: On.\n");
    else
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaTVPower: Off.\n");
#endif

    if (pBIOSInfo->TVPower)
        pBIOSInfo->TVPower(pScrn, On);
}

#ifdef HAVE_DEBUG
void
ViaTVPrintRegs(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    if (pBIOSInfo->TVPrintRegs)
        pBIOSInfo->TVPrintRegs(pScrn);
}
#endif /* HAVE_DEBUG */

/*
 *
 */
static ModeStatus
ViaTVModeValid(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    if (pBIOSInfo->TVModeValid)
        return pBIOSInfo->TVModeValid(pScrn, mode);
    return MODE_OK;
}


static Bool
ViaDFPDetect(ScrnInfoPtr pScrn)
{
    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaDFPDetect\n"));

    VIAPtr pVia = VIAPTR(pScrn);

    xf86MonPtr          monPtr = NULL;

    if (pVia->pI2CBus2)
        monPtr = xf86DoEDID_DDC2(pScrn->scrnIndex, pVia->pI2CBus2);
    
    if (monPtr) {
        xf86PrintEDID(monPtr);
        xf86SetDDCproperties(pScrn, monPtr);
        return TRUE;
    } else {
        return FALSE;
    }
}


/*
 *
 */
void
ViaOutputsDetect(ScrnInfoPtr pScrn)
{
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaOutputsDetect\n"));

    pBIOSInfo->CrtPresent = FALSE;
    pBIOSInfo->PanelPresent = FALSE;
    pBIOSInfo->DfpPresent = FALSE;
    
    /* Panel */
    if (pBIOSInfo->ForcePanel) {
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Enabling panel from config.\n");
        pBIOSInfo->PanelPresent = TRUE;
    } else if (pVia->Id && (pVia->Id->Outputs & VIA_DEVICE_LCD)) {
        xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                   "Enabling panel from PCI-subsystem ID information.\n");
        pBIOSInfo->PanelPresent = TRUE;
    }

    /* Crt */
    if (pVia->DDC1)
        pBIOSInfo->CrtPresent = TRUE;
    /* If any of the unichromes support this, add CRT detection here */
    else if (!pBIOSInfo->PanelPresent) {
        /* Make sure that at least CRT is enabled. */
        if (!pVia->Id || (pVia->Id->Outputs & VIA_DEVICE_CRT))
            pBIOSInfo->CrtPresent = TRUE;
    }

    /*
     * FIXME: xf86I2CProbeAddress(pVia->pI2CBus3, 0x40)
     * disables the panel on P4M900
     * See ViaTVDetect.
     */
    if (pVia->Chipset == VIA_P4M900 && pBIOSInfo->PanelPresent) {
        xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                   "Will not try to detect TV encoder.\n");
    } else {
        /* TV encoder */
        if (ViaTVDetect(pScrn) && ViaTVInit(pScrn)) {
            if (!pBIOSInfo->TVOutput)  /* Config might've set this already */
                ViaTVDACSense(pScrn);
        } else if (pVia->Id && (pVia->Id->Outputs & VIA_DEVICE_TV)) {
            xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
                       "This device is supposed to have a TV encoder, but "
                       "we are unable to detect it (support missing?).\n");
            pBIOSInfo->TVOutput = 0;
        }
    }
    
    switch (pVia->Chipset) {
        case VIA_CX700:
        case VIA_VX800:
        case VIA_VX855:
            if (ViaDFPDetect(pScrn)) {
                pBIOSInfo->DfpPresent = TRUE;
                xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                           "DFP is connected.\n");
            } else {
                pBIOSInfo->DfpPresent = FALSE;
                xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                           "DFP is disconnected.\n");
            }
            break;
    }
}

#ifdef HAVE_DEBUG
/*
 * Returns:
 *   Bit[7] 2nd Path
 *   Bit[6] 1/0 MHS Enable/Disable
 *   Bit[5] 0 = Bypass Callback, 1 = Enable Callback
 *   Bit[4] 0 = Hot-Key Sequence Control (OEM Specific)
 *   Bit[3] LCD
 *   Bit[2] TV
 *   Bit[1] CRT
 *   Bit[0] DVI
 */
static CARD8
VIAGetActiveDisplay(ScrnInfoPtr pScrn)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    CARD8 tmp;

    tmp = (hwp->readCrtc(hwp, 0x3E) >> 4);
    tmp |= ((hwp->readCrtc(hwp, 0x3B) & 0x18) << 3);

    return tmp;
}
#endif /* HAVE_DEBUG */

/*
 *
 */
Bool
ViaOutputsSelect(ScrnInfoPtr pScrn)
{
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;

    if (pVia->IsSecondary) {  /* we just abort for now */
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "ViaOutputsSelect:"
                   " Not handling secondary.\n");
        return FALSE;
    }

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaOutputsSelect\n"));

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaOutputsSelect: X"
                     " Configuration: 0x%02x\n", pVia->ActiveDevice));
    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaOutputsSelect: BIOS"
                     " Initialised register: 0x%02x\n",
                     VIAGetActiveDisplay(pScrn)));

    pBIOSInfo->Panel->IsActive = FALSE;
    pBIOSInfo->CrtActive = FALSE;
    pBIOSInfo->TVActive = FALSE;
    pBIOSInfo->DfpActive = FALSE;
    
    if (!pVia->ActiveDevice) {
        /* always enable the panel when present */
        if (pBIOSInfo->PanelPresent)
            pBIOSInfo->Panel->IsActive = TRUE;
        else if (pBIOSInfo->TVOutput != TVOUTPUT_NONE)  /* cable is attached! */
            pBIOSInfo->TVActive = TRUE;

        /* CRT can be used with everything when present */
        if (pBIOSInfo->CrtPresent)
            pBIOSInfo->CrtActive = TRUE;

#if 0
        # FIXME : DFP must be activated with the ActiveDevice option 
        /* DFP */
        if (pBIOSInfo->DfpPresent)
            pBIOSInfo->DfpActive = TRUE;
#endif
        
    } else {
        if (pVia->ActiveDevice & VIA_DEVICE_LCD) {
            if (pBIOSInfo->PanelPresent)
                pBIOSInfo->Panel->IsActive = TRUE;
            else
                xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Unable to activate"
                           " panel: no panel is present.\n");
        }

        if (pVia->ActiveDevice & VIA_DEVICE_TV) {
            if (!pBIOSInfo->TVI2CDev)
                xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Unable to activate"
                           " TV encoder: no TV encoder present.\n");
            else if (pBIOSInfo->TVOutput == TVOUTPUT_NONE)
                xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Unable to activate"
                           " TV encoder: no cable attached.\n");
            else if (pBIOSInfo->Panel->IsActive)
                xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Unable to activate"
                           " TV encoder and panel simultaneously. Not using"
                           " TV encoder.\n");
            else
                pBIOSInfo->TVActive = TRUE;
        }

        if (pVia->ActiveDevice & VIA_DEVICE_DFP) {
            pBIOSInfo->DfpPresent = TRUE;
            pBIOSInfo->DfpActive = TRUE;
        }

        if ((pVia->ActiveDevice & VIA_DEVICE_CRT)
            || (!pBIOSInfo->Panel->IsActive && !pBIOSInfo->TVActive
                && !pBIOSInfo->DfpActive)) {
            pBIOSInfo->CrtPresent = TRUE;
            pBIOSInfo->CrtActive = TRUE;
        }
        if (pBIOSInfo->TVActive)
            pBIOSInfo->FirstCRTC->IsActive = TRUE ;
    }
    if (!pVia->UseLegacyModeSwitch) {
        if (pBIOSInfo->CrtActive)
            pBIOSInfo->FirstCRTC->IsActive = TRUE ;
        if (pBIOSInfo->DfpActive)
            pBIOSInfo->FirstCRTC->IsActive = TRUE ;
        if (pBIOSInfo->Panel->IsActive) {
            pVia->pBIOSInfo->SecondCRTC->IsActive = TRUE ;
            switch (pVia->Chipset) {
                case VIA_P4M900:
                case VIA_CX700:
                case VIA_VX800:
                case VIA_VX855:
                    pVia->pBIOSInfo->Lvds->IsActive = TRUE ;
                    break;
            }
        }
    }

#ifdef HAVE_DEBUG
    if (pBIOSInfo->CrtActive)
        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                         "ViaOutputsSelect: CRT.\n"));
    if (pBIOSInfo->Panel->IsActive)
        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                         "ViaOutputsSelect: Panel.\n"));
    if (pBIOSInfo->TVActive)
        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                         "ViaOutputsSelect: TV.\n"));
    if (pBIOSInfo->DfpActive)
        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                         "ViaOutputsSelect: DFP.\n"));
#endif
    return TRUE; /* !Secondary always has at least CRT */
}

/*
 *
 */
static void
VIAGetPanelSize(ScrnInfoPtr pScrn)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;
    char *PanelSizeString[7] = { "640x480", "800x480", "800x600", "1024x768", "1280x768"
                                 "1280x1024", "1400x1050", "1600x1200" };
    int width = 0;
    int height = 0;
    Bool ret;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VIAGetPanelSize (UseLegacyModeSwitch)\n"));

    ret = ViaPanelGetSizeFromDDCv1(pScrn, &width, &height);
    if (!ret)
        ret = ViaPanelGetSizeFromDDCv2(pScrn, &width);

    if (ret) {
        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "EDID returned resolution %d x %d \n", width, height));
        switch (width) {
            case 640:
                pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL6X4;
                break;
            case 800:
		if (height == 480)
               	    pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL8X4;
		else
               	    pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL8X6;
                break;
            case 1024:
                pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL10X7;
                break;
            case 1280:
                pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL12X10;
                break;
            case 1400:
                pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL14X10;
                break;
            case 1600:
                pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL16X12;
                break;
            default:
                pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL_INVALID;
                break;
        }
    } else {
        pBIOSInfo->Panel->NativeModeIndex = hwp->readCrtc(hwp, 0x3F) >> 4;
        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Unable to get information from EDID. Resolution from Scratchpad: %d \n", pBIOSInfo->Panel->NativeModeIndex));
        if (pBIOSInfo->Panel->NativeModeIndex == 0) {
            /* VIA_PANEL6X4 == 0, but that value equals unset */
            xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Unable to "
                       "retrieve PanelSize: using default (1024x768)\n");
            pBIOSInfo->Panel->NativeModeIndex = VIA_PANEL10X7;
            return;
        }
    }

    if (pBIOSInfo->Panel->NativeModeIndex < 7)
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Using panel at %s.\n",
                   PanelSizeString[pBIOSInfo->Panel->NativeModeIndex]);
    else
        xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Unknown panel size "
                   "detected: %d.\n", pBIOSInfo->Panel->NativeModeIndex);
}

/*
 *
 */
static Bool
ViaGetResolutionIndex(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    int i;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                     "ViaGetResolutionIndex: Looking for %dx%d\n",
                     mode->CrtcHDisplay, mode->CrtcVDisplay));
    for (i = 0; ViaResolutionTable[i].Index != VIA_RES_INVALID; i++) {
        if ((ViaResolutionTable[i].X == mode->CrtcHDisplay)
            && (ViaResolutionTable[i].Y == mode->CrtcVDisplay)) {
            pBIOSInfo->ResolutionIndex = ViaResolutionTable[i].Index;
            DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaGetResolutionIndex:"
                             " %d\n", pBIOSInfo->ResolutionIndex));
            return TRUE;
        }
    }

    pBIOSInfo->ResolutionIndex = VIA_RES_INVALID;
    return FALSE;
}

/*
 *
 */
static int
ViaGetVesaMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    int i;

    for (i = 0; ViaVesaModes[i].Width; i++)
        if ((ViaVesaModes[i].Width == mode->CrtcHDisplay)
            && (ViaVesaModes[i].Height == mode->CrtcVDisplay)) {
            switch (pScrn->bitsPerPixel) {
                case 8:
                    return ViaVesaModes[i].mode_8b;
                case 16:
                    return ViaVesaModes[i].mode_16b;
                case 24:
                case 32:
                    return ViaVesaModes[i].mode_32b;
                default:
                    return 0xFFFF;
            }
        }
    return 0xFFFF;
}

/*
 *
 * ViaResolutionTable[i].PanelIndex is pBIOSInfo->PanelSize
 * pBIOSInfo->PanelIndex is the index to lcdTable.
 *
 */
static Bool
ViaPanelGetIndex(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    int i;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaPanelGetIndex\n"));

    pBIOSInfo->PanelIndex = VIA_BIOS_NUM_PANEL;

    if (pBIOSInfo->Panel->NativeModeIndex == VIA_PANEL_INVALID) {
        VIAGetPanelSize(pScrn);
        if (pBIOSInfo->Panel->NativeModeIndex == VIA_PANEL_INVALID) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "ViaPanelGetIndex: PanelSize not set.\n");
            return FALSE;
        }
    }

    if (!ViaGetResolutionIndex(pScrn, mode)) {
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Panel does not support this"
                   " resolution: %s\n", mode->name);
        return FALSE;
    }

    for (i = 0; ViaResolutionTable[i].Index != VIA_RES_INVALID; i++) {
        if (ViaResolutionTable[i].PanelIndex
            == pBIOSInfo->Panel->NativeModeIndex) {
            pBIOSInfo->Panel->NativeMode->Width = ViaResolutionTable[i].X;
            pBIOSInfo->Panel->NativeMode->Height = ViaResolutionTable[i].Y;
            break;
        }
    }

    if (ViaResolutionTable[i].Index == VIA_RES_INVALID) {
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaPanelGetIndex: Unable"
                   " to find matching PanelSize in ViaResolutionTable.\n");
        return FALSE;
    }

    if ((pBIOSInfo->Panel->NativeMode->Width != mode->CrtcHDisplay)
        || (pBIOSInfo->Panel->NativeMode->Height != mode->CrtcVDisplay)) {
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaPanelGetIndex: Non-native"
                   " resolutions are broken.\n");
        return FALSE;
    }

    for (i = 0; i < VIA_BIOS_NUM_PANEL; i++) {
        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaPanelGetIndex:"
                         "Match Debug: %d == %d)\n", pBIOSInfo->Panel->NativeModeIndex,
                         lcdTable[i].fpSize));
        if (lcdTable[i].fpSize == pBIOSInfo->Panel->NativeModeIndex) {
            int modeNum, tmp;

            modeNum = ViaGetVesaMode(pScrn, mode);
            if (modeNum == 0xFFFF) {
                xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "ViaPanelGetIndex: "
                           "Unable to determine matching VESA modenumber.\n");
                return FALSE;
            }

            tmp = 0x01 << (modeNum & 0xF);
            if ((CARD16) tmp & lcdTable[i].SuptMode[(modeNum >> 4)]) {
                pBIOSInfo->PanelIndex = i;
                DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaPanelGetIndex:"
                                 "index: %d (%dx%d)\n", pBIOSInfo->PanelIndex,
                                 pBIOSInfo->Panel->NativeMode->Width, pBIOSInfo->Panel->NativeMode->Height));
                return TRUE;
            }

            xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaPanelGetIndex: Unable"
                       " to match given mode with this PanelSize.\n");
            return FALSE;
        }
    }

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaPanelGetIndex: Unable"
               " to match PanelSize with an lcdTable entry.\n");
    return FALSE;
}

/*
 * adapted from nv and savage 
 */
static void
ViaModesMonitorFixup(ScrnInfoPtr pScrn, MonPtr monitorp, DisplayModePtr mode)
{
	/* These are all modes that the driver sets up
	 * so we can comfortably update the monitor
	 * configuration to work with them.
	 */

	if (monitorp->hsync[0].lo == 0)
		monitorp->hsync[0].lo = 31.50;
	if (monitorp->hsync[0].hi == 0)
		monitorp->hsync[0].hi = 37.90;
	if (monitorp->vrefresh[0].lo == 0)
		monitorp->vrefresh[0].lo = 50.00;
	if (monitorp->vrefresh[0].hi == 0)
		monitorp->vrefresh[0].hi = 70.00;

	if (!mode->HSync)
		mode->HSync = ((float) mode->Clock ) / ((float) mode->HTotal);
	if (!mode->VRefresh)
		mode->VRefresh = (1000.0 * ((float) mode->Clock)) /
		   ((float) (mode->HTotal * mode->VTotal));

	if (mode->HSync < monitorp->hsync[0].lo) {
		monitorp->hsync[0].lo = mode->HSync;
		DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModesMonitorFixup - Adjusted HSync.lo to %f\n", monitorp->hsync[0].lo));
	}
	if (mode->HSync > monitorp->hsync[0].hi) {
		monitorp->hsync[0].hi = mode->HSync;
		DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModesMonitorFixup - Adjusted HSync.hi to %f\n", monitorp->hsync[0].hi));
	}
	if (mode->VRefresh < monitorp->vrefresh[0].lo) {
		monitorp->vrefresh[0].lo = mode->VRefresh;
		DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModesMonitorFixup - Adjusted VRefresh.lo to %f\n", monitorp->vrefresh[0].lo));
	}
	if (mode->VRefresh > monitorp->vrefresh[0].hi) {
		monitorp->vrefresh[0].hi = mode->VRefresh;
		DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModesMonitorFixup - Adjusted VRefresh.hi to %f\n", monitorp->vrefresh[0].hi));
	}

	monitorp->nHsync = 1;
	monitorp->nVrefresh = 1;
}

/*
 * Stolen from xf86Config.c's addDefaultModes
 */
static void
ViaModesAttachHelper(ScrnInfoPtr pScrn, MonPtr monitorp, DisplayModePtr Modes)
{
    DisplayModePtr mode;
    DisplayModePtr last = monitorp->Last;
    int i;

    for (i = 0; Modes[i].name; i++) {
        mode = xnfalloc(sizeof(DisplayModeRec));
        memcpy(mode, &Modes[i], sizeof(DisplayModeRec));
        mode->name = xnfstrdup(Modes[i].name);
        if (last) {
            mode->prev = last;
            last->next = mode;
        } else {  /* this is the first mode */
            monitorp->Modes = mode;
            mode->prev = NULL;
        }
        last = mode;
        ViaModesMonitorFixup(pScrn, monitorp, mode);
    }
    monitorp->Last = last;
}

/*
 *
 */
void
ViaModesAttach(ScrnInfoPtr pScrn, MonPtr monitorp)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModesAttach\n"));

    if (pBIOSInfo->Panel->IsActive)
        ViaModesAttachHelper(pScrn, monitorp, ViaPanelModes);
    if (pBIOSInfo->TVActive && pBIOSInfo->TVModes)
        ViaModesAttachHelper(pScrn, monitorp, pBIOSInfo->TVModes);
}

/*
 *
 */
CARD32
ViaGetMemoryBandwidth(ScrnInfoPtr pScrn)
{
    VIAPtr pVia = VIAPTR(pScrn);

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaGetMemoryBandwidth. Memory type: %d\n", pVia->MemClk));

    switch (pVia->MemClk) {
        case VIA_MEM_SDR66:
        case VIA_MEM_SDR100:
        case VIA_MEM_SDR133:
            return VIA_BW_MIN;
        case VIA_MEM_DDR200:
            return VIA_BW_DDR200;
        case VIA_MEM_DDR266:
        case VIA_MEM_DDR333:
        case VIA_MEM_DDR400:
            return VIA_BW_DDR400;
        case VIA_MEM_DDR533:
        case VIA_MEM_DDR667:
            return VIA_BW_DDR667;
        default:
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "ViaBandwidthAllowed: Unknown memory type: %d\n", pVia->MemClk);
            return VIA_BW_MIN;
    }
}

static CARD32
ViaModeDotClockTranslate(ScrnInfoPtr pScrn, DisplayModePtr mode);

/*
 *
 */
ModeStatus
ViaValidMode(int scrnIndex, DisplayModePtr mode, Bool verbose, int flags)
{
    ScrnInfoPtr pScrn = xf86Screens[scrnIndex];
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;
    ModeStatus ret;
    CARD32 temp;

    if (pVia->pVbe)
        return MODE_OK;

    DEBUG(xf86DrvMsg(scrnIndex, X_INFO, "ViaValidMode: Validating %s (Clock: %d)\n",
                     mode->name, mode->Clock));

    if (mode->Flags & V_INTERLACE)
        return MODE_NO_INTERLACE;

    if (pVia->UseLegacyModeSwitch) {

        if (pVia->IsSecondary)
            ret = ViaSecondCRTCModeValid(pScrn, mode);
        else
            ret = ViaFirstCRTCModeValid(pScrn, mode);

        if (ret != MODE_OK)
            return ret;

        if (pBIOSInfo->TVActive) {
            ret = ViaTVModeValid(pScrn, mode);
            if (ret != MODE_OK) {
                xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                           "Mode \"%s\" is not supported by TV encoder.\n",
                           mode->name);
                return ret;
            }
        } else {
            if (pBIOSInfo->Panel->IsActive && !ViaPanelGetIndex(pScrn, mode))
                return MODE_BAD;
            else if (!ViaModeDotClockTranslate(pScrn, mode))
                return MODE_NOCLOCK;
        }

    } else {

        if (pBIOSInfo->FirstCRTC->IsActive) {
            ret = ViaFirstCRTCModeValid(pScrn, mode);
            if (ret != MODE_OK)
                return ret;
        }

        if (pBIOSInfo->SecondCRTC->IsActive) {
            ret = ViaSecondCRTCModeValid(pScrn, mode);
            if (ret != MODE_OK)
                return ret;
        }

        if (pBIOSInfo->Panel->IsActive) {
            ViaPanelModePtr nativeMode = pBIOSInfo->Panel->NativeMode;

            if (nativeMode->Width < mode->HDisplay
                || nativeMode->Height < mode->VDisplay)
                return MODE_PANEL;
        }

        if (!ViaModeDotClockTranslate(pScrn, mode))
            return MODE_NOCLOCK;
    }

    temp = mode->CrtcHDisplay * mode->CrtcVDisplay * mode->VRefresh
            * (pScrn->bitsPerPixel >> 3);
    if (pBIOSInfo->Bandwidth < temp) {
        xf86DrvMsg(scrnIndex, X_INFO,
                   "Required bandwidth is not available. (%u > %u)\n",
                   (unsigned)temp, (unsigned)pBIOSInfo->Bandwidth);
        return MODE_CLOCK_HIGH; /* since there is no MODE_BANDWIDTH */
    }

    return MODE_OK;
}

/*
 *
 * Some very common abstractions.
 *
 */

/*
 * Standard vga call really.
 * Needs to be called to reset the dotclock (after SR40:2/1 reset)
 */
static void
ViaSetUseExternalClock(vgaHWPtr hwp)
{
    CARD8 data;

    DEBUG(xf86DrvMsg(hwp->pScrn->scrnIndex, X_INFO,
                     "ViaSetUseExternalClock\n"));

    data = hwp->readMiscOut(hwp);
    hwp->writeMiscOut(hwp, data | 0x0C);
}

/*
 *
 */
static void
ViaSetDotclock(ScrnInfoPtr pScrn, CARD32 clock, int base, int probase)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);

    DEBUG(xf86DrvMsg(hwp->pScrn->scrnIndex, X_INFO,
                     "ViaSetDotclock to 0x%06x\n", (unsigned)clock));

    if ((pVia->Chipset == VIA_CLE266) || (pVia->Chipset == VIA_KM400)) {
        hwp->writeSeq(hwp, base, clock >> 8);
        hwp->writeSeq(hwp, base+1, clock & 0xFF);
    } else {  /* unichrome pro */
        union pllparams pll;
        int dtz, dr, dn, dm;
        pll.packed = clock;
        dtz = pll.params.dtz;
        dr  = pll.params.dr;
        dn  = pll.params.dn;
        dm  = pll.params.dm;

        /* The VX855 does not modify dm/dn, but earlier chipsets do. */
        if (pVia->Chipset != VIA_VX855) {
            dm -= 2;
            dn -= 2;
        }

        hwp->writeSeq(hwp, probase, dm & 0xff);
        hwp->writeSeq(hwp, probase+1, ((dm >> 8) & 0x03) | (dr << 2) | ((dtz & 1) << 7));
        hwp->writeSeq(hwp, probase+2, (dn & 0x7f) | ((dtz & 2) << 6));
    }
}

/*
 *
 */
static void
ViaSetPrimaryDotclock(ScrnInfoPtr pScrn, CARD32 clock)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);

    ViaSetDotclock(pScrn, clock, 0x46, 0x44);

    ViaSeqMask(hwp, 0x40, 0x02, 0x02);
    ViaSeqMask(hwp, 0x40, 0x00, 0x02);
}

/*
 *
 */
static void
ViaSetSecondaryDotclock(ScrnInfoPtr pScrn, CARD32 clock)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);

    ViaSetDotclock(pScrn, clock, 0x44, 0x4A);

    ViaSeqMask(hwp, 0x40, 0x04, 0x04);
    ViaSeqMask(hwp, 0x40, 0x00, 0x04);
}

/*
 *
 */
static void
ViaSetECKDotclock(ScrnInfoPtr pScrn, CARD32 clock)
{
    /* Does the non-pro chip have an ECK clock ? */  
    ViaSetDotclock(pScrn, clock, 0, 0x47);
}

/*
 * Broken, only does native mode decently. I (Luc) personally broke this.
 */
static void
VIASetLCDMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;
    VIALCDModeTableRec Table = lcdTable[pBIOSInfo->PanelIndex];
    CARD8 modeNum = 0;
    int resIdx;
    int port, offset, data;
    int i, j, misc;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VIASetLCDMode\n"));

    if (pBIOSInfo->Panel->NativeModeIndex == VIA_PANEL12X10)
        hwp->writeCrtc(hwp, 0x89, 0x07);

    /* LCD Expand Mode Y Scale Flag */
    pBIOSInfo->scaleY = FALSE;

    /* Set LCD InitTb Regs */
    if (pBIOSInfo->BusWidth == VIA_DI_12BIT) {
        if (pVia->IsSecondary)
            pBIOSInfo->Clock = Table.InitTb.LCDClk_12Bit;
        else {
            pBIOSInfo->Clock = Table.InitTb.VClk_12Bit;
            /* for some reason still to be defined this is neccessary */
            ViaSetSecondaryDotclock(pScrn, Table.InitTb.LCDClk_12Bit);
        }
    } else {
        if (pVia->IsSecondary)
            pBIOSInfo->Clock = Table.InitTb.LCDClk;
        else {
            pBIOSInfo->Clock = Table.InitTb.VClk;
            ViaSetSecondaryDotclock(pScrn, Table.InitTb.LCDClk);
        }

    }

    ViaSetUseExternalClock(hwp);

    for (i = 0; i < Table.InitTb.numEntry; i++) {
        port = Table.InitTb.port[i];
        offset = Table.InitTb.offset[i];
        data = Table.InitTb.data[i];
        ViaVgahwWrite(hwp, 0x300 + port, offset, 0x301 + port, data);
    }

    if ((mode->CrtcHDisplay != pBIOSInfo->Panel->NativeMode->Width)
        || (mode->CrtcVDisplay != pBIOSInfo->Panel->NativeMode->Height)) {
        VIALCDModeEntryPtr Main;
        VIALCDMPatchEntryPtr Patch1, Patch2;
        int numPatch1, numPatch2;

        resIdx = VIA_RES_INVALID;

        /* Find MxxxCtr & MxxxExp Index and
         * HWCursor Y Scale (PanelSize Y / Res. Y) */
        pBIOSInfo->resY = mode->CrtcVDisplay;
        switch (pBIOSInfo->ResolutionIndex) {
            case VIA_RES_640X480:
                resIdx = 0;
                break;
            case VIA_RES_800X600:
                resIdx = 1;
                break;
            case VIA_RES_1024X768:
                resIdx = 2;
                break;
            case VIA_RES_1152X864:
                resIdx = 3;
                break;
            case VIA_RES_1280X768:
            case VIA_RES_1280X960:
            case VIA_RES_1280X1024:
                if (pBIOSInfo->Panel->NativeModeIndex == VIA_PANEL12X10)
                    resIdx = VIA_RES_INVALID;
                else
                    resIdx = 4;
                break;
            default:
                resIdx = VIA_RES_INVALID;
                break;
        }

        if ((mode->CrtcHDisplay == 640) && (mode->CrtcVDisplay == 400))
            resIdx = 0;

        if (resIdx == VIA_RES_INVALID) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "VIASetLCDMode: Failed "
                       "to find a suitable Panel Size index.\n");
            return;
        }

        if (pBIOSInfo->Center) {
            Main = &(Table.MCtr[resIdx]);
            Patch1 = Table.MPatchDP1Ctr;
            numPatch1 = Table.numMPatchDP1Ctr;
            Patch2 = Table.MPatchDP2Ctr;
            numPatch2 = Table.numMPatchDP2Ctr;
        } else {  /* expand! */
            /* LCD Expand Mode Y Scale Flag */
            pBIOSInfo->scaleY = TRUE;
            Main = &(Table.MExp[resIdx]);
            Patch1 = Table.MPatchDP1Exp;
            numPatch1 = Table.numMPatchDP1Exp;
            Patch2 = Table.MPatchDP2Exp;
            numPatch2 = Table.numMPatchDP2Exp;
        }

        /* Set Main LCD Registers */
        for (i = 0; i < Main->numEntry; i++) {
            ViaVgahwWrite(hwp, 0x300 + Main->port[i], Main->offset[i],
                          0x301 + Main->port[i], Main->data[i]);
        }

        if (pBIOSInfo->BusWidth == VIA_DI_12BIT) {
            if (pVia->IsSecondary)
                pBIOSInfo->Clock = Main->LCDClk_12Bit;
            else
                pBIOSInfo->Clock = Main->VClk_12Bit;
        } else {
            if (pVia->IsSecondary)
                pBIOSInfo->Clock = Main->LCDClk;
            else
                pBIOSInfo->Clock = Main->VClk;
        }

        j = ViaGetVesaMode(pScrn, mode);
        if (j == 0xFFFF) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "VIASetLCDMode: "
                       "Unable to determine matching VESA modenumber.\n");
            return;
        }
        for (i = 0; i < modeFix.numEntry; i++) {
            if (modeFix.reqMode[i] == j) {
                modeNum = modeFix.fixMode[i];
                break;
            }
        }

        /* Set LCD Mode patch registers. */
        for (i = 0; i < numPatch2; i++, Patch2++) {
            if (Patch2->Mode == modeNum) {
                if (!pBIOSInfo->Center
                    && (mode->CrtcHDisplay == pBIOSInfo->Panel->NativeMode->Width))
                    pBIOSInfo->scaleY = FALSE;

                for (j = 0; j < Patch2->numEntry; j++) {
                    ViaVgahwWrite(hwp, 0x300 + Patch2->port[j],
                                  Patch2->offset[j], 0x301 + Patch2->port[j],
                                  Patch2->data[j]);
                }

                if (pBIOSInfo->BusWidth == VIA_DI_12BIT) {
                    if (pVia->IsSecondary)
                        pBIOSInfo->Clock = Patch2->LCDClk_12Bit;
                    else
                        pBIOSInfo->Clock = Patch2->VClk_12Bit;
                } else {
                    if (pVia->IsSecondary)
                        pBIOSInfo->Clock = Patch2->LCDClk;
                    else
                        pBIOSInfo->Clock = Patch2->VClk;
                }
                break;
            }
        }

        /* Set LCD Secondary Mode Patch registers. */
        if (pVia->IsSecondary) {
            for (i = 0; i < numPatch1; i++, Patch1++) {
                if (Patch1->Mode == modeNum) {
                    for (j = 0; j < Patch1->numEntry; j++) {
                        ViaVgahwWrite(hwp, 0x300 + Patch1->port[j],
                                      Patch1->offset[j],
                                      0x301 + Patch1->port[j], Patch1->data[j]);
                    }
                    break;
                }
            }
        }
    }

    /* LCD patch 3D5.02 */
    misc = hwp->readCrtc(hwp, 0x01);
    hwp->writeCrtc(hwp, 0x02, misc);

    /* Enable LCD */
    if (!pVia->IsSecondary) {
        /* CRT Display Source Bit 6 - 0: CRT, 1: LCD */
        ViaSeqMask(hwp, 0x16, 0x40, 0x40);

        /* Enable Simultaneous */
        if (pBIOSInfo->BusWidth == VIA_DI_12BIT) {
            hwp->writeCrtc(hwp, 0x6B, 0xA8);

            if ((pVia->Chipset == VIA_CLE266)
                && CLE266_REV_IS_AX(pVia->ChipRev))
                hwp->writeCrtc(hwp, 0x93, 0xB1);
            else
                hwp->writeCrtc(hwp, 0x93, 0xAF);
        } else {
            ViaCrtcMask(hwp, 0x6B, 0x08, 0x08);
            hwp->writeCrtc(hwp, 0x93, 0x00);
        }
        hwp->writeCrtc(hwp, 0x6A, 0x48);
    } else {
        /* CRT Display Source Bit 6 - 0: CRT, 1: LCD */
        ViaSeqMask(hwp, 0x16, 0x00, 0x40);

        /* Enable SAMM */
        if (pBIOSInfo->BusWidth == VIA_DI_12BIT) {
            ViaCrtcMask(hwp, 0x6B, 0x20, 0x20);
            if ((pVia->Chipset == VIA_CLE266)
                && CLE266_REV_IS_AX(pVia->ChipRev))
                hwp->writeCrtc(hwp, 0x93, 0xB1);
            else
                hwp->writeCrtc(hwp, 0x93, 0xAF);
        } else {
            hwp->writeCrtc(hwp, 0x6B, 0x00);
            hwp->writeCrtc(hwp, 0x93, 0x00);
        }
        hwp->writeCrtc(hwp, 0x6A, 0xC8);
    }
}

static CARD32
ViaComputeDotClock(unsigned clock)
{
    double fout, fref, err, minErr;
    CARD32 dr, dn, dm, maxdm, maxdn;
    CARD32 factual, best;

    fref = 14.31818e6;
    fout = (double)clock * 1.e3;

    factual = ~0;
    maxdm = 127;
    maxdn = 7;
    minErr = 1e10;
    best = 0;

    for (dr = 0; dr < 4; ++dr) {
        for (dn = (dr == 0) ? 2 : 1; dn <= maxdn; ++dn) {
            for (dm = 1; dm <= maxdm; ++dm) {
                factual = fref * dm;
                factual /= (dn << dr);
                err = fabs((double)factual / fout - 1.);
                if (err < minErr) {
                    minErr = err;
                    best = (dm & 127) | ((dn & 31) << 8) | (dr << 14);
                }
            }
        }
    }
    return best;
}

static CARD32
ViaComputeProDotClock(unsigned clock)
{
    double fvco, fout, fref, err, minErr;
    CARD32 dr = 0, dn, dm, maxdm, maxdn;
    CARD32 factual;
    union pllparams bestClock;
    
    fref = 14.318e6;
    fout = (double)clock * 1.e3;

    factual = ~0;
    maxdm = factual / 14318000U;
    minErr = 1.e10;
    bestClock.packed = 0U;

    do {
        fvco = fout * (1 << dr);
    } while (fvco < 300.e6 && dr++ < 8);

    if (dr == 8) {
        return 0;
    }

    if (clock < 30000)
        maxdn = 8;
    else if (clock < 45000)
        maxdn = 7;
    else if (clock < 170000)
        maxdn = 6;
    else
        maxdn = 5;

    for (dn = 2; dn < maxdn; ++dn) {
        for (dm = 2; dm < maxdm; ++dm) {
            factual = 14318000U * dm;
            factual /= dn << dr;
            if ((err = fabs((double)factual / fout - 1.)) < 0.005) {
                if (err < minErr) {
                    minErr = err;
                    bestClock.params.dtz = 1;
                    bestClock.params.dr = dr;
                    bestClock.params.dn = dn;
                    bestClock.params.dm = dm;
                }
            }
        }
    }

    return bestClock.packed;
}

/*
 *
 */
static CARD32
ViaModeDotClockTranslate(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIAPtr pVia = VIAPTR(pScrn);
    int i;

    if ((pVia->Chipset == VIA_CLE266) || (pVia->Chipset == VIA_KM400)) {
        CARD32 best1 = 0, best2;

        for (i = 0; ViaDotClocks[i].DotClock; i++)
            if (ViaDotClocks[i].DotClock == mode->Clock) {
                best1 = ViaDotClocks[i].UniChrome;
                break;
            }

        best2 = ViaComputeDotClock(mode->Clock);

        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                         "ViaComputeDotClock %d : %04x : %04x\n",
                         mode->Clock, (unsigned int)best1, (unsigned int)best2));                         

        return best2;
    } else {
        for (i = 0; ViaDotClocks[i].DotClock; i++)
            if (ViaDotClocks[i].DotClock == mode->Clock)
                return ViaDotClocks[i].UniChromePro.packed;
        return ViaComputeProDotClock(mode->Clock);
    }

    return 0;
}

/*
 *
 */
void
ViaModePrimaryLegacy(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModePrimaryLegacy\n"));
    DEBUG(ViaPrintMode(pScrn, mode));

    /* Turn off Screen */
    ViaCrtcMask(hwp, 0x17, 0x00, 0x80);

    /* Clean Second Path Status */
    hwp->writeCrtc(hwp, 0x6A, 0x00);
    hwp->writeCrtc(hwp, 0x6B, 0x00);
    hwp->writeCrtc(hwp, 0x6C, 0x00);
    hwp->writeCrtc(hwp, 0x93, 0x00);

    ViaCRTCInit(pScrn);
    ViaFirstCRTCSetMode(pScrn, mode);
    pBIOSInfo->Clock = ViaModeDotClockTranslate(pScrn, mode);
    pBIOSInfo->ClockExternal = FALSE;

    /* Enable MMIO & PCI burst (1 wait state) */
    ViaSeqMask(hwp, 0x1A, 0x06, 0x06);

    if (!pBIOSInfo->CrtActive)
        ViaCrtcMask(hwp, 0x36, 0x30, 0x30);
    else
        ViaSeqMask(hwp, 0x16, 0x00, 0x40);

    if (pBIOSInfo->Panel->IsActive && ViaPanelGetIndex(pScrn, mode)) {
        VIASetLCDMode(pScrn, mode);
        ViaLCDPower(pScrn, TRUE);
    } else if (pBIOSInfo->PanelPresent)
        ViaLCDPower(pScrn, FALSE);

    if (pBIOSInfo->TVActive) {
        /* Quick 'n dirty workaround for non-primary case until TVCrtcMode
         * is removed -- copy from clock handling code below */
        if ((pVia->Chipset == VIA_CLE266) && CLE266_REV_IS_AX(pVia->ChipRev))
            ViaSetPrimaryDotclock(pScrn, 0x471C);  /* CLE266Ax uses 2x XCLK */
        else if (pVia->Chipset != VIA_CLE266 && pVia->Chipset != VIA_KM400)
            ViaSetPrimaryDotclock(pScrn, 0x529001);
        else
            ViaSetPrimaryDotclock(pScrn, 0x871C);
        ViaSetUseExternalClock(hwp);

        ViaTVSetMode(pScrn, mode);
    } else
        ViaTVPower(pScrn, FALSE);

    ViaSetPrimaryFIFO(pScrn, mode);

    if (pBIOSInfo->ClockExternal) {
        if ((pVia->Chipset == VIA_CLE266) && CLE266_REV_IS_AX(pVia->ChipRev))
            ViaSetPrimaryDotclock(pScrn, 0x471C);  /* CLE266Ax uses 2x XCLK */
        else if (pVia->Chipset != VIA_CLE266 && pVia->Chipset != VIA_KM400)
            ViaSetPrimaryDotclock(pScrn, 0x529001);
        else
            ViaSetPrimaryDotclock(pScrn, 0x871C);
        if (pVia->Chipset == VIA_CLE266 || pVia->Chipset == VIA_KM400)
            ViaCrtcMask(hwp, 0x6B, 0x01, 0x01);
    } else {
        ViaSetPrimaryDotclock(pScrn, pBIOSInfo->Clock);
        ViaSetUseExternalClock(hwp);
        ViaCrtcMask(hwp, 0x6B, 0x00, 0x01);
    }

    /* Enable CRT Controller (3D5.17 Hardware Reset) */
    ViaCrtcMask(hwp, 0x17, 0x80, 0x80);

    hwp->disablePalette(hwp);
}

/*
 *
 */
void
ViaModeSecondaryLegacy(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModeSecondaryLegacy\n"));
    DEBUG(ViaPrintMode(pScrn, mode));

    /* Turn off Screen */
    ViaCrtcMask(hwp, 0x17, 0x00, 0x80);

    ViaSecondCRTCSetMode(pScrn, mode);

    if (pBIOSInfo->TVActive)
        ViaTVSetMode(pScrn, mode);

    /* CLE266A2 apparently doesn't like this */
    if (!(pVia->Chipset == VIA_CLE266 && pVia->ChipRev == 0x02))
        ViaCrtcMask(hwp, 0x6C, 0x00, 0x1E);

    if (pBIOSInfo->Panel->IsActive
        && (pBIOSInfo->PanelIndex != VIA_BIOS_NUM_PANEL)) {
        pBIOSInfo->SetDVI = TRUE;
        VIASetLCDMode(pScrn, mode);
        ViaLCDPower(pScrn, TRUE);
    } else if (pBIOSInfo->PanelPresent)
        ViaLCDPower(pScrn, FALSE);

    ViaSetSecondaryFIFO(pScrn, mode);

    ViaSetSecondaryDotclock(pScrn, pBIOSInfo->Clock);
    ViaSetUseExternalClock(hwp);

    ViaCrtcMask(hwp, 0x17, 0x80, 0x80);

    hwp->disablePalette(hwp);
}

/*
 *
 */
static void
ViaLCDPowerSequence(vgaHWPtr hwp, VIALCDPowerSeqRec Sequence)
{
    int i;

    for (i = 0; i < Sequence.numEntry; i++) {
        ViaVgahwMask(hwp, 0x300 + Sequence.port[i], Sequence.offset[i],
                     0x301 + Sequence.port[i], Sequence.data[i],
                     Sequence.mask[i]);
        usleep(Sequence.delay[i]);
    }
}

/*
 *
 */
void
ViaLCDPower(ScrnInfoPtr pScrn, Bool On)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;
    int i;

#ifdef HAVE_DEBUG
    if (On)
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaLCDPower: On.\n");
    else
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaLCDPower: Off.\n");
#endif

    /* Enable LCD */
    if (On)
        ViaCrtcMask(hwp, 0x6A, 0x08, 0x08);
    else
        ViaCrtcMask(hwp, 0x6A, 0x00, 0x08);

    if (pBIOSInfo->LCDPower) 
        pBIOSInfo->LCDPower(pScrn, On);

    /* Find Panel Size Index for PowerSeq Table */
    if (pVia->Chipset == VIA_CLE266) {
        if (pBIOSInfo->Panel->NativeModeIndex != VIA_PANEL_INVALID) {
            for (i = 0; i < NumPowerOn; i++) {
                if (lcdTable[pBIOSInfo->PanelIndex].powerSeq
                    == powerOn[i].powerSeq)
                    break;
            }
        } else
            i = 0;
    } else
        /* KM and K8M use PowerSeq Table index 2. */
        i = 2;

    usleep(1);
    if (On)
        ViaLCDPowerSequence(hwp, powerOn[i]);
    else
        ViaLCDPowerSequence(hwp, powerOff[i]);
    usleep(1);
}

void
ViaDFPPower(ScrnInfoPtr pScrn, Bool On)
{
#ifdef HAVE_DEBUG
    if (On)
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaDFPPower: On.\n");
    else
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaDFPPower: Off.\n");
#endif
    vgaHWPtr hwp = VGAHWPTR(pScrn);

    /* Display Channel Select */
    ViaCrtcMask(hwp, 0xD2, 0x30, 0x30);

    if (On)
        /* Power on TMDS */
        ViaCrtcMask(hwp, 0xD2, 0x00, 0x08);
    else
        /* Power off TMDS */
        ViaCrtcMask(hwp, 0xD2, 0x08, 0x08);
}


void
ViaModeFirstCRTC(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModeFirstCRTC\n");
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;

    /* Turn off Screen */
    ViaCrtcMask(hwp, 0x17, 0x00, 0x80);

    ViaFirstCRTCSetMode(pScrn, mode);
    pBIOSInfo->Clock = ViaModeDotClockTranslate(pScrn, mode);
    pBIOSInfo->ClockExternal = FALSE;

    /* Enable MMIO & PCI burst (1 wait state) */
    switch (pVia->Chipset) {
        case VIA_CLE266:
        case VIA_KM400:
        case VIA_K8M800:
        case VIA_PM800:
        case VIA_VM800:
            ViaSeqMask(hwp, 0x1A, 0x06, 0x06);
            break;
        default:
            ViaSeqMask(hwp, 0x1A, 0x0C, 0x0C);
            break;
    }

    ViaSetPrimaryFIFO(pScrn, mode);

    ViaSetPrimaryDotclock(pScrn, pBIOSInfo->Clock);
    ViaSetUseExternalClock(hwp);
    ViaCrtcMask(hwp, 0x6B, 0x00, 0x01);

    hwp->disablePalette(hwp);

    /* Turn on Screen */
    ViaCrtcMask(hwp, 0x17, 0x80, 0x80);
}

void
ViaModeSecondCRTC(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    DisplayModePtr nativeDisplayMode = pBIOSInfo->Panel->NativeDisplayMode;
    DisplayModePtr centeredMode = pBIOSInfo->Panel->CenteredMode;
    DisplayModePtr realMode = mode;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModeSecondCRTC\n"));

    if (pBIOSInfo->Panel->IsActive) {
        if (nativeDisplayMode) {
            ViaPanelScale(pScrn, mode->HDisplay, mode->VDisplay,
                          nativeDisplayMode->HDisplay,
                          nativeDisplayMode->VDisplay);
            if (!pBIOSInfo->Center
                && (mode->HDisplay < nativeDisplayMode->HDisplay
                    || mode->VDisplay < nativeDisplayMode->VDisplay)) {
                pBIOSInfo->Panel->Scale = TRUE;
                realMode = nativeDisplayMode;
            } else {
                pBIOSInfo->Panel->Scale = FALSE;
                ViaPanelCenterMode(centeredMode, nativeDisplayMode, mode);
                realMode = centeredMode;
                ViaPanelScaleDisable(pScrn);
            }
        }
    }

    ViaSecondCRTCSetMode(pScrn, realMode);
    ViaSetSecondaryFIFO(pScrn, realMode);
    pBIOSInfo->Clock = ViaModeDotClockTranslate(pScrn, realMode);

    /* Fix LCD scaling */
    ViaSecondCRTCHorizontalQWCount(pScrn, mode->CrtcHDisplay);

    pBIOSInfo->ClockExternal = FALSE;
    ViaSetSecondaryDotclock(pScrn, pBIOSInfo->Clock);
    ViaSetUseExternalClock(hwp);

    hwp->disablePalette(hwp);
}

void
ViaModeSet(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaModeSet\n"));

    ViaPrintMode(pScrn, mode);

    if (pBIOSInfo->SecondCRTC->IsActive) {
        ViaModeSecondCRTC(pScrn, mode);
        ViaSecondDisplayChannelEnable(pScrn);
    }

    if (pBIOSInfo->FirstCRTC->IsActive) {
        if (pBIOSInfo->CrtActive) {
            /* CRT on FirstCRTC */
            ViaDisplaySetStreamOnCRT(pScrn, TRUE);
            ViaDisplayEnableCRT(pScrn);
        }

        if (pBIOSInfo->DfpActive) {
            /* DFP on FirstCrtc */
            ViaDisplaySetStreamOnDFP(pScrn, TRUE);
            ViaDFPPower(pScrn, TRUE);
        }

        if (pBIOSInfo->TVActive) {
            /* TV on FirstCrtc */
            ViaDisplaySetStreamOnDVO(pScrn, pBIOSInfo->TVDIPort, TRUE);
            ViaDisplayEnableDVO(pScrn, pBIOSInfo->TVDIPort);
            ViaTVSetMode(pScrn, mode);
        }
        
        ViaModeFirstCRTC(pScrn, mode);
    } else {
        ViaDisplayDisableCRT(pScrn);
    }

    if (pBIOSInfo->Simultaneous->IsActive) {
        ViaDisplayEnableSimultaneous(pScrn);
    } else {
        ViaDisplayDisableSimultaneous(pScrn);
    }
}
