/*
 * Copyright © 1999 Keith Packard
 *
 * 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 Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD 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.
 */
/* $XFree86: xc/programs/Xserver/hw/tinyx/trident/trident.c,v 1.4 2004/10/26 22:14:58 tsi Exp $ */
/*
 * Copyright (c) 2004 by The XFree86 Project, 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, 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:
 *
 *   1.  Redistributions of source code must retain the above copyright
 *       notice, this list of conditions, and the following disclaimer.
 *
 *   2.  Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer
 *       in the documentation and/or other materials provided with the
 *       distribution, and in the same place and form as other copyright,
 *       license and disclaimer information.
 *
 *   3.  The end-user documentation included with the redistribution,
 *       if any, must include the following acknowledgment: "This product
 *       includes software developed by The XFree86 Project, Inc
 *       (http://www.xfree86.org/) and its contributors", in the same
 *       place and form as other third-party acknowledgments.  Alternately,
 *       this acknowledgment may appear in the software itself, in the
 *       same form and location as other such third-party acknowledgments.
 *
 *   4.  Except as contained in this notice, the name of The XFree86
 *       Project, Inc shall not be used in advertising or otherwise to
 *       promote the sale, use or other dealings in this Software without
 *       prior written authorization from The XFree86 Project, Inc.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE XFREE86 PROJECT, INC OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef __GNUC__
#define inline __inline__
#endif
#include "trident.h"
#ifdef __GLIBC__
#include <sys/io.h>
#else
#include <asm/io.h>
#endif

#undef TRI_DEBUG

int	trident_clk = 0;
int	trident_mclk = 0;

#define CLOCK 14318	/* KHz */
#define CLK_N(a,b)	(a & 0xff)
#define CLK_M(a,b)	((b) & 0x3f)
#define CLK_K(a,b)	(((b) >> 6) & 3)
#define CLK_FREQ(a,b)	(((CLK_N(a,b) + 8) * CLOCK) / ((CLK_M(a,b)+2) << CLK_K(a,b)))

static Bool
tridentCardInit (KdCardInfo *card)
{
    TridentCardInfo	*tridentc;
    CARD8		r39;

    tridentc = (TridentCardInfo *) xalloc (sizeof (TridentCardInfo));
    if (!tridentc)
	return FALSE;
    
    iopl (3);
    tridentc->cop_base = (CARD8 *) KdMapDevice (TRIDENT_COP_BASE(card),
						TRIDENT_COP_SIZE(card));
    
    if (tridentc->cop_base)
    {
	KdSetMappedMode (TRIDENT_COP_BASE(card),
			 TRIDENT_COP_SIZE(card),
			 KD_MAPPED_MODE_REGISTERS);
    }
    tridentc->cop = (Cop *) (tridentc->cop_base + TRIDENT_COP_OFF(card));
    tridentc->mmio = FALSE;
    r39 = tridentReadIndex (tridentc, 0x3d4, 0x39);
    if (r39 & 1)
    {
	tridentc->mmio = TRUE;
	r39 = tridentReadIndex (tridentc, 0x3d4, 0x39);
	if ((r39 & 1) == 0)
	{
	    ErrorF ("Trident: inconsisent IO mapping values\n");
	    return FALSE;
	}
    }
    
#ifdef VESA
    if (!vesaInitialize (card, &tridentc->vesa))
#else
    if (!fbdevInitialize (card, &tridentc->fb))
#endif
    {
	xfree (tridentc);
	return FALSE;
    }
    
#ifdef USE_PCI
    tridentc->window = (CARD32 *) (tridentc->cop_base + 0x10000);
#else
    tridentc->window = 0;
#endif
    card->driver = tridentc;
    
    return TRUE;
}

