/* $XFree86: xc/programs/Xserver/hw/xfree86/vga256/drivers/ati/ativga.c,v 1.1.2.1 1998/02/01 16:42:07 robin Exp $ */
/*
 * Copyright 1997,1998 by Marc Aurele La France (TSI @ UQV), tsi@ualberta.ca
 *
 * 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 "atidepth.h"
#include "atiio.h"
#include "atimono.h"
#include "ativga.h"
#include "atividmem.h"

/*
 * ATIVGASave --
 *
 * This function is called to save the VGA portion of the current video state.
 */
void
ATIVGASave(ATIHWPtr save)
{
    int Index;

    /* Save miscellaneous output register */
    save->std.MiscOutReg = inb(R_GENMO);
    ATISetVGAIOBase(save->std.MiscOutReg);

    /* Save sequencer registers */
    for (Index = 0;  Index < NumberOf(save->std.Sequencer);  Index++)
        save->std.Sequencer[Index] = GetReg(SEQX, Index);

    /* Save CRTC registers */
    for (Index = 0;  Index < NumberOf(save->std.CRTC);  Index++)
        save->std.CRTC[Index] = GetReg(CRTX(vgaIOBase), Index);

    /* Save attribute controller registers */
    for (Index = 0;  Index < NumberOf(save->std.Attribute);  Index++)
    {
        (void) inb(GENS1(vgaIOBase));   /* Reset flip-flop */
        save->std.Attribute[Index] = GetReg(ATTRX, Index);
    }

    /* Save graphics controller registers */
    for (Index = 0;  Index < NumberOf(save->std.Graphics);  Index++)
        save->std.Graphics[Index] = GetReg(GRAX, Index);
}

/*
 * ATIVGAInit --
 *
 * This function fills in the VGA portion of an ATIHWRec.
 */
