/*
 * Copyright 1992-2003 by Alan Hourihane, North Wales, UK.
 *
 * 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 Alan
 * Hourihane not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission.  Alan Hourihane makes no representations about the
 * suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * ALAN HOURIHANE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL ALAN HOURIHANE 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.
 *
 * Author:  Alan Hourihane, alanh@fairlite.demon.co.uk
 */

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

#include "xf86.h"
#include "xf86_OSproc.h"
#include "xf86Pci.h"

#include "vgaHW.h"

#include "trident.h"
#include "trident_regs.h"


static void
IsClearTV(ScrnInfoPtr pScrn)
{
    int vgaIOBase = VGAHWPTR(pScrn)->IOBase;
    TRIDENTPtr pTrident = TRIDENTPTR(pScrn);
    CARD8 temp;

    if (pTrident->frequency != 0) return;

    OUTB(vgaIOBase + 4, 0xC0);
    temp = INB(vgaIOBase + 5);
    if (temp & 0x80)
        pTrident->frequency = PAL;
    else
        pTrident->frequency = NTSC;
}

void
TGUISetClock(ScrnInfoPtr pScrn, int clock, CARD8 *a, CARD8 *b)
{
    TRIDENTPtr pTrident = TRIDENTPTR(pScrn);
    int powerup[4] = { 1, 2, 4, 8 };
    int clock_diff = 750;
    int freq, ffreq;
    int m, n, k;
    int p, q, r, s;
    int endn, endm, endk, startk;

    p = q = r = s = 0;

    IsClearTV(pScrn);

    if (pTrident->NewClockCode) {
        endn = 255;
        endm = 63;
        endk = 2;
        if (clock >= 100000) startk = 0; else
            if (clock >=  50000) startk = 1; else
                startk = 2;
    } else {
        endn = 121;
        endm = 31;
        endk = 1;
        if (clock > 50000) startk = 1; else
            startk = 0;
    }

    freq = clock;

    for (k = startk; k <= endk; k++) {
        for (n = 0; n <= endn; n++) {
            for (m = 1; m <= endm; m++) {
                ffreq = ((((n + 8) * pTrident->frequency) /
                            ((m + 2) * powerup[k])) * 1000);
                if ((ffreq > freq - clock_diff) &&
                    (ffreq < freq + clock_diff)) {
                    /*
                     * It seems that the 9440 docs have this STRICT
                     * limitation, although most 9440 boards seem to
                     * cope. 96xx/Cyber chips don't need this limit
                     * so, I'm gonna remove it and it allows lower
                     * clocks < 25.175 too!
                     */
#ifdef STRICT
                    if ((n + 8) * 100 / (m + 2) < 978 &&
                        (n + 8) * 100 / (m + 2) > 349) {
#endif
                        clock_diff = (freq > ffreq) ?
                                        freq - ffreq : ffreq - freq;
                        p = n; q = m; r = k; s = ffreq;
#ifdef STRICT
                    }
#endif
                }
            }
        }
    }

    if (s == 0) {
        FatalError("Unable to set programmable clock.\n"
                "Frequency %d is not a valid clock.\n"
                "Please modify XF86Config for a new clock.\n",
                freq);
    }

    if (pTrident->NewClockCode) {
        /* N is all 8bits */
        *a = p;
        /* M is first 6bits, with K last 2bits */
        *b = (q & 0x3F) | (r << 6);
    } else {
        /* N is first 7bits, first M bit is 8th bit */
        *a = ((1 & q) << 7) | p;
        /* first 4bits are rest of M, 1bit for K value */
        *b = (((q & 0xFE) >> 1) | (r << 4));
    }
    xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, 3,
                   "Found Clock %6.2f n=%i m=%i k=%i\n",
                   clock/1000., p, q, r);
}

void
TridentFindClock(ScrnInfoPtr pScrn, int clock)
{
    TRIDENTPtr pTrident = TRIDENTPTR(pScrn);

    pTrident->MUX = FALSE;
#ifdef READOUT
    pTrident->DontSetClock = FALSE;
#endif
    pTrident->currentClock = clock;

    if (pTrident->IsCyber) {
        Bool LCDActive;
#ifdef READOUT
        Bool ShadowModeActive;
        Bool HStretch;
        Bool VStretch;
#endif
        OUTB(0x3CE, FPConfig);
        LCDActive = (INB(0x3CF) & 0x10);
#ifdef READOUT
        OUTB(0x3CE, HorStretch);
        HStretch = (INB(0x3CF) & 0x01);
        OUTB(0x3CE, VertStretch);
        VStretch = (INB(0x3CF) & 0x01);

        if (!(VStretch || HStretch) && LCDActive) {
            CARD8 temp;
            temp = INB(0x3C8);
            temp = INB(0x3C6);
            temp = INB(0x3C6);
            temp = INB(0x3C6);
            temp = INB(0x3C6);
            pTrident->MUX = ((INB(0x3C6) & 0x20) == 0x20);
            temp = INB(0x3C8);
            pTrident->DontSetClock = TRUE;
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                        "Keeping Clock for LCD Mode\n");
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                        "MUX is %s\n",
                        pTrident->MUX ? "on" : "off");
            return;
        } else