static Bool
tridentScreenInit (KdScreenInfo *screen)
{
    TridentCardInfo	*tridentc = screen->card->driver;
    TridentScreenInfo	*tridents;
    int			screen_size, memory;

    tridents = (TridentScreenInfo *) xalloc (sizeof (TridentScreenInfo));
    if (!tridents)
	return FALSE;
    memset (tridents, '\0', sizeof (TridentScreenInfo));
#ifdef VESA
    if (!vesaScreenInitialize (screen, &tridents->vesa))
#else
    if (!fbdevScreenInitialize (screen, &tridents->fbdev))
#endif
    {
	xfree (tridents);
	return FALSE;
    }
    if (!tridentc->cop)
	screen->dumb = TRUE;
#ifdef VESA
    if (tridents->vesa.mapping != VESA_LINEAR)
	screen->dumb = TRUE;
    tridents->screen = tridents->vesa.fb;
    memory = tridents->vesa.fb_size;
#else
    tridents->screen = tridentc->fb.fb;
    memory = (2048 + 512) * 1024;
#endif
    screen_size = screen->fb[0].byteStride * screen->height;
    if (tridents->screen && memory >= screen_size + 2048)
    {
	memory -= 2048;
	tridents->cursor_base = tridents->screen + memory - 2048;
    }
    else
	tridents->cursor_base = 0;
    memory -= screen_size;
    if (memory > screen->fb[0].byteStride)
    {
	tridents->off_screen = tridents->screen + screen_size;
	tridents->off_screen_size = memory;
    }
    else
    {
	tridents->off_screen = 0;
	tridents->off_screen_size = 0;
    }
    screen->driver = tridents;
    return TRUE;
}

static Bool
tridentInitScreen (ScreenPtr pScreen)
{
#ifdef VESA
    return vesaInitScreen (pScreen);
#else
    return fbdevInitScreen (pScreen);
#endif
}

static Bool
tridentFinishInitScreen (ScreenPtr pScreen)
{
#ifdef VESA
    return vesaFinishInitScreen (pScreen);
#endif
}

CARD8
tridentReadIndex (TridentCardInfo *tridentc, CARD16 port, CARD8 index)
{
    CARD8   value;
    
    if (tridentc->mmio)
    {
	tridentc->cop_base[port] = index;
	value = tridentc->cop_base[port+1];
    }
    else
    {
	outb (index, port);
	value = inb (port+1);
    }
    return value;
}

void
tridentWriteIndex (TridentCardInfo *tridentc, CARD16 port, CARD8 index, CARD8 value)
{
    if (tridentc->mmio)
    {
	tridentc->cop_base[port] = index;
	tridentc->cop_base[port+1] = value;
    }
    else
    {
	outb (index, port);
	outb (value, port+1);
    }
}

static CARD8
tridentReadReg (TridentCardInfo *tridentc, CARD16 port)
{
    CARD8   value;
    
    if (tridentc->mmio)
    {
	value = tridentc->cop_base[port];
    }
    else
    {
	value = inb (port);
    }
    return value;
}

static void
tridentWriteReg (TridentCardInfo *tridentc, CARD16 port, CARD8 value)
{
    if (tridentc->mmio)
    {
	tridentc->cop_base[port] = value;
    }
    else
    {
	outb (value, port);
    }
}


static void
tridentPause (void)
{
    struct timeval  tv;

    tv.tv_sec = 0;
    tv.tv_usec = 50 * 1000;
    select (1, 0, 0, 0, &tv);
}