void
ATIVGAInit(DisplayModePtr mode)
{
    int Index, VDisplay;

    if (!mode)
    {
        /*
         * An ATIHWRec structure has been allocated and cleared.  Fill in the
         * VGA data common to all modes generated by the server.
         */

        /* Initialize sequencer register values */
        ATINewHWPtr->std.Sequencer[0] = 0x03U;
        if (ATIUsing1bppModes)
            ATINewHWPtr->std.Sequencer[2] = 0x01U << BIT_PLANE;
        else
            ATINewHWPtr->std.Sequencer[2] = 0x0FU;
        if (ATIUsingPlanarModes)
            ATINewHWPtr->std.Sequencer[4] = 0x06U;
        else if (ATIAdapter == ATI_ADAPTER_VGA)
            ATINewHWPtr->std.Sequencer[4] = 0x0EU;
        else
            ATINewHWPtr->std.Sequencer[4] = 0x0AU;

        /* Initialize CRTC register values */
        if (!ATIUsingPlanarModes &&
            ((ATIChip >= ATI_CHIP_264CT) ||
             ((ATIChip <= ATI_CHIP_18800) && (ATIvideoRam == 256))))
            ATINewHWPtr->std.CRTC[19] = vga256InfoRec.displayWidth >> 3;
        else
            ATINewHWPtr->std.CRTC[19] = vga256InfoRec.displayWidth >> 4;
        ATINewHWPtr->std.CRTC[24] = 0xFFU;

        /* Initialize attribute controller register values */
        if (ATIUsing1bppModes)
        {
            for (Index = 0;  Index < 16;  Index++)
                if (Index & (0x01U << BIT_PLANE))
                    ATINewHWPtr->std.Attribute[Index] = MONO_WHITE;
                else
                    ATINewHWPtr->std.Attribute[Index] = MONO_BLACK;
            ATINewHWPtr->std.Attribute[16] = 0x01U;
            ATINewHWPtr->std.Attribute[17] = MONO_OVERSCAN;
        }
        else
        {
            for (Index = 1;  Index < 16;  Index++)
                ATINewHWPtr->std.Attribute[Index] = Index;
            if (ATIUsingPlanarModes)
                ATINewHWPtr->std.Attribute[16] = 0x81U;
            else
            {
                if (ATIAdapter == ATI_ADAPTER_VGA)
                    ATINewHWPtr->std.Attribute[16] = 0x41U;
                else
                    ATINewHWPtr->std.Attribute[16] = 0x01U;
                ATINewHWPtr->std.Attribute[17] = 0xFFU;
            }
        }
        ATINewHWPtr->std.Attribute[18] = 0x0FU;

        /* Initialize graphics controller register values */
        if (ATIUsing1bppModes)
            ATINewHWPtr->std.Graphics[4] = BIT_PLANE;
        else if (ATIUsingPlanarModes)
            ATINewHWPtr->std.Graphics[5] = 0x02U;
        else if (ATIChip >= ATI_CHIP_264CT)
            ATINewHWPtr->std.Graphics[5] = 0x40U;
        if (ATIUsingSmallApertures && (ATIChip >= ATI_CHIP_264VTB))
            ATINewHWPtr->std.Graphics[6] = 0x01U;       /* 128kB aperture */
        else
            ATINewHWPtr->std.Graphics[6] = 0x05U;       /* 64kB aperture */
        ATINewHWPtr->std.Graphics[7] = 0x0FU;
        ATINewHWPtr->std.Graphics[8] = 0xFFU;
    }
    else
    {
        /* Adjust mode timings as needed, then fill in VGA data */
        if (!mode->CrtcHAdjusted)
        {
            mode->CrtcHAdjusted = TRUE;
            mode->CrtcHDisplay = (mode->CrtcHDisplay >> 3) - 1;
            mode->CrtcHSyncStart >>= 3;
            mode->CrtcHSyncEnd >>= 3;
            mode->CrtcHTotal = (mode->CrtcHTotal >> 3) - 5;
        }
        if (!ATIUsingPlanarModes && (ATIAdapter == ATI_ADAPTER_VGA))
            ATINewHWPtr->std.CRTC[23] = 0xC3U;
        else
            ATINewHWPtr->std.CRTC[23] = 0xE3U;

        /*
         * It is necessary to redo all vertical adjustments that have been
         * made.  Doing so fixes a minor bug in doublescanned modes.
         */
        mode->CrtcVDisplay = mode->VDisplay;
        mode->CrtcVSyncStart = mode->VSyncStart;
        mode->CrtcVSyncEnd = mode->VSyncEnd;
        mode->CrtcVTotal = mode->VTotal;

        /* Adjust double scanned modes */
        if (mode->Flags & V_DBLSCAN)
        {
            mode->CrtcVDisplay <<= 1;
            mode->CrtcVSyncStart <<= 1;
            mode->CrtcVSyncEnd <<= 1;
            mode->CrtcVTotal <<= 1;
        }

        /*
         * The following two adjustments to the vertical timings don't apply to
         * generic VGA, because ATIValidMode has already weeded out the
         * affected modes.
         */
        if ((mode->Flags & V_INTERLACE) && (ATIChip < ATI_CHIP_264CT))
        {
            mode->CrtcVDisplay >>= 1;
            mode->CrtcVSyncStart >>= 1;
            mode->CrtcVSyncEnd >>= 1;
            mode->CrtcVTotal >>= 1;
        }
        if (mode->CrtcVTotal > 1024)
        {
            /* Use vertical doubling bit */
            ATINewHWPtr->std.CRTC[23] |= 0x04U;
            mode->CrtcVDisplay >>= 1;
            mode->CrtcVSyncStart >>= 1;
            mode->CrtcVSyncEnd >>= 1;
            mode->CrtcVTotal >>= 1;
        }

        mode->CrtcVDisplay--;
        mode->CrtcVTotal -= 2;
        mode->CrtcVAdjusted = TRUE;

        /* Setup miscellanous output register value */
        ATINewHWPtr->std.MiscOutReg = 0x23U;
        if ((mode->Flags & (V_PHSYNC | V_NHSYNC)) &&
            (mode->Flags & (V_PVSYNC | V_NVSYNC)))
        {
            if (mode->Flags & V_NHSYNC)
                ATINewHWPtr->std.MiscOutReg |= 0x40U;
            if (mode->Flags & V_NVSYNC)
                ATINewHWPtr->std.MiscOutReg |= 0x80U;
        }
        else
        {
            VDisplay = mode->VDisplay;
            if (mode->Flags & V_DBLSCAN)
                VDisplay *= 2;
    
            if (VDisplay < 400)
                ATINewHWPtr->std.MiscOutReg |= 0x80U;       /* +hsync -vsync */
            else if (VDisplay < 480)
                ATINewHWPtr->std.MiscOutReg |= 0x40U;       /* -hsync +vsync */
            else if (VDisplay < 768)
                ATINewHWPtr->std.MiscOutReg |= 0xC0U;       /* -hsync -vsync */
        }
    
        /* Setup sequencer register values */
        if (mode->Flags & V_CLKDIV2)
            ATINewHWPtr->std.Sequencer[1] = 0x09U;
        else
            ATINewHWPtr->std.Sequencer[1] = 0x01U;
    
        /* Setup CRTC register values */
        ATINewHWPtr->std.CRTC[0] = mode->CrtcHTotal;
        ATINewHWPtr->std.CRTC[1] = mode->CrtcHDisplay;
        ATINewHWPtr->std.CRTC[2] = mode->CrtcHSyncStart - 1;
        ATINewHWPtr->std.CRTC[3] = (mode->CrtcHSyncEnd & 0x1FU) | 0x80U;
        Index = ((mode->CrtcHSkew << 2) + 0x10U) & ~0x1FU;
        if (Index < 0x0080)
            ATINewHWPtr->std.CRTC[3] |= Index;
        ATINewHWPtr->std.CRTC[4] = mode->CrtcHSyncStart;
        ATINewHWPtr->std.CRTC[5] = ((mode->CrtcHSyncEnd & 0x20U) << 2) |
                                   ((mode->CrtcHSyncEnd & 0x1FU)     );
        ATINewHWPtr->std.CRTC[6] = mode->CrtcVTotal & 0xFFU;
        ATINewHWPtr->std.CRTC[7] = ((mode->CrtcVTotal & 0x0100U) >> 8) |
                                   ((mode->CrtcVDisplay & 0x0100U) >> 7) |
                                   ((mode->CrtcVSyncStart & 0x0100U) >> 6) |
                                   ((mode->CrtcVSyncStart & 0x0100U) >> 5) |
                                   0x10U |
                                   ((mode->CrtcVTotal & 0x0200U) >> 4) |
                                   ((mode->CrtcVDisplay & 0x0200U) >> 3) |
                                   ((mode->CrtcVSyncStart & 0x0200U) >> 2);
        ATINewHWPtr->std.CRTC[9] = ((mode->CrtcVSyncStart & 0x0200U) >> 4) |
                                   0x40U;
        /*
         * For doublescanned modes, setting bits 0-4 to 1 (which amounts to
         * oring in 1) appears to produce more consistent results than simply
         * setting bit 7.
         */
        if (mode->Flags & V_DBLSCAN)
            ATINewHWPtr->std.CRTC[9] |= 0x01U;
        ATINewHWPtr->std.CRTC[16] = mode->CrtcVSyncStart & 0xFFU;
        ATINewHWPtr->std.CRTC[17] = (mode->CrtcVSyncEnd & 0x0FU) | 0x20U;
        ATINewHWPtr->std.CRTC[18] = mode->CrtcVDisplay & 0xFFU;
        ATINewHWPtr->std.CRTC[21] = (mode->CrtcVSyncStart & 0xFFU);
        ATINewHWPtr->std.CRTC[22] = (mode->CrtcVSyncEnd + 1) & 0xFFU;
    }
}

/*
 * ATIVGARestore --
 *
 * This function is called to load the VGA portion of a video mode.
 */
void
ATIVGARestore(ATIHWPtr restore)
{
    int Index;

    /* Load miscellaneous output register */
    outb(GENMO, restore->std.MiscOutReg);

    /* Load sequencer in reverse index order;  this also ends its reset */
    for (Index = NumberOf(restore->std.Sequencer);  --Index >= 0;  )
        PutReg(SEQX, Index, restore->std.Sequencer[Index]);

    /* Load CRTC */
    for (Index = 0;  Index < NumberOf(restore->std.CRTC);  Index++)
        PutReg(CRTX(vgaIOBase), Index, restore->std.CRTC[Index]);

    /* Load attribute controller */
    (void) inb(GENS1(vgaIOBase));       /* Reset flip-flop */
    for (Index = 0;  Index < NumberOf(restore->std.Attribute);  Index++)
    {
        outb(ATTRX, Index);
        outb(ATTRX, restore->std.Attribute[Index]);
    }

    /* Load graphics controller */
    for (Index = 0;  Index < NumberOf(restore->std.Graphics);  Index++)
        PutReg(GRAX, Index, restore->std.Graphics[Index]);
}