#endif
        {
            if (pTrident->lcdMode != 0xff && LCDActive)
                pTrident->currentClock = clock =
                                        LCD[pTrident->lcdMode].clock;
        }

    } 
#ifndef READOUT
    if (pTrident->Chipset != BLADEXP &&
        clock > pTrident->MUXThreshold)
        pTrident->MUX = TRUE;
    else  
        pTrident->MUX = FALSE;
#endif
}

float
CalculateMCLK(ScrnInfoPtr pScrn)
{
    int vgaIOBase = VGAHWPTR(pScrn)->IOBase;
    TRIDENTPtr pTrident = TRIDENTPTR(pScrn);
    int a, b;
    int m, n, k;
    float freq = 0.0;
    int powerup[4] = { 1, 2, 4, 8 };
    CARD8 temp;

    if (pTrident->HasSGRAM) {
        OUTB(vgaIOBase + 4, 0x28);
        switch(INB(vgaIOBase + 5) & 0x07) {
        case 0:
            freq = 60;
            break;
        case 1:
            freq = 78;
            break;
        case 2:
            freq = 90;
            break;
        case 3:
            freq = 120;
            break;
        case 4:
            freq = 66;
            break;
        case 5:
            freq = 83;
            break;
        case 6:
            freq = 100;
            break;
        case 7:
            freq = 132;
            break;
        }
    } else {
        OUTB(0x3C4, NewMode1);
        temp = INB(0x3C5);

        OUTB(0x3C5, 0xC2);
        if (!Is3Dchip) {
            a = INB(0x43C6);
            b = INB(0x43C7);
        } else {
            OUTB(0x3C4, 0x16);
            a = INB(0x3C5);
            OUTB(0x3C4, 0x17);
            b = INB(0x3C5);
        }

        OUTB(0x3C4, NewMode1);
        OUTB(0x3C5, temp);

        IsClearTV(pScrn);

        if (pTrident->NewClockCode) {
            m = b & 0x3F;
            n = a;
            k = (b & 0xC0) >> 6;
        } else {
            m = (a & 0x07);
            k = (b & 0x02) >> 1;
            n = ((a & 0xF8) >> 3) | ((b&0x01) << 5);
        }

        freq = ((n + 8) * pTrident->frequency) /
                ((m + 2) * powerup[k]);
    }

    return (freq);
}

void
TGUISetMCLK(ScrnInfoPtr pScrn, int clock, CARD8 *a, CARD8 *b)
{
    TRIDENTPtr pTrident = TRIDENTPTR(pScrn);
    int powerup[4] = { 1,2,4,8 };
    int clock_diff = 750;
    int freq, ffreq;
    int m,n,k;
    int p, q, r, s; 
    int startn, endn;
    int endm, endk;

    p = q = r = s = 0;

    IsClearTV(pScrn);

    if (pTrident->NewClockCode) {
        startn = 64;
        endn = 255;
        endm = 63;
        endk = 3;
    } else {
        startn = 0;
        endn = 121;
        endm = 31;
        endk = 1;
    }

    freq = clock;

    if (!pTrident->HasSGRAM) {
        for (k = 0; k <= endk; k++) {
            for (n = startn; n <= endn; n++) {
                for (m = 1;m <= endm; m++) {
                    ffreq = ((((n + 8) * pTrident->frequency) /
                            ((m + 2) * powerup[k])) * 1000);
                    if ((ffreq > freq - clock_diff) &&
                        (ffreq < freq + clock_diff)) {
                        clock_diff = (freq > ffreq) ?
                                        freq - ffreq : ffreq - freq;
                        p = n; q = m; r = k; s = ffreq;
                    }
                }
            }
        }

        if (s == 0) {
            FatalError("Unable to set memory clock.\n"
                        "Frequency %d is not a valid clock.\n"
                        "Please modify XF86Config for a new clock.\n",
                        freq);
        }

        if (pTrident->NewClockCode) {
            /* N is all 8bits */
            *a = p;
            /* M is first 6bits, with K last 2bits */
            *b = (q & 0x3F) | (r << 6);
        }
        else {
            /* N is first 7bits, first M bit is 8th bit */
            *a = ((1 & q) << 7) | p;
            /* first 4bits are rest of M, 1bit for K value */
            *b = (((q & 0xFE) >> 1) | (r << 4));
        }
    }
}