static void
tridentPreserve (KdCardInfo *card)
{
    TridentCardInfo	*tridentc = card->driver;

#ifdef VESA
    vesaPreserve(card);
#else
    fbdevPreserve (card);
#endif
    tridentPause ();
    tridentc->save.reg_3c4_0e = tridentReadIndex (tridentc, 0x3c4, 0x0e);
    tridentc->save.reg_3d4_36 = tridentReadIndex (tridentc, 0x3d4, 0x36);
    tridentc->save.reg_3d4_39 = tridentReadIndex (tridentc, 0x3d4, 0x39);
    tridentc->save.reg_3d4_62 = tridentReadIndex (tridentc, 0x3d4, 0x62);
    tridentc->save.reg_3ce_21 = tridentReadIndex (tridentc, 0x3ce, 0x21);
    tridentc->save.reg_3c2 = tridentReadReg (tridentc, 0x3cc);
    tridentc->save.reg_3c4_16 = tridentReadIndex (tridentc, 0x3c4, 0x16);
    tridentc->save.reg_3c4_17 = tridentReadIndex (tridentc, 0x3c4, 0x17);
    tridentc->save.reg_3c4_18 = tridentReadIndex (tridentc, 0x3c4, 0x18);
    tridentc->save.reg_3c4_19 = tridentReadIndex (tridentc, 0x3c4, 0x19);
    ErrorF ("clk low 0x%x high 0x%x freq %d\n",
	    tridentc->save.reg_3c4_18,
	    tridentc->save.reg_3c4_19,
	    CLK_FREQ(tridentc->save.reg_3c4_18,
		     tridentc->save.reg_3c4_19));
#ifdef TRI_DEBUG
    fprintf (stderr, "3c4 0e: %02x\n", tridentc->save.reg_3c4_0e);
    fprintf (stderr, "3d4 36: %02x\n", tridentc->save.reg_3d4_36);
    fprintf (stderr, "3d4 39: %02x\n", tridentc->save.reg_3d4_39);
    fprintf (stderr, "3d4 62: %02x\n", tridentc->save.reg_3d4_62);
    fprintf (stderr, "3ce 21: %02x\n", tridentc->save.reg_3ce_21);
    fflush (stderr);
#endif
    tridentPause ();
}

static void
tridentSetCLK(int clock, CARD8 *a, CARD8 *b)
{
    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;

    startn = 64;
    endn = 255;
    endm = 63;
    endk = 3;

    freq = clock;

    for (k=0;k<=endk;k++)
	for (n=startn;n<=endn;n++)
	    for (m=1;m<=endm;m++)
	    {
		ffreq = ( ( ((n + 8) * CLOCK) / ((m + 2) * powerup[k]) ));
		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;
		}
	    }

    ErrorF ("ffreq %d clock %d\n", s, clock);
    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);
    }

    /* N is all 8bits */
    *a = p;
    /* M is first 6bits, with K last 2bits */
    *b = (q & 0x3F) | (r << 6);
}

static void
tridentSetMCLK(int clock, CARD8 *a, CARD8 *b)
{
    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;

    startn = 64;
    endn = 255;
    endm = 63;
    endk = 3;

    freq = clock;

    for (k=0;k<=endk;k++)
	for (n=startn;n<=endn;n++)
	    for (m=1;m<=endm;m++) {
		ffreq = ((((n+8)*CLOCK)/((m+2)*powerup[k])));
		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);
    }

    /* N is all 8bits */
    *a = p;
    /* M is first 6bits, with K last 2bits */
    *b = (q & 0x3F) | (r << 6);
}

void
tridentSetMMIO (TridentCardInfo *tridentc)
{
    int	tries;
    CARD8   v;

#ifdef TRI_DEBUG
    fprintf (stderr, "Set MMIO\n");
#endif
    /* enable config port writes */
    for (tries = 0; tries < 3; tries++)
    {
	/* enable direct read when GE busy, enable PCI retries */
	tridentWriteIndex (tridentc, 0x3d4, 0x62,
			   tridentc->save.reg_3d4_62 | 0x70);
	/* make sure the chip is in new mode */
	tridentReadIndex (tridentc, 0x3c4, 0xb);
	/* enable access to upper registers */
	tridentWriteIndex (tridentc, 0x3c4, 0xe, 
			   tridentc->save.reg_3c4_0e | 0x80);
	v = tridentReadIndex (tridentc, 0x3c4, 0xe);
	if (!(v & 0x80))
	{
	    fprintf (stderr, "Trident GE not enabled 0x%x\n", v);
	    continue;
	}
	/* enable screen */
	tridentWriteIndex (tridentc, 0x3ce, 0x21, 0x80);
#ifdef USE_PCI
	/* enable burst r/w, enable memory mapped ports */
	tridentWriteIndex (tridentc, 0x3d4, 0x39, 7);
	tridentc->mmio = TRUE;
	/* reset GE, enable GE, set GE to pci 1 */
	tridentWriteIndex (tridentc, 0x3d4, 0x36, 0x90);
#else
	/* enable burst r/w, disable memory mapped ports */
	tridentWriteIndex (tridentc, 0x3d4, 0x39, 0x6);
	/* reset GE, enable GE, set GE to 0xbff00 */
	tridentWriteIndex (tridentc, 0x3d4, 0x36, 0x92);
#endif
	/* set clock */
	if (trident_clk)
	{
	    CARD8   a, b;

	    a = tridentReadIndex (tridentc, 0x3c4, 0x18);
	    b = tridentReadIndex (tridentc, 0x3c4, 0x19);
	    ErrorF ("old clock 0x%x 0x%x %d\n", 
		    a, b, CLK_FREQ(a,b));
	    tridentSetCLK (trident_clk, &a, &b);
	    ErrorF ("clk %d-> 0x%x 0x%x %d\n", trident_clk, a, b,
		    CLK_FREQ(a,b));
#if 1
	    tridentWriteIndex (tridentc, 0x3c4, 0x18, a);
	    tridentWriteIndex (tridentc, 0x3c4, 0x19, b);
#endif
	}
	if (trident_mclk)
	{
	    CARD8   a, b;

	    tridentSetMCLK (trident_mclk, &a, &b);
	    ErrorF ("mclk %d -> 0x%x 0x%x\n", trident_mclk, a, b);
#if 0
	    tridentWriteIndex (tridentc, 0x3c4, 0x16, a);
	    tridentWriteIndex (tridentc, 0x3c4, 0x17, b);
#endif
	}
	if (trident_clk || trident_mclk)
	{
	    CARD8   mode;

	    mode = tridentReadReg (tridentc, 0x3cc);
	    ErrorF ("old mode 0x%x\n", mode);
	    mode = (mode & 0xf3) | 0x08;
	    ErrorF ("new mode 0x%x\n", mode);
#if 1
	    tridentWriteReg (tridentc, 0x3c2, mode);
#endif
	}
#ifdef TRI_DEBUG
	fprintf (stderr, "0x36: 0x%02x\n",
		 tridentReadIndex (tridentc, 0x3d4, 0x36));
#endif
	if (tridentc->cop->status != 0xffffffff)
	    break;
    }
#ifdef TRI_DEBUG
    fprintf (stderr, "COP status 0x%x\n", tridentc->cop->status);
#endif
    if (tridentc->cop->status == 0xffffffff)
	FatalError ("Trident COP not visible\n");
}

static void
tridentResetMMIO (TridentCardInfo *tridentc)
{
#ifdef TRI_DEBUG
    fprintf (stderr, "Reset MMIO\n");
#endif
    tridentPause ();
#if 0
    tridentWriteIndex (tridentc, 0x3c4, 0x16, tridentc->save.reg_3c4_16);
    tridentWriteIndex (tridentc, 0x3c4, 0x17, tridentc->save.reg_3c4_17);
#endif
    tridentWriteIndex (tridentc, 0x3c4, 0x18, tridentc->save.reg_3c4_18);
    tridentWriteIndex (tridentc, 0x3c4, 0x19, tridentc->save.reg_3c4_19);
    tridentWriteReg (tridentc, 0x3c2, tridentc->save.reg_3c2);
    tridentPause ();
    tridentWriteIndex (tridentc, 0x3ce, 0x21, tridentc->save.reg_3ce_21);
    tridentPause ();
    tridentWriteIndex (tridentc, 0x3d4, 0x62, tridentc->save.reg_3d4_62);
    tridentWriteIndex (tridentc, 0x3d4, 0x39, tridentc->save.reg_3d4_39);
    tridentc->mmio = FALSE;
    tridentWriteIndex (tridentc, 0x3d4, 0x36, tridentc->save.reg_3d4_36);
    tridentWriteIndex (tridentc, 0x3c4, 0x0e, tridentc->save.reg_3c4_0e);
    tridentPause ();
}

static Bool
tridentEnable (ScreenPtr pScreen)
{
    KdScreenPriv(pScreen);
    TridentCardInfo	*tridentc = pScreenPriv->card->driver;

#ifdef VESA
    if (!vesaEnable (pScreen))
	return FALSE;
#else
    if (!fbdevEnable (pScreen))
	return FALSE;
#endif
    tridentSetMMIO (tridentc);
    return TRUE;
}

static void
tridentDisable (ScreenPtr pScreen)
{
#ifdef VESA
    vesaDisable (pScreen);
#else
    fbdevDisable (pScreen);
#endif
}

const CARD8	tridentDPMSModes[4] = {
    0x80,	    /* KD_DPMS_NORMAL */
    0x8c,	    /* KD_DPMS_STANDBY */
#ifdef USE_ALL_DPMS_MODES
    0xb0,	    /* KD_DPMS_SUSPEND */
    0xbc,	    /* KD_DPMS_POWERDOWN */
#else
    0x8c,	    /* KD_DPMS_STANDBY */
    0x8c,	    /* KD_DPMS_STANDBY */
#endif
};

static Bool
tridentDPMS (ScreenPtr pScreen, int mode)
{
    KdScreenPriv(pScreen);
    TridentCardInfo	*tridentc = pScreenPriv->card->driver;

    tridentWriteIndex (tridentc, 0x3ce, 0x21, tridentDPMSModes[mode]);
    tridentPause ();
    return TRUE;
}

static void
tridentRestore (KdCardInfo *card)
{
    TridentCardInfo	*tridentc = card->driver;

    tridentResetMMIO (tridentc);
#ifdef VESA
    vesaRestore (card);
#else
    fbdevRestore (card);
#endif
}

static void
tridentScreenFini (KdScreenInfo *screen)
{
    TridentScreenInfo	*tridents = (TridentScreenInfo *) screen->driver;

#ifdef VESA
    vesaScreenFini (screen);
#endif
    xfree (tridents);
    screen->driver = 0;
}

static void
tridentCardFini (KdCardInfo *card)
{
    TridentCardInfo	*tridentc = card->driver;

    if (tridentc->cop_base)
    {
	KdUnmapDevice ((void *) tridentc->cop_base, TRIDENT_COP_SIZE(card));
	KdResetMappedMode (TRIDENT_COP_BASE(card),
			   TRIDENT_COP_SIZE(card),
			   KD_MAPPED_MODE_REGISTERS);
    }
#ifdef VESA
    vesaCardFini (card);
#else
    fbdevCardFini (card);
#endif
}

KdCardFuncs	tridentFuncs = {
    tridentCardInit,	    /* cardinit */
    tridentScreenInit,	    /* scrinit */
    tridentInitScreen,	    /* initScreen */
    tridentPreserve,	    /* preserve */
    tridentEnable,	    /* enable */
    tridentDPMS,	    /* dpms */
    tridentDisable,	    /* disable */
    tridentRestore,	    /* restore */
    tridentScreenFini,	    /* scrfini */
    tridentCardFini,	    /* cardfini */
    
    tridentCursorInit,	    /* initCursor */
    tridentCursorEnable,    /* enableCursor */
    tridentCursorDisable,   /* disableCursor */
    tridentCursorFini,	    /* finiCursor */
    tridentRecolorCursor,   /* recolorCursor */
    
    tridentDrawInit,        /* initAccel */
    tridentDrawEnable,      /* enableAccel */
    tridentDrawSync,	    /* syncAccel */
    tridentDrawDisable,     /* disableAccel */
    tridentDrawFini,        /* finiAccel */
    
#ifdef VESA
    vesaGetColors,    	    /* getColors */
    vesaPutColors,	    /* putColors */
#else
    fbdevGetColors,    	    /* getColors */
    fbdevPutColors,	    /* putColors */
#endif
    tridentFinishInitScreen /* finishInitScreen */
};
