
/*
Copyright (C) 1994-1999 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:

The above copyright notice and this permission notice 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, FIT-
NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
XFREE86 PROJECT 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.

Except as contained in this notice, the name of the XFree86 Project 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.
*/
#include "config.h"

#include <unistd.h>

#include "xf86.h"
#include "xf86DDC.h"
#include "vbe.h"

/* Needed by the Shadow Framebuffer */
#include "shadowfb.h"

/*
 * s3v_driver.c
 * Port to 4.0 design level
 *
 * S3 ViRGE driver
 *
 * 10/98 - 3/99 Kevin Brosius
 * based largely on the SVGA ViRGE driver from 3.3.3x,
 * Started 09/03/97 by S. Marineau
 *
 *
 */


/* Most xf86 commons are already in s3v.h */
#include "s3v.h"
#include "s3v_pciids.h"

#include "globals.h"
#include <X11/extensions/dpmsconst.h>

#ifndef USE_INT10
#define USE_INT10 0
#endif

/*
 * Internals
 */
static void S3VEnableMmio(ScrnInfoPtr pScrn);
static void S3VDisableMmio(ScrnInfoPtr pScrn);

/*
 * Forward definitions for the functions that make up the driver.
 */

/* Mandatory functions */
static const OptionInfoRec * S3VAvailableOptions(int chipid, int busid);
static void S3VIdentify(int flags);
static Bool S3VProbe(DriverPtr drv, int flags);
static Bool S3VPreInit(ScrnInfoPtr pScrn, int flags);

static Bool S3VEnterVT(ScrnInfoPtr pScrn);
static void S3VLeaveVT(ScrnInfoPtr pScrn);
static void S3VSave (ScrnInfoPtr pScrn);
static void S3VWriteMode (ScrnInfoPtr pScrn, vgaRegPtr, S3VRegPtr);

static void S3VSaveSTREAMS(ScrnInfoPtr pScrn, unsigned int *streams);
static void S3VRestoreSTREAMS(ScrnInfoPtr pScrn, unsigned int *streams);
static void S3VDisableSTREAMS(ScrnInfoPtr pScrn);
static Bool S3VScreenInit(ScreenPtr pScreen, int argc, char **argv);
static int S3VInternalScreenInit(ScrnInfoPtr pScrn, ScreenPtr pScreen);
static void S3VPrintRegs(ScrnInfoPtr);
static ModeStatus S3VValidMode(ScrnInfoPtr pScrn, DisplayModePtr mode, Bool verbose, int flags);

static Bool S3VMapMem(ScrnInfoPtr pScrn);
static void S3VUnmapMem(ScrnInfoPtr pScrn);
static Bool S3VModeInit(ScrnInfoPtr pScrn, DisplayModePtr mode);
static Bool S3VCloseScreen(ScreenPtr pScreen);
static Bool S3VSaveScreen(ScreenPtr pScreen, int mode);
static void S3VInitSTREAMS(ScrnInfoPtr pScrn, unsigned int *streams, DisplayModePtr mode);
static void S3VLoadPalette(ScrnInfoPtr pScrn, int numColors, int *indices, LOCO *colors, VisualPtr pVisual);

static void S3VDisplayPowerManagementSet(ScrnInfoPtr pScrn,
					 int PowerManagementMode,
					 int flags);
static Bool S3Vddc1(ScrnInfoPtr pScrn);
static Bool S3Vddc2(ScrnInfoPtr pScrn);

static unsigned int S3Vddc1Read(ScrnInfoPtr pScrn);
static void S3VProbeDDC(ScrnInfoPtr pScrn, int index);

/*
 * This is intentionally screen-independent.  It indicates the binding
 * choice made in the first PreInit.
 */
static int pix24bpp = 0;

#define S3VIRGE_NAME "S3VIRGE"
#define S3VIRGE_DRIVER_NAME "s3virge"
#define S3VIRGE_VERSION_NAME PACKAGE_VERSION
#define S3VIRGE_VERSION_MAJOR   PACKAGE_VERSION_MAJOR
#define S3VIRGE_VERSION_MINOR   PACKAGE_VERSION_MINOR
#define S3VIRGE_PATCHLEVEL      PACKAGE_VERSION_PATCHLEVEL
#define S3VIRGE_DRIVER_VERSION ((S3VIRGE_VERSION_MAJOR << 24) | \
				(S3VIRGE_VERSION_MINOR << 16) | \
				S3VIRGE_PATCHLEVEL)

/*
 * This contains the functions needed by the server after loading the
 * driver module.  It must be supplied, and gets added the driver list by
 * the Module Setup function in the dynamic case.  In the static case a
 * reference to this is compiled in, and this requires that the name of
 * this DriverRec be an upper-case version of the driver name.
 */

_X_EXPORT DriverRec S3VIRGE =
{
    S3VIRGE_DRIVER_VERSION,
    S3VIRGE_DRIVER_NAME,
    S3VIdentify,
    S3VProbe,
    S3VAvailableOptions,
    NULL,
    0
};


/* Supported chipsets */
static SymTabRec S3VChipsets[] = {
				  	/* base (86C325) */
  { PCI_CHIP_VIRGE,			"virge" },
  { PCI_CHIP_VIRGE,			"86C325" },
  					/* VX (86C988) */
  { PCI_CHIP_VIRGE_VX,		"virge vx" },
  { PCI_CHIP_VIRGE_VX,		"86C988" },
  					/* DX (86C375) GX (86C385) */
  { PCI_CHIP_VIRGE_DXGX,	"virge dx" },
  { PCI_CHIP_VIRGE_DXGX,	"virge gx" },
  { PCI_CHIP_VIRGE_DXGX,	"86C375" },
  { PCI_CHIP_VIRGE_DXGX,	"86C385" },
                                        /* GX2 (86C357) */
  { PCI_CHIP_VIRGE_GX2,		"virge gx2" },
  { PCI_CHIP_VIRGE_GX2,		"86C357" },
  					/* MX (86C260) */
  { PCI_CHIP_VIRGE_MX,		"virge mx" },
  { PCI_CHIP_VIRGE_MX,		"86C260" },
  					/* MX+ (86C280) */
  { PCI_CHIP_VIRGE_MXP,		"virge mx+" },
  { PCI_CHIP_VIRGE_MXP,		"86C280" },
  					/* Trio3D (86C365) */
  { PCI_CHIP_Trio3D,		"trio 3d" },
  { PCI_CHIP_Trio3D,		"86C365" },
  					/* Trio3D/2x (86C362/86C368) */
  { PCI_CHIP_Trio3D_2X,		"trio 3d/2x" },
  { PCI_CHIP_Trio3D_2X,		"86C362" },
  { PCI_CHIP_Trio3D_2X,		"86C368" },
  {-1,			NULL }
};

static PciChipsets S3VPciChipsets[] = {
  /* numChipset,		PciID,			Resource */
  { PCI_CHIP_VIRGE,      PCI_CHIP_VIRGE,     	RES_SHARED_VGA },
  { PCI_CHIP_VIRGE_VX,   PCI_CHIP_VIRGE_VX,     RES_SHARED_VGA },
  { PCI_CHIP_VIRGE_DXGX, PCI_CHIP_VIRGE_DXGX,	RES_SHARED_VGA },
  { PCI_CHIP_VIRGE_GX2,  PCI_CHIP_VIRGE_GX2,  	RES_SHARED_VGA },
  { PCI_CHIP_VIRGE_MX,   PCI_CHIP_VIRGE_MX,   	RES_SHARED_VGA },
  { PCI_CHIP_VIRGE_MXP,  PCI_CHIP_VIRGE_MXP,  	RES_SHARED_VGA },
  { PCI_CHIP_Trio3D,     PCI_CHIP_Trio3D,  	RES_SHARED_VGA },
  { PCI_CHIP_Trio3D_2X,  PCI_CHIP_Trio3D_2X,  	RES_SHARED_VGA },
  { -1,                       -1,   		RES_UNDEFINED }
};

typedef enum {
   OPTION_SLOW_EDODRAM,
   OPTION_SLOW_DRAM,
   OPTION_FAST_DRAM,
   OPTION_FPM_VRAM,
   OPTION_PCI_BURST,
   OPTION_FIFO_CONSERV,
   OPTION_FIFO_MODERATE,
   OPTION_FIFO_AGGRESSIVE,
   OPTION_PCI_RETRY,
   OPTION_NOACCEL,
   OPTION_EARLY_RAS_PRECHARGE,
   OPTION_LATE_RAS_PRECHARGE,
   OPTION_LCD_CENTER,
   OPTION_LCDCLOCK,
   OPTION_MCLK,
   OPTION_REFCLK,
   OPTION_SHOWCACHE,
   OPTION_SWCURSOR,
   OPTION_HWCURSOR,
   OPTION_SHADOW_FB,
   OPTION_ROTATE,
   OPTION_FB_DRAW,
   OPTION_MX_CR3A_FIX,
   OPTION_XVIDEO
} S3VOpts;

static const OptionInfoRec S3VOptions[] =
{
  /*    int token, const char* name, OptionValueType type,
	ValueUnion value, Bool found.
  */
   { OPTION_SLOW_EDODRAM, 	"slow_edodram",	OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_SLOW_DRAM, 		"slow_dram",	OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_FAST_DRAM, 		"fast_dram",	OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_FPM_VRAM, 		"fpm_vram",	OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_PCI_BURST, 		"pci_burst",	OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_FIFO_CONSERV, 	"fifo_conservative", OPTV_BOOLEAN, {0}, FALSE },
   { OPTION_FIFO_MODERATE, 	"fifo_moderate", OPTV_BOOLEAN, 	{0}, FALSE },
   { OPTION_FIFO_AGGRESSIVE, 	"fifo_aggressive", OPTV_BOOLEAN, {0}, FALSE },
   { OPTION_PCI_RETRY, 		"pci_retry",	OPTV_BOOLEAN,	{0}, FALSE  },
   { OPTION_NOACCEL, 		"NoAccel",	OPTV_BOOLEAN,	{0}, FALSE  },
   { OPTION_EARLY_RAS_PRECHARGE, "early_ras_precharge",	OPTV_BOOLEAN, {0}, FALSE },
   { OPTION_LATE_RAS_PRECHARGE, "late_ras_precharge", OPTV_BOOLEAN, {0}, FALSE },
   { OPTION_LCD_CENTER, 	"lcd_center", 	OPTV_BOOLEAN, 	{0}, FALSE },
   { OPTION_LCDCLOCK, 		"set_lcdclk", 	OPTV_INTEGER, 	{0}, FALSE },
   { OPTION_MCLK, 		"set_mclk", 	OPTV_FREQ, 	{0}, FALSE },
   { OPTION_REFCLK, 		"set_refclk", 	OPTV_FREQ, 	{0}, FALSE },
   { OPTION_SHOWCACHE,		"show_cache",   OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_HWCURSOR,		"HWCursor",     OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_SWCURSOR,		"SWCursor",     OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_SHADOW_FB,          "ShadowFB",	OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_ROTATE, 	        "Rotate",	OPTV_ANYSTR,	{0}, FALSE },
   { OPTION_MX_CR3A_FIX,        "mxcr3afix",	OPTV_BOOLEAN,	{0}, FALSE },
   { OPTION_XVIDEO,             "XVideo",	OPTV_BOOLEAN,	{0}, FALSE },
   {-1, NULL, OPTV_NONE,	{0}, FALSE}
};

static MODULESETUPPROTO(s3virgeSetup);

static XF86ModuleVersionInfo S3VVersRec =
{
    "s3virge",
    MODULEVENDORSTRING,
    MODINFOSTRING1,
    MODINFOSTRING2,
    XORG_VERSION_CURRENT,
    S3VIRGE_VERSION_MAJOR, S3VIRGE_VERSION_MINOR, S3VIRGE_PATCHLEVEL,
    ABI_CLASS_VIDEODRV,		       /* This is a video driver */
    ABI_VIDEODRV_VERSION,
    MOD_CLASS_VIDEODRV,
    {0, 0, 0, 0}
};


/*
 * This is the module init data for XFree86 modules.
 *
 * Its name has to be the driver name followed by ModuleData.
 */
_X_EXPORT XF86ModuleData s3virgeModuleData = {
    &S3VVersRec,
    s3virgeSetup,
    NULL
};

static void*
s3virgeSetup(void *module, void *opts, int *errmaj, int *errmin)
{
    static Bool setupDone = FALSE;

    if (!setupDone) {
	setupDone = TRUE;
	xf86AddDriver(&S3VIRGE, module, 0);

	/*
	 * The return value must be non-NULL on success even though there
	 * is no TearDownProc.
	 */
	return (void*) 1;
    } else {
	if (errmaj)
	    *errmaj = LDR_ONCEONLY;
	return NULL;
    }
}

static unsigned char *find_bios_string(S3VPtr ps3v, int BIOSbase,
                                       const char *match1, const char *match2)
{
#define BIOS_BSIZE 0x10000
#define BIOS_BASE  0xc0000

   static unsigned char bios[BIOS_BSIZE];
   static int init=0;
   int i,j,l1,l2;

   if (!init) {
      init = 1;
      if (pci_device_read_rom(ps3v->PciInfo, bios))
	return NULL;
      if ((bios[0] != 0x55) || (bios[1] != 0xaa))
	 return NULL;
   }
   if (match1 == NULL)
      return NULL;

   l1 = strlen(match1);
   if (match2 != NULL)
      l2 = strlen(match2);
   else	/* for compiler-warnings */
      l2 = 0;

   for (i=0; i<BIOS_BSIZE-l1; i++)
       if (bios[i] == match1[0] && !memcmp(&bios[i],match1,l1)) {
	 if (match2 == NULL)
	    return &bios[i+l1];
	 else
	    for(j=i+l1; (j<BIOS_BSIZE-l2) && bios[j]; j++)
	       if (bios[j] == match2[0] && !memcmp(&bios[j],match2,l2))
		   return &bios[j+l2];
       }

   return NULL;
}


static Bool
S3VGetRec(ScrnInfoPtr pScrn)
{
    PVERB5("	S3VGetRec\n");
    /*
     * Allocate an 'Chip'Rec, and hook it into pScrn->driverPrivate.
     * pScrn->driverPrivate is initialised to NULL, so we can check if
     * the allocation has already been done.
     */
    if (pScrn->driverPrivate != NULL)
	return TRUE;

    pScrn->driverPrivate = XNFcallocarray(sizeof(S3VRec), 1);
    /* Initialise it here when needed (or possible) */

    return TRUE;
}

static void
S3VFreeRec(ScrnInfoPtr pScrn)
{
    PVERB5("	S3VFreeRec\n");
    if (pScrn->driverPrivate == NULL)
	return;
    free(pScrn->driverPrivate);
    pScrn->driverPrivate = NULL;
}

static const OptionInfoRec *
S3VAvailableOptions(int chipid, int busid)
{
    return S3VOptions;
}

static void
S3VIdentify(int flags)
{
    PVERB5("	S3VIdentify\n");
    xf86PrintChipsets(S3VIRGE_NAME,
	"driver (version " S3VIRGE_VERSION_NAME ") for S3 ViRGE chipsets",
	S3VChipsets);
}


static Bool
S3VProbe(DriverPtr drv, int flags)
{
    int i;
    GDevPtr *devSections;
    int *usedChips;
    int numDevSections;
    int numUsed;
    Bool foundScreen = FALSE;

    PVERB5("	S3VProbe begin\n");

    if ((numDevSections = xf86MatchDevice(S3VIRGE_DRIVER_NAME,
					  &devSections)) <= 0) {
	/*
	 * There's no matching device section in the config file, so quit
	 * now.
	 */
	return FALSE;
    }

    numUsed = xf86MatchPciInstances(S3VIRGE_NAME, PCI_S3_VENDOR_ID,
				    S3VChipsets, S3VPciChipsets, devSections,
				    numDevSections, drv, &usedChips);

    /* Free it since we don't need that list after this */
    free(devSections);
    if (numUsed <= 0)
	return FALSE;

    if (flags & PROBE_DETECT)
	foundScreen = TRUE;
    else for (i = 0; i < numUsed; i++) {
	/* Allocate a ScrnInfoRec and claim the slot */
	ScrnInfoPtr pScrn = NULL;
	if ((pScrn = xf86ConfigPciEntity(pScrn,0,usedChips[i],
					       S3VPciChipsets,NULL,NULL, NULL,
					       NULL,NULL))) {
	    /* Fill in what we can of the ScrnInfoRec */
	    pScrn->driverVersion = S3VIRGE_DRIVER_VERSION;
	    pScrn->driverName	 = S3VIRGE_DRIVER_NAME;
	    pScrn->name		 = S3VIRGE_NAME;
	    pScrn->Probe	 = S3VProbe;
	    pScrn->PreInit	 = S3VPreInit;
	    pScrn->ScreenInit	 = S3VScreenInit;
	    pScrn->SwitchMode	 = S3VSwitchMode;
	    pScrn->AdjustFrame	 = S3VAdjustFrame;
	    pScrn->EnterVT	 = S3VEnterVT;
	    pScrn->LeaveVT	 = S3VLeaveVT;
	    pScrn->FreeScreen	 = NULL; /*S3VFreeScreen;*/
	    pScrn->ValidMode	 = S3VValidMode;
	    foundScreen = TRUE;
	}
    }
    free(usedChips);
    PVERB5("	S3VProbe end\n");
    return foundScreen;
}


/* Mandatory */
static Bool
S3VPreInit(ScrnInfoPtr pScrn, int flags)
{
    EntityInfoPtr pEnt;
    S3VPtr ps3v;
    MessageType from;
    int i;
    double real;
    ClockRangePtr clockRanges;
    const char *s;

    unsigned char config1, config2, m, n, n1, n2, cr66 = 0;
    int mclk;

    vgaHWPtr hwp;
    int vgaCRIndex, vgaCRReg, vgaIOBase;

    PVERB5("	S3VPreInit 1\n");

    if (flags & PROBE_DETECT) {
	  S3VProbeDDC( pScrn, xf86GetEntityInfo(pScrn->entityList[0])->index );
      return TRUE;
      }

    /*
     * Note: This function is only called once at server startup, and
     * not at the start of each server generation.  This means that
     * only things that are persistent across server generations can
     * be initialised here.  xf86Screens[] is (pScrn is a pointer to one
     * of these).  Privates allocated using xf86AllocateScrnInfoPrivateIndex()
     * are too, and should be used for data that must persist across
     * server generations.
     *
     * Per-generation data should be allocated with
     * AllocateScreenPrivateIndex() from the ScreenInit() function.
     */

    /* The vgahw module should be loaded here when needed */

    if (!xf86LoadSubModule(pScrn, "vgahw"))
	return FALSE;

    /*
     * Allocate a vgaHWRec
     */
    if (!vgaHWGetHWRec(pScrn))
	return FALSE;
    vgaHWSetStdFuncs(VGAHWPTR(pScrn));

    /* Set pScrn->monitor */
    pScrn->monitor = pScrn->confScreen->monitor;

    /*
     * The first thing we should figure out is the depth, bpp, etc.
     * We support both 24bpp and 32bpp layouts, so indicate that.
     */
    if (!xf86SetDepthBpp(pScrn, 0, 0, 0, Support24bppFb | Support32bppFb |
				SupportConvert32to24 | PreferConvert32to24)) {
	return FALSE;
    } else {
	/* Check that the returned depth is one we support */
	switch (pScrn->depth) {
	case 8:
	case 15:
	case 16:
	case 24:
	    /* OK */
	    break;
	default:
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		       "Given depth (%d) is not supported by this driver\n",
		       pScrn->depth);
	    return FALSE;
	}
    }
    xf86PrintDepthBpp(pScrn);

    /* Get the depth24 pixmap format */
    if (pScrn->depth == 24 && pix24bpp == 0)
	pix24bpp = xf86GetBppFromDepth(pScrn, 24);

    /*
     * This must happen after pScrn->display has been set because
     * xf86SetWeight references it.
     */
    if (pScrn->depth > 8) {
	/* The defaults are OK for us */
	rgb zeros = {0, 0, 0};

	if (!xf86SetWeight(pScrn, zeros, zeros)) {
	    return FALSE;
	} else {
	    /* XXX check that weight returned is supported */
            ;
        }
    }

    if (!xf86SetDefaultVisual(pScrn, -1)) {
	return FALSE;
    } else {		/* editme - from MGA, does ViRGE? */
	/* We don't currently support DirectColor at > 8bpp */
	if (pScrn->depth > 8 && pScrn->defaultVisual != TrueColor) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Given default visual"
		       " (%s) is not supported at depth %d\n",
		       xf86GetVisualName(pScrn->defaultVisual), pScrn->depth);
	    return FALSE;
	}
    }

    /* We use a programmable clock */
    pScrn->progClock = TRUE;

    /* Allocate the S3VRec driverPrivate */
    if (!S3VGetRec(pScrn)) {
	return FALSE;
    }
    ps3v = S3VPTR(pScrn);

    /* Collect all of the relevant option flags (fill in pScrn->options) */
    xf86CollectOptions(pScrn, NULL);

    /* Set the bits per RGB for 8bpp mode */
    if (pScrn->depth == 8) {
    				/* ViRGE supports 6 RGB bits in depth 8 */
				/* modes (with 256 entry LUT) */
      pScrn->rgbBits = 6;
    }

    /* Process the options */
    if (!(ps3v->Options = malloc(sizeof(S3VOptions))))
	return FALSE;
    memcpy(ps3v->Options, S3VOptions, sizeof(S3VOptions));
    xf86ProcessOptions(pScrn->scrnIndex, pScrn->options, ps3v->Options);


    if (xf86ReturnOptValBool(ps3v->Options, OPTION_PCI_BURST, FALSE)) {
	ps3v->pci_burst = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: pci_burst - PCI burst read enabled\n");
    } else
   	ps3v->pci_burst = FALSE;
					/* default */
    ps3v->NoPCIRetry = 1;
   					/* Set option */
    if (xf86ReturnOptValBool(ps3v->Options, OPTION_PCI_RETRY, FALSE)) {
      if (xf86ReturnOptValBool(ps3v->Options, OPTION_PCI_BURST, FALSE)) {
      	ps3v->NoPCIRetry = 0;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: pci_retry\n");
	}
      else {
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
		"\"pci_retry\" option requires \"pci_burst\".\n");
	}
    }
    if (xf86IsOptionSet(ps3v->Options, OPTION_FIFO_CONSERV)) {
	ps3v->fifo_conservative = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: fifo_conservative set\n");
    } else
   	ps3v->fifo_conservative = FALSE;

    if (xf86IsOptionSet(ps3v->Options, OPTION_FIFO_MODERATE)) {
	ps3v->fifo_moderate = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: fifo_moderate set\n");
    } else
   	ps3v->fifo_moderate = FALSE;

    if (xf86IsOptionSet(ps3v->Options, OPTION_FIFO_AGGRESSIVE)) {
	ps3v->fifo_aggressive = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: fifo_aggressive set\n");
    } else
   	ps3v->fifo_aggressive = FALSE;

    if (xf86IsOptionSet(ps3v->Options, OPTION_SLOW_EDODRAM)) {
	ps3v->slow_edodram = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: slow_edodram set\n");
    } else
   	ps3v->slow_edodram = FALSE;

    if (xf86IsOptionSet(ps3v->Options, OPTION_SLOW_DRAM)) {
	ps3v->slow_dram = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: slow_dram set\n");
    } else
   	ps3v->slow_dram = FALSE;

    if (xf86IsOptionSet(ps3v->Options, OPTION_FAST_DRAM)) {
	ps3v->fast_dram = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: fast_dram set\n");
    } else
   	ps3v->fast_dram = FALSE;

    if (xf86IsOptionSet(ps3v->Options, OPTION_FPM_VRAM)) {
	ps3v->fpm_vram = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: fpm_vram set\n");
    } else
   	ps3v->fpm_vram = FALSE;

    if (xf86ReturnOptValBool(ps3v->Options, OPTION_NOACCEL, FALSE)) {
	ps3v->NoAccel = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: NoAccel - Acceleration disabled\n");
    } else
   	ps3v->NoAccel = FALSE;

    if (xf86ReturnOptValBool(ps3v->Options, OPTION_EARLY_RAS_PRECHARGE, FALSE)) {
	ps3v->early_ras_precharge = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: early_ras_precharge set\n");
    } else
   	ps3v->early_ras_precharge = FALSE;

    if (xf86ReturnOptValBool(ps3v->Options, OPTION_LATE_RAS_PRECHARGE, FALSE)) {
	ps3v->late_ras_precharge = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: late_ras_precharge set\n");
    } else
   	ps3v->late_ras_precharge = FALSE;

    if (xf86ReturnOptValBool(ps3v->Options, OPTION_LCD_CENTER, FALSE)) {
	ps3v->lcd_center = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: lcd_center set\n");
    } else
   	ps3v->lcd_center = FALSE;

    if (xf86ReturnOptValBool(ps3v->Options, OPTION_SHOWCACHE, FALSE)) {
	ps3v->ShowCache = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: show_cache set\n");
    } else
   	ps3v->ShowCache = FALSE;

    if (xf86GetOptValInteger(ps3v->Options, OPTION_LCDCLOCK, &ps3v->LCDClk)) {
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: lcd_setclk set to %1.3f Mhz\n",
		ps3v->LCDClk / 1000.0 );
    } else
   	ps3v->LCDClk = 0;

    if (xf86GetOptValFreq(ps3v->Options, OPTION_MCLK, OPTUNITS_MHZ, &real)) {
	ps3v->MCLK = (int)(real * 1000.0);
    	if (ps3v->MCLK <= 100000) {
	  xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: set_mclk set to %1.3f Mhz\n",
		ps3v->MCLK / 1000.0 );
	} else {
	  xf86DrvMsg(pScrn->scrnIndex, X_WARNING
	  	, "Memory Clock value of %1.3f MHz is larger than limit of 100 MHz\n"
		, ps3v->MCLK/1000.0);
	  ps3v->MCLK = 0;
	}
    } else
   	ps3v->MCLK = 0;

    if (xf86GetOptValFreq(ps3v->Options, OPTION_REFCLK, OPTUNITS_MHZ, &real)) {
	ps3v->REFCLK = (int)(real * 1000.0);
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Option: set_refclk set to %1.3f Mhz\n",
		   ps3v->REFCLK / 1000.0 );
    } else
   	ps3v->REFCLK = 0;

    from = X_DEFAULT;
    ps3v->hwcursor = TRUE;
    if (xf86GetOptValBool(ps3v->Options, OPTION_HWCURSOR, &ps3v->hwcursor))
	  from = X_CONFIG;
    if (xf86ReturnOptValBool(ps3v->Options, OPTION_SWCURSOR, FALSE)) {
	  ps3v->hwcursor = FALSE;
	  from = X_CONFIG;
    }
    xf86DrvMsg(pScrn->scrnIndex, from, "Using %s Cursor\n",
		ps3v->hwcursor ? "HW" : "SW");

    if (xf86GetOptValBool(ps3v->Options, OPTION_SHADOW_FB,&ps3v->shadowFB))
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "ShadowFB %s.\n",
		   ps3v->shadowFB ? "enabled" : "disabled");

    if ((s = xf86GetOptValString(ps3v->Options, OPTION_ROTATE))) {
	if(!xf86NameCmp(s, "CW")) {
	    /* accel is disabled below for shadowFB */
	    ps3v->shadowFB = TRUE;
	    ps3v->rotate = 1;
	    xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
		       "Rotating screen clockwise - acceleration disabled\n");
	} else if(!xf86NameCmp(s, "CCW")) {
	    ps3v->shadowFB = TRUE;
	    ps3v->rotate = -1;
	    xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,  "Rotating screen"
		       "counter clockwise - acceleration disabled\n");
	} else {
	    xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "\"%s\" is not a valid"
		       "value for Option \"Rotate\"\n", s);
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "Valid options are \"CW\" or \"CCW\"\n");
	}
    }

    if (ps3v->shadowFB && !ps3v->NoAccel) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "HW acceleration not supported with \"shadowFB\".\n");
	ps3v->NoAccel = TRUE;
    }

    if (ps3v->rotate && ps3v->hwcursor) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "HW cursor not supported with \"rotate\".\n");
	ps3v->hwcursor = FALSE;
    }

    if (xf86IsOptionSet(ps3v->Options, OPTION_MX_CR3A_FIX))
      {
	if (xf86GetOptValBool(ps3v->Options, OPTION_MX_CR3A_FIX ,&ps3v->mx_cr3a_fix))
	  xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "%s mx_cr3a_fix.\n",
		     ps3v->mx_cr3a_fix ? "Enabling (default)" : "Disabling");
      }
    else
      {
	ps3v->mx_cr3a_fix = TRUE;
	xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT, "mx_cr3a_fix.\n");
      }

    /* Find the PCI slot for this screen */
    /*
     * XXX Ignoring the Type list for now.  It might be needed when
     * multiple cards are supported.
     */
    if (pScrn->numEntities > 1) {
	S3VFreeRec(pScrn);
	return FALSE;
    }

    pEnt = xf86GetEntityInfo(pScrn->entityList[0]);

#if USE_INT10
    if (xf86LoadSubModule(pScrn, "int10")) {
 	xf86Int10InfoPtr pInt;
#if 1
	xf86DrvMsg(pScrn->scrnIndex,X_INFO,"initializing int10\n");
	pInt = xf86InitInt10(pEnt->index);
	xf86FreeInt10(pInt);
#endif
    }
#endif
    if (xf86LoadSubModule(pScrn, "vbe")) {
	ps3v->pVbe =  VBEInit(NULL,pEnt->index);
    }

    ps3v->PciInfo = xf86GetPciInfoForEntity(pEnt->index);

    /*
     * Set the Chipset and ChipRev, allowing config file entries to
     * override.
     */
    if (pEnt->device->chipset && *pEnt->device->chipset) {
	pScrn->chipset = pEnt->device->chipset;
        ps3v->Chipset = xf86StringToToken(S3VChipsets, pScrn->chipset);
        from = X_CONFIG;
    } else if (pEnt->device->chipID >= 0) {
	ps3v->Chipset = pEnt->device->chipID;
	pScrn->chipset = (char *)xf86TokenToString(S3VChipsets, ps3v->Chipset);
	from = X_CONFIG;
  	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "ChipID override: 0x%04X\n",
		   ps3v->Chipset);
    } else {
	from = X_PROBED;
	ps3v->Chipset = PCI_DEV_DEVICE_ID(ps3v->PciInfo);
	pScrn->chipset = (char *)xf86TokenToString(S3VChipsets, ps3v->Chipset);
    }

    if (pEnt->device->chipRev >= 0) {
	ps3v->ChipRev = pEnt->device->chipRev;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "ChipRev override: %d\n",
		   ps3v->ChipRev);
    } else {
        ps3v->ChipRev = PCI_DEV_REVISION(ps3v->PciInfo);
    }
    free(pEnt);

    /*
     * This shouldn't happen because such problems should be caught in
     * S3VProbe(), but check it just in case.
     */
    if (pScrn->chipset == NULL) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "ChipID 0x%04X is not recognised\n", ps3v->Chipset);
	vbeFree(ps3v->pVbe);
	ps3v->pVbe = NULL;
	return FALSE;
    }
    if (ps3v->Chipset < 0) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "Chipset \"%s\" is not recognised\n", pScrn->chipset);
	vbeFree(ps3v->pVbe);
	ps3v->pVbe = NULL;
	return FALSE;
    }

    xf86DrvMsg(pScrn->scrnIndex, from, "Chipset: \"%s\"\n", pScrn->chipset);

    /* Handle XVideo after we know chipset, so we can give an */
    /* intelligent comment about support */
    if (xf86IsOptionSet(ps3v->Options, OPTION_XVIDEO))
      {
	if(S3VQueryXvCapable(pScrn))
	  {
	    if (xf86GetOptValBool(ps3v->Options, OPTION_XVIDEO ,&ps3v->XVideo))
	      xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "%s XVideo.\n",
			 ps3v->XVideo ? "Enabling (default)" : "Disabling");
	  }
	else
	  xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT, "XVideo not supported.\n");
      }
    else
      {
	ps3v->XVideo = S3VQueryXvCapable(pScrn);
	if(ps3v->XVideo)
	  xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT, "XVideo supported.\n");
	else
	  xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT, "XVideo not supported.\n");
      }


  S3VMapMem(pScrn);
  hwp = VGAHWPTR(pScrn);
  vgaIOBase = hwp->IOBase;
  vgaCRIndex = vgaIOBase + 4;
  vgaCRReg = vgaIOBase + 5;

    xf86ErrorFVerb(VERBLEV,
	"	S3VPreInit vgaCRIndex=%x, vgaIOBase=%x, MMIOBase=%p\n",
	vgaCRIndex, vgaIOBase, hwp->MMIOBase );


#if 0	/* Not needed in 4.0 flavors */
   /* Unlock sys regs */
   VGAOUT8(vgaCRIndex, 0x38);
   VGAOUT8(vgaCRReg, 0x48);
#endif

   /* Next go on to detect amount of installed ram */

   VGAOUT8(vgaCRIndex, 0x36);           /* for register CR36 (CONFG_REG1),*/
   config1 = VGAIN8(vgaCRReg);          /* get amount of vram installed   */

   VGAOUT8(vgaCRIndex, 0x37);           /* for register CR37 (CONFG_REG2),*/
   config2 = VGAIN8(vgaCRReg);          /* get amount of off-screen ram   */

   if (xf86LoadSubModule(pScrn, "ddc")) {
       xf86MonPtr pMon = NULL;

       if ((ps3v->pVbe)
	   && ((pMon = xf86PrintEDID(vbeDoEDID(ps3v->pVbe, NULL))) != NULL))
	   xf86SetDDCproperties(pScrn,pMon);
       else if (!S3Vddc1(pScrn)) {
	   S3Vddc2(pScrn);
       }
   }
   if (ps3v->pVbe) {
       vbeFree(ps3v->pVbe);
       ps3v->pVbe = NULL;
   }

   /*
    * If the driver can do gamma correction, it should call xf86SetGamma()
    * here. (from MGA, no ViRGE gamma support yet, but needed for
    * xf86HandleColormaps support.)
    */
   {
       Gamma zeros = {0.0, 0.0, 0.0};

       if (!xf86SetGamma(pScrn, zeros)) {
	   return FALSE;
       }
   }

   /* And compute the amount of video memory and offscreen memory */
   ps3v->MemOffScreen = 0;

   if (!pScrn->videoRam) {
      if (ps3v->Chipset == S3_ViRGE_VX) {
	  switch((config2 & 0x60) >> 5) {
         case 1:
            ps3v->MemOffScreen = 4 * 1024;
            break;
         case 2:
            ps3v->MemOffScreen = 2 * 1024;
            break;
         }
         switch ((config1 & 0x60) >> 5) {
         case 0:
            ps3v->videoRamKbytes = 2 * 1024;
            break;
         case 1:
            ps3v->videoRamKbytes = 4 * 1024;
            break;
         case 2:
            ps3v->videoRamKbytes = 6 * 1024;
            break;
         case 3:
            ps3v->videoRamKbytes = 8 * 1024;
            break;
         }
         ps3v->videoRamKbytes -= ps3v->MemOffScreen;
      }
      else if (S3_TRIO_3D_2X_SERIES(ps3v->Chipset)) {
         switch((config1 & 0xE0) >> 5) {
         case 0:  /* 8MB -- only 4MB usable for display/cursor */
            ps3v->videoRamKbytes = 4 * 1024;
            ps3v->MemOffScreen   = 4 * 1024;
            break;
         case 1:    /* 32 bit interface -- yuck */
	   xf86ErrorFVerb(VERBLEV,
			  "	found 32 bit interface for video memory -- yuck:(\n");
         case 2:
            ps3v->videoRamKbytes = 4 * 1024;
            break;
         case 6:
            ps3v->videoRamKbytes = 2 * 1024;
            break;
         }
      }
      else if (S3_TRIO_3D_SERIES(ps3v->Chipset)) {
        switch((config1 & 0xE0) >> 5) {
        case 0:
        case 2:
           ps3v->videoRamKbytes = 4 * 1024;
           break;
        case 4:
           ps3v->videoRamKbytes = 2 * 1024;
           break;
        }
      }
      else if (S3_ViRGE_GX2_SERIES(ps3v->Chipset) || S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
	  switch((config1 & 0xC0) >> 6) {
	  case 1:
            ps3v->videoRamKbytes = 4 * 1024;
            break;
         case 3:
            ps3v->videoRamKbytes = 2 * 1024;
            break;
         }
      }
      else {
         switch((config1 & 0xE0) >> 5) {
         case 0:
            ps3v->videoRamKbytes = 4 * 1024;
            break;
         case 4:
            ps3v->videoRamKbytes = 2 * 1024;
            break;
         case 6:
            ps3v->videoRamKbytes = 1 * 1024;
            break;
         }
      }
      					/* And save a byte value also */
      ps3v->videoRambytes = ps3v->videoRamKbytes * 1024;
      				       	/* Make sure the screen also */
					/* has correct videoRam setting */
      pScrn->videoRam = ps3v->videoRamKbytes;

      if (ps3v->MemOffScreen)
	 xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
	    	"videoram:  %dk (plus %dk off-screen)\n",
                ps3v->videoRamKbytes, ps3v->MemOffScreen);
      else
	 xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "videoram:  %dk\n",
                ps3v->videoRamKbytes);
   } else {
   					/* Note: if ram is not probed then */
					/* ps3v->videoRamKbytes will not be init'd */
					/* should we? can do it here... */


      xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "videoram:  %dk\n",
              	ps3v->videoRamKbytes);
   }

   /* reset S3 graphics engine to avoid memory corruption */
   if (ps3v->Chipset != S3_ViRGE_VX) {
      VGAOUT8(vgaCRIndex, 0x66);
      cr66 = VGAIN8(vgaCRReg);
      VGAOUT8(vgaCRReg, cr66 | 0x02);
      usleep(10000);  /* wait a little bit... */
   }

   /*
    * There was a lot of plainly wrong code here. pScrn->clock is just a list
    * of supported _dotclocks_ used when you don't have a programmable clock.
    *
    * S3V and Savage seem to think that this is the max ramdac speed. This
    * driver just ignores the whole mess done before and sets
    * clockRange->maxClock differently slightly later.
    *
    * In order to not ditch information, here is a table of what the dacspeeds
    * "were" before the cleanup.
    *
    * Chipset              ### >= 24bpp ### lower
    *
    *  S3_ViRGE_VX               135000     220000
    *  S3_TRIO_3D_2X_SERIES      135000     230000
    *  S3_ViRGE_DXGX             135000     170000
    *  S3_ViRGE_GX2_SERIES       135000     170000
    *  S3_ViRGE_MX_SERIES        100000     135000
    *
    * Others devices get:
    *      > 24bpp:  57000
    *      = 24bpp:  95000
    *      < 24bpp: 135000
    *
    * Special case is the MELCO BIOS:
    *      > 24bpp:  83500
    *      = 24bpp: 111500
    *      >  8bpp: 162500
    *      <= 8bpp: 191500
    */

   if (find_bios_string(ps3v, BIOS_BASE, "S3 86C325",
			"MELCO WGP-VG VIDEO BIOS") != NULL) {
      if (xf86GetVerbosity())
	 xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "MELCO BIOS found\n");
      if (ps3v->MCLK <= 0)
          ps3v->MCLK = 74000;
   }

   if (ps3v->Chipset != S3_ViRGE_VX) {
      VGAOUT8(vgaCRIndex, 0x66);
      VGAOUT8(vgaCRReg, cr66 & ~0x02);  /* clear reset flag */
      usleep(10000);  /* wait a little bit... */
   }

   /* Detect current MCLK and print it for user */
   VGAOUT8(0x3c4, 0x08);
   VGAOUT8(0x3c5, 0x06);
   VGAOUT8(0x3c4, 0x10);
   n = VGAIN8(0x3c5);
   VGAOUT8(0x3c4, 0x11);
   m = VGAIN8(0x3c5);
   m &= 0x7f;
   n1 = n & 0x1f;
   n2 = (n>>5) & 0x03;
   mclk = ((1431818 * (m+2)) / (n1+2) / (1 << n2) + 50) / 100;
   if (S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
      MessageType is_probed = X_PROBED;
      /*
       * try to figure out which reference clock is used:
       * Toshiba Tecra 5x0/7x0 seems to use 28.636 MHz
       * Compaq Armada 7x00 uses 14.318 MHz
       */
      if (find_bios_string(ps3v, BIOS_BASE, "COMPAQ M5 BIOS", NULL) != NULL) {
	 if (xf86GetVerbosity())
	    xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "COMPAQ M5 BIOS found\n");
	 /* ps3v->refclk_fact = 1.0; */
      }
      else if (find_bios_string(ps3v, BIOS_BASE, "TOSHIBA Video BIOS", NULL) != NULL) {
	 if (xf86GetVerbosity())
	    xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "TOSHIBA Video BIOS found\n");
	 /* ps3v->refclk_fact = 2.0; */
      }
      /* else */ {  /* always use guessed value... */
	 if (mclk > 60000)
	    ps3v->refclk_fact = 1.0;
	 else
	    ps3v->refclk_fact = 2.0;  /* don't know why ??? */
      }
      if (ps3v->REFCLK != 0) {
	 ps3v->refclk_fact = ps3v->REFCLK / 14318.0;
	 is_probed = X_CONFIG;
      }
      else
	 ps3v->REFCLK = (int)(14318.18 * ps3v->refclk_fact);

      mclk = (int)(mclk * ps3v->refclk_fact);
      xf86DrvMsg(pScrn->scrnIndex, is_probed, "assuming RefCLK value of %1.3f MHz\n",
		 ps3v->REFCLK / 1000.0);
   }
   xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "Detected current MCLK value of %1.3f MHz\n",
	     mclk / 1000.0);

   if (S3_ViRGE_MX_SERIES(ps3v->Chipset) && xf86GetVerbosity()) {
       int lcdclk, h_lcd, v_lcd;
       if (ps3v->LCDClk) {
	  lcdclk = ps3v->LCDClk;
       } else {
	  unsigned char sr12, sr13, sr29;
          VGAOUT8(0x3c4, 0x12);
          sr12 = VGAIN8(0x3c5);
          VGAOUT8(0x3c4, 0x13);
          sr13 = VGAIN8(0x3c5) & 0x7f;
          VGAOUT8(0x3c4, 0x29);
          sr29 = VGAIN8(0x3c5);
    	  n1 = sr12 & 0x1f;
    	  n2 = ((sr12>>6) & 0x03) | ((sr29 & 0x01) << 2);
          lcdclk = ((int)(ps3v->refclk_fact * 1431818 * (sr13+2)) / (n1+2) / (1 << n2) + 50) / 100;
       }
       VGAOUT8(0x3c4, 0x61);
       h_lcd = VGAIN8(0x3c5);
       VGAOUT8(0x3c4, 0x66);
       h_lcd |= ((VGAIN8(0x3c5) & 0x02) << 7);
       h_lcd = (h_lcd+1) * 8;
       VGAOUT8(0x3c4, 0x69);
       v_lcd = VGAIN8(0x3c5);
       VGAOUT8(0x3c4, 0x6e);
       v_lcd |= ((VGAIN8(0x3c5) & 0x70) << 4);
       v_lcd++;
       xf86DrvMsg(pScrn->scrnIndex
	      , ps3v->LCDClk ? X_CONFIG : X_PROBED
       	      , "LCD size %dx%d, clock %1.3f MHz\n"
	      , h_lcd, v_lcd
	      , lcdclk / 1000.0);
   }

   S3VDisableMmio(pScrn);
   S3VUnmapMem(pScrn);

   /* And finally set various possible option flags */

   ps3v->bankedMono = FALSE;


#if 0
   vga256InfoRec.directMode = XF86DGADirectPresent;
#endif

    				/* Lower depths default to config file */
  pScrn->virtualX = pScrn->display->virtualX;
				/* Adjust the virtualX to meet ViRGE hardware */
				/* limits for depth 24, bpp 24 & 32.  This is */
				/* mostly for 32 bpp as 1024x768 is one pixel */
				/* larger than supported. */
  if (pScrn->depth == 24)
      if ( ((pScrn->bitsPerPixel/8) * pScrn->display->virtualX) > 4095 ) {
        pScrn->virtualX = 4095 / (pScrn->bitsPerPixel / 8);
        xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
	   "Virtual width adjusted, max for this depth & bpp is %d.\n",
	   pScrn->virtualX );
      }

    /*
     * Setup the ClockRanges, which describe what clock ranges are available,
     * and what sort of modes they can be used for.
     */
    clockRanges = XNFcallocarray(sizeof(ClockRange), 1);
    clockRanges->next = NULL;
    clockRanges->minClock = 10000;
    if (ps3v->Chipset == S3_ViRGE_VX )
        clockRanges->maxClock = 440000;
    else
        clockRanges->maxClock = 270000;
    clockRanges->clockIndex = -1;		/* programmable */
    clockRanges->interlaceAllowed = TRUE;	/* yes, S3V SVGA 3.3.2 */
    clockRanges->doubleScanAllowed = TRUE;

  					/* Screen pointer 		*/
    i = xf86ValidateModes(pScrn,
  					/* Available monitor modes 	*/
					/* (DisplayModePtr availModes)  */
		pScrn->monitor->Modes,
					/* req mode names for screen 	*/
					/* (char **modesNames)  	*/
		pScrn->display->modes,
					/* list of clock ranges allowed */
					/* (ClockRangePtr clockRanges) 	*/
		clockRanges,
					/* list of driver line pitches, */
					/* supply or NULL and use min/	*/
					/* max below			*/
					/* (int *linePitches)		*/
		NULL,
					/* min lin pitch (width)	*/
					/* (int minPitch)		*/
		256,
					/* max line pitch (width)	*/
					/* (int maxPitch)		*/
		2048,
					/* bits of granularity for line	*/
					/* pitch (width) above, reguired*/
					/* (int pitchInc)		*/
		pScrn->bitsPerPixel,
					/* min virt height, 0 no limit	*/
					/* (int minHeight)		*/
		128,
					/* max virt height, 0 no limit	*/
					/* (int maxHeight)		*/
		2048,
					/* force virtX, 0 for auto 	*/
					/* (int VirtualX) 		*/
					/* value is adjusted above for  */
					/* hardware limits */
		pScrn->virtualX,
					/* force virtY, 0 for auto	*/
					/* (int VirtualY)		*/
		pScrn->display->virtualY,
					/* size (bytes) of aper used to	*/
					/* access video memory		*/
					/* (unsigned long apertureSize)	*/
		ps3v->videoRambytes,
					/* how to pick mode */
					/* (LookupModeFlags strategy)	*/
		LOOKUP_BEST_REFRESH);

    if (i == -1) {
	S3VFreeRec(pScrn);
	return FALSE;
    }
    /* Prune the modes marked as invalid */
    xf86PruneDriverModes(pScrn);

    if (i == 0 || pScrn->modes == NULL) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "No valid modes found\n");
	S3VFreeRec(pScrn);
	return FALSE;
    }

    xf86SetCrtcForModes(pScrn, INTERLACE_HALVE_V);
    /*xf86SetCrtcForModes(pScrn, 0);*/

    /* Set the current mode to the first in the list */
    pScrn->currentMode = pScrn->modes;

    /* Print the list of modes being used */
    xf86PrintModes(pScrn);

    /* Set display resolution */
    xf86SetDpi(pScrn, 0, 0);

    /* Load bpp-specific modules */
    if( xf86LoadSubModule(pScrn, "fb") == NULL )
    {
	S3VFreeRec(pScrn);
	return FALSE;
    }

    if (!ps3v->NoAccel || ps3v->hwcursor ) {
	xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, VERBLEV,
		       "Falling back to shadowfb\n");
	ps3v->NoAccel = 1;
	ps3v->hwcursor = 0;
	ps3v->shadowFB = 1;
    }

    /* Load ramdac if needed */
    if (ps3v->hwcursor) {
	if (!xf86LoadSubModule(pScrn, "ramdac")) {
	    S3VFreeRec(pScrn);
	    return FALSE;
	}
    }

    if (ps3v->shadowFB) {
	if (!xf86LoadSubModule(pScrn, "shadowfb")) {
	    S3VFreeRec(pScrn);
	    return FALSE;
	}
    }

    /* Setup WAITFIFO() for accel and ModeInit() */
    /* Needs to be done prior to first ModeInit call */
    /* and any accel activity. */
    switch(ps3v->Chipset)
      {
	/* GX2_SERIES chips, GX2 & TRIO_3D_2X */
      case S3_ViRGE_GX2:
      case S3_TRIO_3D_2X:
	ps3v->pWaitFifo = S3VWaitFifoGX2;
	ps3v->pWaitCmd = S3VWaitCmdGX2;
	break;
      case S3_ViRGE:
      case S3_ViRGE_VX:
      default:
	ps3v->pWaitFifo = S3VWaitFifoMain;
	/* Do nothing... */
	ps3v->pWaitCmd = S3VWaitDummy;
	break;
      }

    return TRUE;
}


/*
 * This is called when VT switching back to the X server.  Its job is
 * to reinitialise the video mode.
 *
 * We may wish to unmap video/MMIO memory too.
 */


/* Mandatory */
static Bool
S3VEnterVT(ScrnInfoPtr pScrn)
{
    PVERB5("	S3VEnterVT\n");
    				/* Enable MMIO and map memory */
#ifdef unmap_always
    S3VMapMem(pScrn);
#endif
    S3VEnableMmio(pScrn);

    S3VSave(pScrn);
    return S3VModeInit(pScrn, pScrn->currentMode);
}


/*
 * This is called when VT switching away from the X server.  Its job is
 * to restore the previous (text) mode.
 *
 * We may wish to remap video/MMIO memory too.
 *
 */

/* Mandatory */
static void
S3VLeaveVT(ScrnInfoPtr pScrn)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    S3VPtr ps3v = S3VPTR(pScrn);
    vgaRegPtr vgaSavePtr = &hwp->SavedReg;
    S3VRegPtr S3VSavePtr = &ps3v->SavedReg;

    PVERB5("	S3VLeaveVT\n");
    					/* Like S3VRestore, but uses passed */
					/* mode registers.		    */
    S3VWriteMode(pScrn, vgaSavePtr, S3VSavePtr);
    					/* Restore standard register access */
					/* and unmap memory.		    */
    S3VDisableMmio(pScrn);
#ifdef unmap_always
    S3VUnmapMem(pScrn);
#endif
    /*vgaHWLockMMIO(hwp);*/

}


/*
 * This function performs the inverse of the restore function: It saves all
 * the standard and extended registers that we are going to modify to set
 * up a video mode. Again, we also save the STREAMS context if it is needed.
 *
 * prototype
 *   void ChipSave(ScrnInfoPtr pScrn)
 *
 */

static void
S3VSave (ScrnInfoPtr pScrn)
{
  unsigned char cr3a, cr66;
  vgaHWPtr hwp = VGAHWPTR(pScrn);
  vgaRegPtr vgaSavePtr = &hwp->SavedReg;
  S3VPtr ps3v = S3VPTR(pScrn);
  S3VRegPtr save = &ps3v->SavedReg;
  int vgaCRIndex, vgaCRReg, vgaIOBase;
  vgaIOBase = hwp->IOBase;
  vgaCRIndex = 0;

  vgaCRReg = 0;

  vgaCRReg = vgaIOBase + 5;
  vgaCRIndex = vgaIOBase + 4;

    PVERB5("	S3VSave\n");

   /*
    * This function will handle creating the data structure and filling
    * in the generic VGA portion.
    */

   VGAOUT8(vgaCRIndex, 0x66);
   cr66 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRReg, cr66 | 0x80);
   VGAOUT8(vgaCRIndex, 0x3a);
   cr3a = VGAIN8(vgaCRReg);
   save->CR3A = cr3a;

   VGAOUT8(vgaCRReg, cr3a | 0x80);

   /* VGA_SR_MODE saves mode info only, no fonts, no colormap */
					/* Save all for primary, anything */
					/* for secondary cards?, do MODE */
					/* for the moment. */
   if (xf86IsPrimaryPci(ps3v->PciInfo))
   	vgaHWSave(pScrn, vgaSavePtr, VGA_SR_ALL);
   else
   	vgaHWSave(pScrn, vgaSavePtr, VGA_SR_MODE);

   VGAOUT8(vgaCRIndex, 0x66);
   VGAOUT8(vgaCRReg, cr66);
   VGAOUT8(vgaCRIndex, 0x3a);
   VGAOUT8(vgaCRReg, cr3a);

   /* First unlock extended sequencer regs */
   VGAOUT8(0x3c4, 0x08);
   save->SR08 = VGAIN8(0x3c5);
   VGAOUT8(0x3c5, 0x06);

   /* Now we save all the s3 extended regs we need */
   VGAOUT8(vgaCRIndex, 0x31);
   save->CR31 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x34);
   save->CR34 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x36);
   save->CR36 = VGAIN8(vgaCRReg);

   /* workaround cr3a corruption */
   if( !(ps3v->mx_cr3a_fix))
     {
       VGAOUT8(vgaCRIndex, 0x3a);
       save->CR3A = VGAIN8(vgaCRReg);
     }

   if (!S3_TRIO_3D_SERIES(ps3v->Chipset)) {
     VGAOUT8(vgaCRIndex, 0x40);
     save->CR40 = VGAIN8(vgaCRReg);
   }
   if (S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
     VGAOUT8(vgaCRIndex, 0x41);
     save->CR41 = VGAIN8(vgaCRReg);
   }
   VGAOUT8(vgaCRIndex, 0x42);
   save->CR42 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x45);
   save->CR45 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x51);
   save->CR51 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x53);
   save->CR53 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x54);
   save->CR54 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x55);
   save->CR55 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x58);
   save->CR58 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x63);
   save->CR63 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x66);
   save->CR66 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x67);
   save->CR67 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x68);
   save->CR68 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x69);
   save->CR69 = VGAIN8(vgaCRReg);

   VGAOUT8(vgaCRIndex, 0x33);
   save->CR33 = VGAIN8(vgaCRReg);
   if (S3_TRIO_3D_2X_SERIES(ps3v->Chipset) || S3_ViRGE_GX2_SERIES(ps3v->Chipset)
       /* MXTESTME */ || S3_ViRGE_MX_SERIES(ps3v->Chipset) )
   {
      VGAOUT8(vgaCRIndex, 0x85);
      save->CR85 = VGAIN8(vgaCRReg);
   }
   if (ps3v->Chipset == S3_ViRGE_DXGX) {
      VGAOUT8(vgaCRIndex, 0x86);
      save->CR86 = VGAIN8(vgaCRReg);
   }
   if ((ps3v->Chipset == S3_ViRGE_GX2) ||
       S3_ViRGE_MX_SERIES(ps3v->Chipset) ) {
      VGAOUT8(vgaCRIndex, 0x7B);
      save->CR7B = VGAIN8(vgaCRReg);
      VGAOUT8(vgaCRIndex, 0x7D);
      save->CR7D = VGAIN8(vgaCRReg);
      VGAOUT8(vgaCRIndex, 0x87);
      save->CR87 = VGAIN8(vgaCRReg);
      VGAOUT8(vgaCRIndex, 0x92);
      save->CR92 = VGAIN8(vgaCRReg);
      VGAOUT8(vgaCRIndex, 0x93);
      save->CR93 = VGAIN8(vgaCRReg);
   }
   if (ps3v->Chipset == S3_ViRGE_DXGX || S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
       S3_ViRGE_MX_SERIES(ps3v->Chipset) || S3_TRIO_3D_SERIES(ps3v->Chipset)) {
      VGAOUT8(vgaCRIndex, 0x90);
      save->CR90 = VGAIN8(vgaCRReg);
      VGAOUT8(vgaCRIndex, 0x91);
      save->CR91 = VGAIN8(vgaCRReg);
   }

   /* Extended mode timings regs */

   VGAOUT8(vgaCRIndex, 0x3b);
   save->CR3B = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x3c);
   save->CR3C = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x43);
   save->CR43 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x5d);
   save->CR5D = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x5e);
   save->CR5E = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x65);
   save->CR65 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRIndex, 0x6d);
   save->CR6D = VGAIN8(vgaCRReg);


   /* Save sequencer extended regs for DCLK PLL programming */

   VGAOUT8(0x3c4, 0x10);
   save->SR10 = VGAIN8(0x3c5);
   VGAOUT8(0x3c4, 0x11);
   save->SR11 = VGAIN8(0x3c5);

   VGAOUT8(0x3c4, 0x12);
   save->SR12 = VGAIN8(0x3c5);
   VGAOUT8(0x3c4, 0x13);
   save->SR13 = VGAIN8(0x3c5);
   if (S3_ViRGE_GX2_SERIES(ps3v->Chipset) || S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
     VGAOUT8(0x3c4, 0x29);
     save->SR29 = VGAIN8(0x3c5);
   }
        /* SR 54,55,56,57 undocumented for GX2.  Was this supposed to be CR? */
        /* (These used to be part of the above if() */
   if (S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
     VGAOUT8(0x3c4, 0x54);
     save->SR54 = VGAIN8(0x3c5);
     VGAOUT8(0x3c4, 0x55);
     save->SR55 = VGAIN8(0x3c5);
     VGAOUT8(0x3c4, 0x56);
     save->SR56 = VGAIN8(0x3c5);
     VGAOUT8(0x3c4, 0x57);
     save->SR57 = VGAIN8(0x3c5);
   }

   VGAOUT8(0x3c4, 0x15);
   save->SR15 = VGAIN8(0x3c5);
   VGAOUT8(0x3c4, 0x18);
   save->SR18 = VGAIN8(0x3c5);
   if (S3_TRIO_3D_SERIES(ps3v->Chipset)) {
     VGAOUT8(0x3c4, 0x0a);
     save->SR0A = VGAIN8(0x3c5);
     VGAOUT8(0x3c4, 0x0F);
     save->SR0F = VGAIN8(0x3c5);
   }

   VGAOUT8(vgaCRIndex, 0x66);
   cr66 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRReg, cr66 | 0x80);
   VGAOUT8(vgaCRIndex, 0x3a);
   cr3a = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRReg, cr3a | 0x80);

   /* And if streams is to be used, save that as well */

   if(ps3v->NeedSTREAMS) {
      S3VSaveSTREAMS(pScrn, save->STREAMS);
      }

   /* Now save Memory Interface Unit registers */
   if( S3_ViRGE_GX2_SERIES(ps3v->Chipset)
      /* MXTESTME */ || S3_ViRGE_MX_SERIES(ps3v->Chipset) )
     {
     /* No MMPR regs on MX & GX2 */
     }
   else
     {
     save->MMPR0 = INREG(FIFO_CONTROL_REG);
     save->MMPR1 = INREG(MIU_CONTROL_REG);
     save->MMPR2 = INREG(STREAMS_TIMEOUT_REG);
     save->MMPR3 = INREG(MISC_TIMEOUT_REG);
     }

   if (xf86GetVerbosity() > 1) {
      /* Debug */
     /* Which chipsets? */
     if (
	 /* virge */
	 ps3v->Chipset == S3_ViRGE ||
	 /* VX */
	 S3_ViRGE_VX_SERIES(ps3v->Chipset) ||
	 /* DX & GX */
	 ps3v->Chipset == S3_ViRGE_DXGX ||
	 /* GX2 & Trio3D_2X */
	 /* S3_ViRGE_GX2_SERIES(ps3v->Chipset) || */
	 /* Trio3D_2X */
	 /* S3_TRIO_3D_2X_SERIES(ps3v->Chipset) */
	 /* MX & MX+ */
	 /* S3_ViRGE_MX_SERIES(ps3v->Chipset) || */
	 /* MX+ only */
	 /* S3_ViRGE_MXP_SERIES(ps3v->Chipset) || */
	 /* Trio3D */
	 ps3v->Chipset == S3_TRIO_3D
         )
       {

      xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, VERBLEV,
         "MMPR regs: %08lx %08lx %08lx %08lx\n",
	     (unsigned long)INREG(FIFO_CONTROL_REG),
	     (unsigned long)INREG(MIU_CONTROL_REG),
	     (unsigned long)INREG(STREAMS_TIMEOUT_REG),
	     (unsigned long)INREG(MISC_TIMEOUT_REG));
       }

      PVERB5("\n\nViRGE driver: saved current video mode. Register dump:\n\n");
   }

   VGAOUT8(vgaCRIndex, 0x3a);
   VGAOUT8(vgaCRReg, cr3a);
   VGAOUT8(vgaCRIndex, 0x66);
   VGAOUT8(vgaCRReg, cr66);
   				/* Dup the VGA & S3V state to the */
				/* new mode state, but only first time. */
   if( !ps3v->ModeStructInit ) {
     /* XXX Should check the return value of vgaHWCopyReg() */
     vgaHWCopyReg( &hwp->ModeReg, vgaSavePtr );
     memcpy( &ps3v->ModeReg, save, sizeof(S3VRegRec) );
     ps3v->ModeStructInit = TRUE;
   }

   if (xf86GetVerbosity() > 1) S3VPrintRegs(pScrn);

   return;
}


/* This function saves the STREAMS registers to our private structure */

static void
S3VSaveSTREAMS(ScrnInfoPtr pScrn, unsigned int *streams)
{
  S3VPtr ps3v = S3VPTR(pScrn);

   streams[0] = INREG(PSTREAM_CONTROL_REG);
   streams[1] = INREG(COL_CHROMA_KEY_CONTROL_REG);
   streams[2] = INREG(SSTREAM_CONTROL_REG);
   streams[3] = INREG(CHROMA_KEY_UPPER_BOUND_REG);
   streams[4] = INREG(SSTREAM_STRETCH_REG);
   streams[5] = INREG(BLEND_CONTROL_REG);
   streams[6] = INREG(PSTREAM_FBADDR0_REG);
   streams[7] = INREG(PSTREAM_FBADDR1_REG);
   streams[8] = INREG(PSTREAM_STRIDE_REG);
   streams[9] = INREG(DOUBLE_BUFFER_REG);
   streams[10] = INREG(SSTREAM_FBADDR0_REG);
   streams[11] = INREG(SSTREAM_FBADDR1_REG);
   streams[12] = INREG(SSTREAM_STRIDE_REG);
   streams[13] = INREG(OPAQUE_OVERLAY_CONTROL_REG);
   streams[14] = INREG(K1_VSCALE_REG);
   streams[15] = INREG(K2_VSCALE_REG);
   streams[16] = INREG(DDA_VERT_REG);
   streams[17] = INREG(STREAMS_FIFO_REG);
   streams[18] = INREG(PSTREAM_START_REG);
   streams[19] = INREG(PSTREAM_WINDOW_SIZE_REG);
   streams[20] = INREG(SSTREAM_START_REG);
   streams[21] = INREG(SSTREAM_WINDOW_SIZE_REG);

}


/*
 * This function is used to restore a video mode. It writes out all
 * of the standard VGA and extended S3 registers needed to setup a
 * video mode.
 *
 * Note that our life is made more difficult because of the STREAMS
 * processor which must be used for 24bpp. We need to disable STREAMS
 * before we switch video modes, or we risk locking up the machine.
 * We also have to follow a certain order when reenabling it.
 */
/* let's try restoring in the same order as in the 3.3.2.3 driver */
static void
S3VWriteMode (ScrnInfoPtr pScrn, vgaRegPtr vgaSavePtr, S3VRegPtr restore)
{
  unsigned char tmp, cr3a=0, cr66, cr67;

  vgaHWPtr hwp = VGAHWPTR(pScrn);
  S3VPtr ps3v = S3VPTR(pScrn);
  int vgaCRIndex, vgaCRReg, vgaIOBase;
  vgaIOBase = hwp->IOBase;
  vgaCRIndex = vgaIOBase + 4;
  vgaCRReg = vgaIOBase + 5;

    PVERB5("	S3VWriteMode\n");

  vgaHWProtect(pScrn, TRUE);

   /* Are we going to re-enable STREAMS in this new mode? */
   ps3v->STREAMSRunning = restore->CR67 & 0x0c;

   /* First reset GE to make sure nothing is going on */
   if(ps3v->Chipset == S3_ViRGE_VX) {
      VGAOUT8(vgaCRIndex, 0x63);
      if(VGAIN8(vgaCRReg) & 0x01) S3VGEReset(pScrn,0,__LINE__,__FILE__);
      }
   else {
      VGAOUT8(vgaCRIndex, 0x66);
      if(VGAIN8(vgaCRReg) & 0x01) S3VGEReset(pScrn,0,__LINE__,__FILE__);
      }

   /* As per databook, always disable STREAMS before changing modes */
   VGAOUT8(vgaCRIndex, 0x67);
   cr67 = VGAIN8(vgaCRReg);
   if ((cr67 & 0x0c) == 0x0c) {
      S3VDisableSTREAMS(pScrn);     /* If STREAMS was running, disable it */
      }

   /* Restore S3 extended regs */
   VGAOUT8(vgaCRIndex, 0x63);
   VGAOUT8(vgaCRReg, restore->CR63);
   VGAOUT8(vgaCRIndex, 0x66);
   VGAOUT8(vgaCRReg, restore->CR66);
   VGAOUT8(vgaCRIndex, 0x3a);
   VGAOUT8(vgaCRReg, restore->CR3A);
   VGAOUT8(vgaCRIndex, 0x31);
   VGAOUT8(vgaCRReg, restore->CR31);
   VGAOUT8(vgaCRIndex, 0x58);
   VGAOUT8(vgaCRReg, restore->CR58);
   VGAOUT8(vgaCRIndex, 0x55);
   VGAOUT8(vgaCRReg, restore->CR55);

   /* Extended mode timings registers */
   VGAOUT8(vgaCRIndex, 0x53);
   VGAOUT8(vgaCRReg, restore->CR53);
   VGAOUT8(vgaCRIndex, 0x5d);
   VGAOUT8(vgaCRReg, restore->CR5D);
   VGAOUT8(vgaCRIndex, 0x5e);
   VGAOUT8(vgaCRReg, restore->CR5E);
   VGAOUT8(vgaCRIndex, 0x3b);
   VGAOUT8(vgaCRReg, restore->CR3B);
   VGAOUT8(vgaCRIndex, 0x3c);
   VGAOUT8(vgaCRReg, restore->CR3C);
   VGAOUT8(vgaCRIndex, 0x43);
   VGAOUT8(vgaCRReg, restore->CR43);
   VGAOUT8(vgaCRIndex, 0x65);
   VGAOUT8(vgaCRReg, restore->CR65);
   VGAOUT8(vgaCRIndex, 0x6d);
   VGAOUT8(vgaCRReg, restore->CR6D);

   /* Restore the desired video mode with CR67 */

   VGAOUT8(vgaCRIndex, 0x67);
   cr67 = VGAIN8(vgaCRReg) & 0xf; /* Possible hardware bug on VX? */
   VGAOUT8(vgaCRReg, 0x50 | cr67);
   usleep(10000);
   VGAOUT8(vgaCRIndex, 0x67);
   VGAOUT8(vgaCRReg, restore->CR67 & ~0x0c); /* Don't enable STREAMS yet */

   /* Other mode timing and extended regs */
   VGAOUT8(vgaCRIndex, 0x34);
   VGAOUT8(vgaCRReg, restore->CR34);
   if ( S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
	/* S3_ViRGE_MX_SERIES(ps3v->Chipset) || CR40 reserved on MX */
	S3_ViRGE_MXP_SERIES(ps3v->Chipset) ||
	S3_ViRGE_VX_SERIES(ps3v->Chipset) ||
	/* S3_TRIO_3D_2X_SERIES(ps3v->Chipset) * included in GX2 series */
	ps3v->Chipset == S3_ViRGE_DXGX ||
	ps3v->Chipset == S3_ViRGE
	)
     {
       VGAOUT8(vgaCRIndex, 0x40);
       VGAOUT8(vgaCRReg, restore->CR40);
     }
   if (S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
     VGAOUT8(vgaCRIndex, 0x41);
     VGAOUT8(vgaCRReg, restore->CR41);
   }
   VGAOUT8(vgaCRIndex, 0x42);
   VGAOUT8(vgaCRReg, restore->CR42);
   VGAOUT8(vgaCRIndex, 0x45);
   VGAOUT8(vgaCRReg, restore->CR45);
   VGAOUT8(vgaCRIndex, 0x51);
   VGAOUT8(vgaCRReg, restore->CR51);
   VGAOUT8(vgaCRIndex, 0x54);
   VGAOUT8(vgaCRReg, restore->CR54);

   /* Memory timings */
   VGAOUT8(vgaCRIndex, 0x36);
   VGAOUT8(vgaCRReg, restore->CR36);
   VGAOUT8(vgaCRIndex, 0x68);
   VGAOUT8(vgaCRReg, restore->CR68);
   VGAOUT8(vgaCRIndex, 0x69);
   VGAOUT8(vgaCRReg, restore->CR69);

   VGAOUT8(vgaCRIndex, 0x33);
   VGAOUT8(vgaCRReg, restore->CR33);
   if (S3_TRIO_3D_2X_SERIES(ps3v->Chipset) || S3_ViRGE_GX2_SERIES(ps3v->Chipset)
       /* MXTESTME */ || S3_ViRGE_MX_SERIES(ps3v->Chipset) )
   {
      VGAOUT8(vgaCRIndex, 0x85);
      VGAOUT8(vgaCRReg, restore->CR85);
   }
   if (ps3v->Chipset == S3_ViRGE_DXGX) {
      VGAOUT8(vgaCRIndex, 0x86);
      VGAOUT8(vgaCRReg, restore->CR86);
   }
   if ( (ps3v->Chipset == S3_ViRGE_GX2) ||
	S3_ViRGE_MX_SERIES(ps3v->Chipset) ) {
      VGAOUT8(vgaCRIndex, 0x7B);
      VGAOUT8(vgaCRReg, restore->CR7B);
      VGAOUT8(vgaCRIndex, 0x7D);
      VGAOUT8(vgaCRReg, restore->CR7D);
      VGAOUT8(vgaCRIndex, 0x87);
      VGAOUT8(vgaCRReg, restore->CR87);
      VGAOUT8(vgaCRIndex, 0x92);
      VGAOUT8(vgaCRReg, restore->CR92);
      VGAOUT8(vgaCRIndex, 0x93);
      VGAOUT8(vgaCRReg, restore->CR93);
   }
   if (ps3v->Chipset == S3_ViRGE_DXGX || S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
       S3_ViRGE_MX_SERIES(ps3v->Chipset) || S3_TRIO_3D_SERIES(ps3v->Chipset)) {
      VGAOUT8(vgaCRIndex, 0x90);
      VGAOUT8(vgaCRReg, restore->CR90);
      VGAOUT8(vgaCRIndex, 0x91);
      VGAOUT8(vgaCRReg, restore->CR91);
   }

   /* Unlock extended sequencer regs */
   VGAOUT8(0x3c4, 0x08);
   VGAOUT8(0x3c5, 0x06);


   /* Restore extended sequencer regs for MCLK. SR10 == 255 indicates that
    * we should leave the default SR10 and SR11 values there.
    */

   if (restore->SR10 != 255) {
       VGAOUT8(0x3c4, 0x10);
       VGAOUT8(0x3c5, restore->SR10);
       VGAOUT8(0x3c4, 0x11);
       VGAOUT8(0x3c5, restore->SR11);
       }

   /* Restore extended sequencer regs for DCLK */
   VGAOUT8(0x3c4, 0x12);
   VGAOUT8(0x3c5, restore->SR12);
   VGAOUT8(0x3c4, 0x13);
   VGAOUT8(0x3c5, restore->SR13);
   if (S3_ViRGE_GX2_SERIES(ps3v->Chipset) || S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
     VGAOUT8(0x3c4, 0x29);
     VGAOUT8(0x3c5, restore->SR29);
   }
   if (S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
     VGAOUT8(0x3c4, 0x54);
     VGAOUT8(0x3c5, restore->SR54);
     VGAOUT8(0x3c4, 0x55);
     VGAOUT8(0x3c5, restore->SR55);
     VGAOUT8(0x3c4, 0x56);
     VGAOUT8(0x3c5, restore->SR56);
     VGAOUT8(0x3c4, 0x57);
     VGAOUT8(0x3c5, restore->SR57);
   }

   VGAOUT8(0x3c4, 0x18);
   VGAOUT8(0x3c5, restore->SR18);

   /* Load new m,n PLL values for DCLK & MCLK */
   VGAOUT8(0x3c4, 0x15);
   tmp = VGAIN8(0x3c5) & ~0x21;

   /* databook either 0x3 or 0x20, but not both?? */
   VGAOUT8(0x3c5, tmp | 0x03);
   VGAOUT8(0x3c5, tmp | 0x23);
   VGAOUT8(0x3c5, tmp | 0x03);
   VGAOUT8(0x3c5, restore->SR15);
   if (S3_TRIO_3D_SERIES(ps3v->Chipset)) {
     VGAOUT8(0x3c4, 0x0a);
     VGAOUT8(0x3c5, restore->SR0A);
     VGAOUT8(0x3c4, 0x0f);
     VGAOUT8(0x3c5, restore->SR0F);
   }

   VGAOUT8(0x3c4, 0x08);
   VGAOUT8(0x3c5, restore->SR08);


   /* Now write out CR67 in full, possibly starting STREAMS */

   VerticalRetraceWait();
   VGAOUT8(vgaCRIndex, 0x67);
   VGAOUT8(vgaCRReg, 0x50);   /* For possible bug on VX?! */
   usleep(10000);
   VGAOUT8(vgaCRIndex, 0x67);
   VGAOUT8(vgaCRReg, restore->CR67);

   VGAOUT8(vgaCRIndex, 0x66);
   cr66 = VGAIN8(vgaCRReg);
   VGAOUT8(vgaCRReg, cr66 | 0x80);
   VGAOUT8(vgaCRIndex, 0x3a);

   /* workaround cr3a corruption */
   if( ps3v->mx_cr3a_fix )
     {
       VGAOUT8(vgaCRReg, restore->CR3A | 0x80);
     }
   else
     {
       cr3a = VGAIN8(vgaCRReg);
       VGAOUT8(vgaCRReg, cr3a | 0x80);
     }

   /* And finally, we init the STREAMS processor if we have CR67 indicate 24bpp
    * We also restore FIFO and TIMEOUT memory controller registers. (later...)
    */

   if (ps3v->NeedSTREAMS) {
      if(ps3v->STREAMSRunning) S3VRestoreSTREAMS(pScrn, restore->STREAMS);
      }

   /* Now, before we continue, check if this mode has the graphic engine ON
    * If yes, then we reset it.
    * This fixes some problems with corruption at 24bpp with STREAMS
    * Also restore the MIU registers.
    */

#ifndef MetroLink
   if(ps3v->Chipset == S3_ViRGE_VX) {
      if(restore->CR63 & 0x01) S3VGEReset(pScrn,0,__LINE__,__FILE__);
      }
   else {
      if(restore->CR66 & 0x01) S3VGEReset(pScrn,0,__LINE__,__FILE__);
      }
#else
   S3VGEReset(pScrn,0,__LINE__,__FILE__);
#endif

   VerticalRetraceWait();
   if (S3_ViRGE_GX2_SERIES(ps3v->Chipset)
       /* MXTESTME */ || S3_ViRGE_MX_SERIES(ps3v->Chipset) )
     {
      VGAOUT8(vgaCRIndex, 0x85);
      /* primary stream threshold */
      VGAOUT8(vgaCRReg, 0x1f );
     }
   else
     {
       OUTREG(FIFO_CONTROL_REG, restore->MMPR0);
     }
   if( !( S3_ViRGE_GX2_SERIES(ps3v->Chipset)
	  /* MXTESTME */ || S3_ViRGE_MX_SERIES(ps3v->Chipset) ))
   {
     WaitIdle();                  /* Don't ask... */
     OUTREG(MIU_CONTROL_REG, restore->MMPR1);
     WaitIdle();
     OUTREG(STREAMS_TIMEOUT_REG, restore->MMPR2);
     WaitIdle();
     OUTREG(MISC_TIMEOUT_REG, restore->MMPR3);
   }

   /* Restore the standard VGA registers */
   /* False indicates no fontinfo restore. */
   /* VGA_SR_MODE restores mode info only, no font, no colormap */
   					/* Do all for primary video */
   if (xf86IsPrimaryPci(ps3v->PciInfo))
     vgaHWRestore(pScrn, vgaSavePtr, VGA_SR_ALL);
   					/* Mode only for non-primary? */
   else
     vgaHWRestore(pScrn, vgaSavePtr, VGA_SR_MODE);
 		/* moved from before vgaHWRestore, to prevent segfault? */
   VGAOUT8(vgaCRIndex, 0x66);
   VGAOUT8(vgaCRReg, cr66);
   VGAOUT8(vgaCRIndex, 0x3a);

   /* workaround cr3a corruption */
   if( ps3v->mx_cr3a_fix )
     VGAOUT8(vgaCRReg, restore->CR3A);
   else
     VGAOUT8(vgaCRReg, cr3a);

   if (xf86GetVerbosity() > 1) {
      xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, VERBLEV,
         "ViRGE driver: done restoring mode, dumping CR registers:\n");
      S3VPrintRegs(pScrn);
   }

   vgaHWProtect(pScrn, FALSE);

   return;

}


/* This function restores the saved STREAMS registers */

static void
S3VRestoreSTREAMS(ScrnInfoPtr pScrn, unsigned int *streams)
{
  S3VPtr ps3v = S3VPTR(pScrn);


/* For now, set most regs to their default values for 24bpp
 * Restore only those that are needed for width/height/stride
 * Otherwise, we seem to get lockups because some registers
 * when saved have some reserved bits set.
 */

  OUTREG(PSTREAM_CONTROL_REG, streams[0] & 0x77000000);
  OUTREG(COL_CHROMA_KEY_CONTROL_REG, 0x00);
  OUTREG(SSTREAM_CONTROL_REG, 0x03000000);
  OUTREG(CHROMA_KEY_UPPER_BOUND_REG, 0x00);
  OUTREG(SSTREAM_STRETCH_REG, 0x00);
  OUTREG(BLEND_CONTROL_REG, 0x01000000);
  OUTREG(PSTREAM_FBADDR0_REG, 0x00);
  OUTREG(PSTREAM_FBADDR1_REG, 0x00);
  OUTREG(PSTREAM_STRIDE_REG, streams[8] & 0x0fff);
  OUTREG(DOUBLE_BUFFER_REG, 0x00);
  OUTREG(SSTREAM_FBADDR0_REG, 0x00);
  OUTREG(SSTREAM_FBADDR1_REG, 0x00);
  OUTREG(SSTREAM_STRIDE_REG, 0x01);
  OUTREG(OPAQUE_OVERLAY_CONTROL_REG, 0x40000000);
  OUTREG(K1_VSCALE_REG, 0x00);
  OUTREG(K2_VSCALE_REG, 0x00);
  OUTREG(DDA_VERT_REG, 0x00);
  OUTREG(PSTREAM_START_REG, 0x00010001);
  OUTREG(PSTREAM_WINDOW_SIZE_REG, streams[19] & 0x07ff07ff);
  OUTREG(SSTREAM_START_REG, 0x07ff07ff);
  OUTREG(SSTREAM_WINDOW_SIZE_REG, 0x00010001);


}




/* And this function disables the STREAMS processor as per databook.
 * This is useful before we do a mode change
 */

static void
S3VDisableSTREAMS(ScrnInfoPtr pScrn)
{
unsigned char tmp;
  vgaHWPtr hwp = VGAHWPTR(pScrn);
  S3VPtr ps3v = S3VPTR(pScrn);
  int vgaCRIndex, vgaCRReg, vgaIOBase;
  vgaIOBase = hwp->IOBase;
  vgaCRIndex = vgaIOBase + 4;
  vgaCRReg = vgaIOBase + 5;

   VerticalRetraceWait();
   OUTREG(FIFO_CONTROL_REG, 0xC000);
   VGAOUT8(vgaCRIndex, 0x67);
   tmp = VGAIN8(vgaCRReg);
                         /* Disable STREAMS processor */
   VGAOUT8( vgaCRReg, tmp & ~0x0C );

   return;
}



/* MapMem - contains half of pre-4.0 EnterLeave function */
/* The EnterLeave function which en/dis access to IO ports and ext. regs */
                /********************************************************/
		/* Aaagh...  So many locations!  On my machine (KJB) the*/
		/* following is true. 					*/
		/* PciInfo->memBase[0] returns e400 0000 		*/
		/* From my ViRGE manual, the memory map looks like 	*/
		/* Linear mem - 16M  	000 0000 - 0ff ffff 		*/
		/* Image xfer - 32k  	100 0000 - 100 7fff 		*/
		/* PCI cnfg    		100 8000 - 100 8043 		*/
		/* ...				   			*/
		/* CRT VGA 3b? reg	100 83b0 - 			*/
		/* And S3_NEWMMIO_VGABASE = S3_NEWMMIO_REGBASE + 0x8000	*/
		/* where S3_NEWMMIO_REGBASE = 0x100 0000  ( 16MB )      */
		/* S3_NEWMMIO_REGSIZE = 0x1 0000  ( 64KB )		*/
		/* S3V_MMIO_REGSIZE = 0x8000 ( 32KB ) - above includes	*/
		/* the image transfer area, so this one is used instead.*/
		/* ps3v->IOBase is assigned the virtual address returned*/
		/* from MapPciMem, it is the address to base all 	*/
		/* register access. (It is a pointer.)  		*/
		/* hwp->MemBase is a CARD32, containing the register	*/
		/* base. (It's a conversion from IOBase above.) 	*/
                /********************************************************/


static Bool
S3VMapMem(ScrnInfoPtr pScrn)
{
  S3VPtr ps3v;
  vgaHWPtr hwp;

    PVERB5("	S3VMapMem\n");

  ps3v = S3VPTR(pScrn);

    					/* Map the ViRGE register space */
					/* Starts with Image Transfer area */
					/* so that we can use registers map */
					/* structure - see newmmio.h */
					/* around 0x10000 from MemBase */
  {
    void** result = (void**)&ps3v->MapBase;
    int err = pci_device_map_range(ps3v->PciInfo,
				   PCI_REGION_BASE(ps3v->PciInfo, 0, REGION_MEM) + S3_NEWMMIO_REGBASE,
				   S3_NEWMMIO_REGSIZE,
				   PCI_DEV_MAP_FLAG_WRITABLE,
				   result);

    if (err)
      return FALSE;
  }
  ps3v->MapBaseDense = ps3v->MapBase;

  if( !ps3v->MapBase ) {
    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
	"Internal error: could not map registers.\n");
    return FALSE;
  }
					/* Map the framebuffer */
  if (ps3v->videoRambytes) { /* not set in PreInit() */
      {
	void** result = (void**)&ps3v->FBBase;
	int err = pci_device_map_range(ps3v->PciInfo,
				       PCI_REGION_BASE(ps3v->PciInfo, 0, REGION_MEM),
				       ps3v->videoRambytes,
				       PCI_DEV_MAP_FLAG_WRITABLE |
				       PCI_DEV_MAP_FLAG_WRITE_COMBINE,
				       result);

	if (err)
	  return FALSE;
      }

      if( !ps3v->FBBase ) {
	  xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		     "Internal error: could not map framebuffer.\n");
	  return FALSE;
      }
  		       		/* Initially the visual display start */
				/* is the same as the mapped start. */
      ps3v->FBStart = ps3v->FBBase;
  }

  pScrn->memPhysBase = PCI_REGION_BASE(ps3v->PciInfo, 0, REGION_MEM);
  pScrn->fbOffset = 0;

  				/* Set up offset to hwcursor memory area */
  				/* It's a 1K chunk at the end of the frame buffer */
  ps3v->FBCursorOffset = ps3v->videoRambytes - 1024;
  S3VEnableMmio( pScrn);
   					/* Assign hwp->MemBase & IOBase here */
  hwp = VGAHWPTR(pScrn);
					/* Sets MMIOBase and Offset, assigns */
					/* functions. Offset from map area   */
					/* to VGA reg area is 0x8000. */
  vgaHWSetMmioFuncs( hwp, ps3v->MapBase, S3V_MMIO_REGSIZE );
  					/* assigns hwp->IOBase to 3D0 or 3B0 */
					/* needs hwp->MMIOBase to work */
  vgaHWGetIOBase(hwp);

    					/* Map the VGA memory when the */
					/* primary video */

  if (xf86IsPrimaryPci(ps3v->PciInfo)) {
    hwp->MapSize = 0x10000;
    if (!vgaHWMapMem(pScrn))
      return FALSE;
    ps3v->PrimaryVidMapped = TRUE;
  }

  return TRUE;
}



/* UnMapMem - contains half of pre-4.0 EnterLeave function */
/* The EnterLeave function which en/dis access to IO ports and ext. regs */

static void
S3VUnmapMem(ScrnInfoPtr pScrn)
{
  S3VPtr ps3v;

  ps3v = S3VPTR(pScrn);
					/* Unmap VGA mem if mapped. */
  if( ps3v->PrimaryVidMapped ) {
    vgaHWUnmapMem( pScrn );
    ps3v->PrimaryVidMapped = FALSE;
  }

  pci_device_unmap_range(ps3v->PciInfo, ps3v->MapBase,
			 S3_NEWMMIO_REGSIZE);

  pci_device_unmap_range(ps3v->PciInfo, ps3v->FBBase,
			 ps3v->videoRambytes);
  return;
}



/* Mandatory */

/* This gets called at the start of each server generation */

static Bool
S3VScreenInit(ScreenPtr pScreen, int argc, char **argv)
{
  ScrnInfoPtr pScrn;
  S3VPtr ps3v;
  int ret;

  PVERB5("	S3VScreenInit\n");
                                        /* First get the ScrnInfoRec */
  pScrn = xf86ScreenToScrn(pScreen);
  					/* Get S3V rec */
  ps3v = S3VPTR(pScrn);
   					/* Map MMIO regs and framebuffer */
  if( !S3VMapMem(pScrn) )
    return FALSE;
    					/* Save the chip/graphics state */
  S3VSave(pScrn);
				 	/* Blank the screen during init */
  vgaHWBlankScreen(pScrn, TRUE );
    					/* Initialise the first mode */
  if (!S3VModeInit(pScrn, pScrn->currentMode))
    return FALSE;

    /*
     * The next step is to setup the screen's visuals, and initialise the
     * framebuffer code.  In cases where the framebuffer's default
     * choices for things like visual layouts and bits per RGB are OK,
     * this may be as simple as calling the framebuffer's ScreenInit()
     * function.  If not, the visuals will need to be setup before calling
     * a fb ScreenInit() function and fixed up after.
     *
     * For most PC hardware at depths >= 8, the defaults that fb uses
     * are not appropriate.  In this driver, we fixup the visuals after.
     */

    /*
     * Reset the visual list.
     */
  miClearVisualTypes();

    /* Setup the visuals we support. */

    /*
     * For bpp > 8, the default visuals are not acceptable because we only
     * support TrueColor and not DirectColor.  To deal with this, call
     * miSetVisualTypes with the appropriate visual mask.
     */

  if (pScrn->bitsPerPixel > 8) {
	if (!miSetVisualTypes(pScrn->depth, TrueColorMask, pScrn->rgbBits,
				pScrn->defaultVisual))
	    return FALSE;

	if (!miSetPixmapDepths ())
	    return FALSE;
  } else {
	if (!miSetVisualTypes(pScrn->depth,
			      miGetDefaultVisualMask(pScrn->depth),
			      pScrn->rgbBits, pScrn->defaultVisual))
	    return FALSE;

	if (!miSetPixmapDepths ())
	    return FALSE;
  }

  ret = S3VInternalScreenInit(pScrn, pScreen);

  if (!ret)
    return FALSE;

  xf86SetBlackWhitePixels(pScreen);

  if (pScrn->bitsPerPixel > 8) {
    	VisualPtr visual;
					/* Fixup RGB ordering */
	visual = pScreen->visuals + pScreen->numVisuals;
	while (--visual >= pScreen->visuals) {
	    if ((visual->class | DynamicClass) == DirectColor) {
		visual->offsetRed = pScrn->offset.red;
		visual->offsetGreen = pScrn->offset.green;
		visual->offsetBlue = pScrn->offset.blue;
		visual->redMask = pScrn->mask.red;
		visual->greenMask = pScrn->mask.green;
		visual->blueMask = pScrn->mask.blue;
	    }
	}
  }

  /* must be after RGB ordering fixed */
  fbPictureInit (pScreen, 0, 0);

  	      				/* Initialize acceleration layer */
  if (!ps3v->NoAccel) {
    if(pScrn->bitsPerPixel == 32) {
      /* 32 bit Accel currently broken
      if (!S3VAccelInit32(pScreen))
        return FALSE;
	*/
	;
    } else
      if (!S3VAccelInit(pScreen))
        return FALSE;
  }

  xf86SetBackingStore(pScreen);
  xf86SetSilkenMouse(pScreen);
  						/* hardware cursor needs to wrap this layer */
  S3VDGAInit(pScreen);

    					/* Initialise cursor functions */
  miDCInitialize(pScreen, xf86GetPointerScreenFuncs());

    /* Initialize HW cursor layer.
	Must follow software cursor initialization*/
  if (ps3v->hwcursor) {
  if(!S3VHWCursorInit(pScreen)) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		"Hardware cursor initialization failed\n");
		}
  }

  if (ps3v->shadowFB) {
      RefreshAreaFuncPtr refreshArea = s3vRefreshArea;

      if(ps3v->rotate) {
	  if (!ps3v->PointerMoved) {
	      ps3v->PointerMoved = pScrn->PointerMoved;
	      pScrn->PointerMoved = s3vPointerMoved;
	  }

	  switch(pScrn->bitsPerPixel) {
	  case 8:	refreshArea = s3vRefreshArea8;	break;
	  case 16:	refreshArea = s3vRefreshArea16;	break;
	  case 24:	refreshArea = s3vRefreshArea24;	break;
	  case 32:	refreshArea = s3vRefreshArea32;	break;
	  }
      }

      ShadowFBInit(pScreen, refreshArea);
  }

    					/* Initialise default colourmap */
  if (!miCreateDefColormap(pScreen))
    return FALSE;
  					/* Initialize colormap layer.   */
					/* Must follow initialization   */
					/* of the default colormap. 	*/
					/* And SetGamma call, else it 	*/
					/* will load palette with solid */
					/* white. */
  if(!xf86HandleColormaps(pScreen, 256, 6, S3VLoadPalette, NULL,
			CMAP_RELOAD_ON_MODE_SWITCH ))
	return FALSE;
				    	/* All the ugly stuff is done, 	*/
					/* so re-enable the screen. 	*/
  vgaHWBlankScreen(pScrn, FALSE );

#if 0
  pScrn->racMemFlags = RAC_COLORMAP | RAC_CURSOR | RAC_FB | RAC_VIEWPORT;
#endif
  pScreen->SaveScreen = S3VSaveScreen;

    					/* Wrap the current CloseScreen function */
  ps3v->CloseScreen = pScreen->CloseScreen;
  pScreen->CloseScreen = S3VCloseScreen;

  if(xf86DPMSInit(pScreen, S3VDisplayPowerManagementSet, 0) == FALSE)
    xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "DPMS initialization failed!\n");

  S3VInitVideo(pScreen);

    /* Report any unused options (only for the first generation) */
  if (serverGeneration == 1) {
    xf86ShowUnusedOptions(pScrn->scrnIndex, pScrn->options);
  }
    					/* Done */
  return TRUE;
}



/* Common init routines needed in EnterVT and ScreenInit */

static int
S3VInternalScreenInit(ScrnInfoPtr pScrn, ScreenPtr pScreen)
{
  int ret = TRUE;
  S3VPtr ps3v;
  int width, height, displayWidth;
  unsigned char* FBStart;

  ps3v = S3VPTR(pScrn);

  displayWidth = pScrn->displayWidth;
  if (ps3v->rotate) {
      height = pScrn->virtualX;
      width = pScrn->virtualY;
  } else {
      width = pScrn->virtualX;
      height = pScrn->virtualY;
  }

  if(ps3v->shadowFB) {
      ps3v->ShadowPitch = BitmapBytePad(pScrn->bitsPerPixel * width);
      ps3v->ShadowPtr = malloc(ps3v->ShadowPitch * height);
      displayWidth = ps3v->ShadowPitch / (pScrn->bitsPerPixel >> 3);
      FBStart = ps3v->ShadowPtr;
  } else {
      ps3v->ShadowPtr = NULL;
      FBStart = ps3v->FBStart;
  }

    /*
     * Call the framebuffer layer's ScreenInit function, and fill in other
     * pScreen fields.
     */

    switch (pScrn->bitsPerPixel)
    {
	case 8:
	case 16:
	case 24:
	case 32:
	    ret = fbScreenInit(pScreen, FBStart, width,
			       height, pScrn->xDpi, pScrn->yDpi,
			       displayWidth, pScrn->bitsPerPixel);
	    break;
	default:
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		       "Internal error: invalid bpp (%d) in S3VScreenInit\n",
		       pScrn->bitsPerPixel);
	    ret = FALSE;
	    break;
    }

  return ret;
}



/* Checks if a mode is suitable for the selected chipset. */

static ModeStatus
S3VValidMode(ScrnInfoPtr pScrn, DisplayModePtr mode, Bool verbose, int flags)
{
    if ((pScrn->bitsPerPixel + 7)/8 * mode->HDisplay > 4095)
	return MODE_VIRTUAL_X;

    /* todo -  The virge limit is 2048 vertical & horizontal */
    /* pixels, not clock register settings. */
				/* true for all ViRGE? */
    if (mode->HTotal > 2048)
        return MODE_BAD_HVALUE;

    if (mode->VTotal > 2048)
        return MODE_BAD_VVALUE;

    return MODE_OK;
}



static Bool
S3VModeInit(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
  vgaHWPtr hwp = VGAHWPTR(pScrn);
  S3VPtr ps3v = S3VPTR(pScrn);
  int width, dclk;
  int i, j;

  		      		/* Store values to current mode register structs */
  S3VRegPtr new = &ps3v->ModeReg;
  vgaRegPtr vganew = &hwp->ModeReg;
  int vgaCRIndex, vgaCRReg, vgaIOBase;

  vgaIOBase = hwp->IOBase;
  vgaCRIndex = vgaIOBase + 4;
  vgaCRReg = vgaIOBase + 5;

    PVERB5("	S3VModeInit\n");

    /* Set scale factors for mode timings */

    if (ps3v->Chipset == S3_ViRGE_VX || S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
	S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
      ps3v->HorizScaleFactor = 1;
    }
    else if (pScrn->bitsPerPixel == 8) {
      ps3v->HorizScaleFactor = 1;
    }
    else if (pScrn->bitsPerPixel == 16) {
      if (S3_TRIO_3D_SERIES(ps3v->Chipset) && mode->Clock > 115000)
	ps3v->HorizScaleFactor = 1;
      else
	ps3v->HorizScaleFactor = 2;
    }
    else {
      ps3v->HorizScaleFactor = 1;
    }


   /* First we adjust the horizontal timings if needed */

   if(ps3v->HorizScaleFactor != 1)
      if (!mode->CrtcHAdjusted) {
             mode->CrtcHDisplay *= ps3v->HorizScaleFactor;
             mode->CrtcHSyncStart *= ps3v->HorizScaleFactor;
             mode->CrtcHSyncEnd *= ps3v->HorizScaleFactor;
             mode->CrtcHTotal *= ps3v->HorizScaleFactor;
             mode->CrtcHSkew *= ps3v->HorizScaleFactor;
             mode->CrtcHAdjusted = TRUE;
             }

   if(!vgaHWInit (pScrn, mode))
      return FALSE;

   /* Now we fill in the rest of the stuff we need for the virge */
   /* Start with MMIO, linear addr. regs */

   VGAOUT8(vgaCRIndex, 0x3a);
   {
       unsigned char tmp = VGAIN8(vgaCRReg);
       if (S3_ViRGE_GX2_SERIES(ps3v->Chipset)
           /* MXTESTME */ || S3_ViRGE_MX_SERIES(ps3v->Chipset) )
       {
           if (ps3v->pci_burst)
               /*new->CR3A = (tmp & 0x38) | 0x10; / ENH 256, PCI burst */
               /* Don't clear reserved bits... */
               new->CR3A = (tmp & 0x7f) | 0x10; /* ENH 256, PCI burst */
           else
               new->CR3A = tmp | 0x90;      /* ENH 256, no PCI burst! */
       }
       else
       {
           if (ps3v->pci_burst)
               new->CR3A = (tmp & 0x7f) | 0x15; /* ENH 256, PCI burst */
           else
               new->CR3A = tmp | 0x95;      /* ENH 256, no PCI burst! */
       }
   }


   VGAOUT8(vgaCRIndex, 0x55);
   new->CR55 = VGAIN8(vgaCRReg);
   if (ps3v->hwcursor)
     new->CR55 |= 0x10;  /* Enables X11 hw cursor mode */
   if (S3_TRIO_3D_SERIES(ps3v->Chipset)) {
     new->CR31 = 0x0c;               /* [trio3d] page 54 */
   } else {
     new->CR53 = 0x08;     /* Enables MMIO */
     new->CR31 = 0x8c;     /* Dis. 64k window, en. ENH maps */
   }

   /* Enables S3D graphic engine and PCI disconnects */
   if(ps3v->Chipset == S3_ViRGE_VX){
      new->CR66 = 0x90;
      new->CR63 = 0x09;
      }
   else {
     new->CR66 = 0x89;
     /* Set display fifo */
     if( S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
	 S3_ViRGE_MX_SERIES(ps3v->Chipset) )
       {
	 /* Changed from 0x08 based on reports that this */
	 /* prevents MX from running properly below 1024x768 */
	 new->CR63 = 0x10;
       }
     else
       {
	 new->CR63 = 0;
       }
      }

  /* Now set linear addr. registers */
  /* LAW size: we have 2 cases, 2MB, 4MB or >= 4MB for VX */
   VGAOUT8(vgaCRIndex, 0x58);
   new->CR58 = VGAIN8(vgaCRReg) & 0x80;
   if(pScrn->videoRam == 2048){
      new->CR58 |= 0x02 | 0x10;
      }
   else if (pScrn->videoRam == 1024) {
      new->CR58 |= 0x01 | 0x10;
   }
   else {
     if (S3_TRIO_3D_2X_SERIES(ps3v->Chipset) && pScrn->videoRam == 8192)
       new->CR58 |= 0x07 | 0x10; /* 8MB window on Trio3D/2X */
     else
       new->CR58 |= 0x03 | 0x10; /* 4MB window on virge, 8MB on VX */
      }
   if(ps3v->Chipset == S3_ViRGE_VX)
      new->CR58 |= 0x40;
   if (ps3v->early_ras_precharge)
      new->CR58 |= 0x80;
   if (ps3v->late_ras_precharge)
      new->CR58 &= 0x7f;

  /* ** On PCI bus, no need to reprogram the linear window base address */

  /* Now do clock PLL programming. Use the s3gendac function to get m,n */
  /* Also determine if we need doubling etc. */

   dclk = mode->Clock;
   new->CR67 = 0x00;             /* Defaults */

   if (!S3_TRIO_3D_SERIES(ps3v->Chipset))
     new->SR15 = 0x03 | 0x80;
   else {
     VGAOUT8(0x3c4, 0x15);
     new->SR15 = VGAIN8(0x3c5);
     VGAOUT8(0x3c4, 0x0a);
     new->SR0A = VGAIN8(0x3c5);
     if (ps3v->slow_dram) {
       new->SR15 = 0x03;  /* 3 CYC MWR */
       new->SR0A &= 0x7F;
     } else if (ps3v->fast_dram) {
       new->SR15 = 0x03 | 0x80; /* 2 CYC MWR */
       new->SR0A |= 0x80;
     } else { /* keep BIOS init defaults */
       new->SR15 = (new->SR15 & 0x80) | 0x03;
     }
   }
   new->SR18 = 0x00;
   new->CR43 = 0x00;
   new->CR45 = 0x00;
   				/* Enable MMIO to RAMDAC registers */
   new->CR65 = 0x00;		/* CR65_2 must be zero, doc seems to be wrong */
   new->CR54 = 0x00;

   if ( S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
	/* S3_ViRGE_MX_SERIES(ps3v->Chipset) || CR40 reserved on MX */
	S3_ViRGE_MXP_SERIES(ps3v->Chipset) ||
	S3_ViRGE_VX_SERIES(ps3v->Chipset) ||
	/* S3_TRIO_3D_2X_SERIES(ps3v->Chipset) * included in GX2 series */
	ps3v->Chipset == S3_ViRGE_DXGX ||
	ps3v->Chipset == S3_ViRGE
	) {
     VGAOUT8(vgaCRIndex, 0x40);
     new->CR40 = VGAIN8(vgaCRReg) & ~0x01;
   }

   if (S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
     /* fix problems with APM suspend/resume trashing CR90/91 */
     switch(pScrn->bitsPerPixel) {
       case  8: new->CR41 = 0x38; break;
       case 15: new->CR41 = 0x58; break;
       case 16: new->CR41 = 0x48; break;
       default: new->CR41 = 0x77;
     }
   }

    xf86ErrorFVerb(VERBLEV, "	S3VModeInit dclk=%i \n",
   	dclk
	);

   /* Memory controller registers. Optimize for better graphics engine
    * performance. These settings are adjusted/overridden below for other bpp/
    * XConfig options.The idea here is to give a longer number of contiguous
    * MCLK's to both refresh and the graphics engine, to diminish the
    * relative penalty of 3 or 4 mclk's needed to setup memory transfers.
    */
   new->MMPR0 = 0x010400; /* defaults */
   new->MMPR1 = 0x00;
   new->MMPR2 = 0x0808;
   new->MMPR3 = 0x08080810;

   /*
    * These settings look like they ought to be better adjusted for depth,
    * so for problem modes running without any fifo_ option should be
    * usable.  Note that these adjust some memory timings and relate to
    * the boards MCLK setting.
    * */
    if( ps3v->fifo_aggressive || ps3v->fifo_moderate ||
       ps3v->fifo_conservative ) {

         new->MMPR1 = 0x0200;   /* Low P. stream waits before filling */
         new->MMPR2 = 0x1808;   /* Let the FIFO refill itself */
         new->MMPR3 = 0x08081810; /* And let the GE hold the bus for a while */
      }

   /* And setup here the new value for MCLK. We use the XConfig
    * option "set_mclk", whose value gets stored in ps3v->MCLK.
    * I'm not sure what the maximum "permitted" value should be, probably
    * 100 MHz is more than enough for now.
    */

   if(ps3v->MCLK> 0) {
       if (S3_ViRGE_MX_SERIES(ps3v->Chipset))
	  S3VCommonCalcClock(pScrn, mode,
			     (int)(ps3v->MCLK / ps3v->refclk_fact),
			     1, 1, 31, 0, 3,
			     135000, 270000, &new->SR11, &new->SR10);
       else
	  S3VCommonCalcClock(pScrn, mode, ps3v->MCLK, 1, 1, 31, 0, 3,
			     135000, 270000, &new->SR11, &new->SR10);
       }
   else {
       new->SR10 = 255; /* This is a reserved value, so we use as flag */
       new->SR11 = 255;
       }

   					/* most modes don't need STREAMS */
					/* processor, preset FALSE */
   /* support for XVideo needs streams, so added it to some modes */
   ps3v->NeedSTREAMS = FALSE;

   if(ps3v->Chipset == S3_ViRGE_VX){
       if (pScrn->bitsPerPixel == 8) {
          if (dclk <= 110000) new->CR67 = 0x00; /* 8bpp, 135MHz */
          else new->CR67 = 0x10;                /* 8bpp, 220MHz */
          }
       else if ((pScrn->bitsPerPixel == 16) && (pScrn->weight.green == 5)) {
          if (dclk <= 110000) new->CR67 = 0x20; /* 15bpp, 135MHz */
          else new->CR67 = 0x30;                /* 15bpp, 220MHz */
          }
       else if (pScrn->bitsPerPixel == 16) {
          if (dclk <= 110000) new->CR67 = 0x40; /* 16bpp, 135MHz */
          else new->CR67 = 0x50;                /* 16bpp, 220MHz */
          }
       else if ((pScrn->bitsPerPixel == 24) || (pScrn->bitsPerPixel == 32)) {
          new->CR67 = 0xd0 | 0x0c;              /* 24bpp, 135MHz, STREAMS */
	  					/* Flag STREAMS proc. required */
          ps3v->NeedSTREAMS = TRUE;
          S3VInitSTREAMS(pScrn, new->STREAMS, mode);
          new->MMPR0 = 0xc098;            /* Adjust FIFO slots */
          }
       S3VCommonCalcClock(pScrn, mode, dclk, 1, 1, 31, 0, 4,
	   220000, 440000, &new->SR13, &new->SR12);

      } /* end VX if() */
   else if (S3_ViRGE_GX2_SERIES(ps3v->Chipset) || S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
       if (pScrn->bitsPerPixel == 8)
	  new->CR67 = 0x00;
       else if (pScrn->bitsPerPixel == 16) {
	 /* XV support needs STREAMS in depth 16 */
          ps3v->NeedSTREAMS = TRUE;
          S3VInitSTREAMS(pScrn, new->STREAMS, mode);
	  if (pScrn->weight.green == 5)
	     new->CR67 = 0x30 | 0x4;                  /* 15bpp */
	  else
	     new->CR67 = 0x50 | 0x4;                  /* 16bpp */
          }
       else if ((pScrn->bitsPerPixel == 24) ) {
	 new->CR67 = 0x74;              /* 24bpp, STREAMS */
	  					/* Flag STREAMS proc. required */
          ps3v->NeedSTREAMS = TRUE;
          S3VInitSTREAMS(pScrn, new->STREAMS, mode);
          }
       else if (pScrn->bitsPerPixel == 32) {
          new->CR67 = 0xd0;              /* 32bpp */
	  	/* Missing STREAMs and other stuff here? KJB */
          /* new->MMPR0 = 0xc098;            / Adjust FIFO slots */
          }
       {
         unsigned char ndiv;
	 if (S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
	   unsigned char sr8;
	   VGAOUT8(0x3c4, 0x08);  /* unlock extended SEQ regs */
	   sr8 = VGAIN8(0x3c5);
	   VGAOUT8(0x3c5, 0x06);
	   VGAOUT8(0x3c4, 0x31);
	   if (VGAIN8(0x3c5) & 0x10) { /* LCD on */
	     if (!ps3v->LCDClk) {  /* entered only once for first mode */
	       int h_lcd, v_lcd;
	       VGAOUT8(0x3c4, 0x61);
	       h_lcd = VGAIN8(0x3c5);
	       VGAOUT8(0x3c4, 0x66);
	       h_lcd |= ((VGAIN8(0x3c5) & 0x02) << 7);
	       h_lcd = (h_lcd+1) * 8;
	       VGAOUT8(0x3c4, 0x69);
	       v_lcd = VGAIN8(0x3c5);
	       VGAOUT8(0x3c4, 0x6e);
	       v_lcd |= ((VGAIN8(0x3c5) & 0x70) << 4);
	       v_lcd++;

	       /* check if first mode has physical LCD resolution */
	       if (pScrn->modes->HDisplay == h_lcd && pScrn->modes->VDisplay == v_lcd)
		 ps3v->LCDClk = mode->Clock;
	       else {
		 int n1, n2, sr12, sr13, sr29;
		 VGAOUT8(0x3c4, 0x12);
		 sr12 = VGAIN8(0x3c5);
		 VGAOUT8(0x3c4, 0x13);
		 sr13 = VGAIN8(0x3c5) & 0x7f;
		 VGAOUT8(0x3c4, 0x29);
		 sr29 = VGAIN8(0x3c5);
		 n1 = sr12 & 0x1f;
		 n2 = ((sr12>>6) & 0x03) | ((sr29 & 0x01) << 2);
		 ps3v->LCDClk = ((int)(ps3v->refclk_fact * 1431818 * (sr13+2)) / (n1+2) / (1 << n2) + 50) / 100;
	       }
	     }
	     S3VCommonCalcClock(pScrn, mode,
			     (int)(ps3v->LCDClk / ps3v->refclk_fact),
			     1, 1, 31, 0, 4,
			     170000, 340000, &new->SR13, &ndiv);
	   }
	   else
	     S3VCommonCalcClock(pScrn, mode,
			     (int)(dclk / ps3v->refclk_fact),
			     1, 1, 31, 0, 4,
			     170000, 340000, &new->SR13, &ndiv);
	   VGAOUT8(0x3c4, 0x08);
	   VGAOUT8(0x3c5, sr8);
	 }
	 else  /* S3_ViRGE_GX2 */
	   S3VCommonCalcClock(pScrn, mode, dclk, 1, 1, 31, 0, 4,
			   170000, 340000, &new->SR13, &ndiv);
         new->SR29 = ndiv >> 7;
         new->SR12 = (ndiv & 0x1f) | ((ndiv & 0x60) << 1);
       }
   } /* end GX2 or MX if() */
   else if(S3_TRIO_3D_SERIES(ps3v->Chipset)) {
      new->SR0F = 0x00;
      if (pScrn->bitsPerPixel == 8) {
         if(dclk > 115000) {                     /* We need pixmux */
            new->CR67 = 0x10;
            new->SR15 |= 0x10;                   /* Set DCLK/2 bit */
            new->SR18 = 0x80;                   /* Enable pixmux */
        }
      }
      else if ((pScrn->bitsPerPixel == 16) && (pScrn->weight.green == 5)) {
        if(dclk > 115000) {
           new->CR67 = 0x20;
           new->SR15 |= 0x10;
           new->SR18 = 0x80;
	   new->SR0F = 0x10;
        } else {
           new->CR67 = 0x30;                       /* 15bpp */
        }
      }
      else if (pScrn->bitsPerPixel == 16) {
        if(dclk > 115000) {
            new->CR67 = 0x40;
            new->SR15 |= 0x10;
            new->SR18 = 0x80;
	    new->SR0F = 0x10;
        } else {
           new->CR67 = 0x50;
        }
      }
      else if (pScrn->bitsPerPixel == 24) {
         new->CR67 = 0xd0 | 0x0c;
	 ps3v->NeedSTREAMS = TRUE;
         S3VInitSTREAMS(pScrn, new->STREAMS, mode);
         new->MMPR0 = 0xc000;            /* Adjust FIFO slots */
      }
      else if (pScrn->bitsPerPixel == 32) {
         new->CR67 = 0xd0 | 0x0c;
	 ps3v->NeedSTREAMS = TRUE;
         S3VInitSTREAMS(pScrn, new->STREAMS, mode);
         new->MMPR0 = 0x10000;            /* Still more FIFO slots */
	 new->SR0F = 0x10;
      }
      S3VCommonCalcClock(pScrn, mode, dclk, 1, 1, 31, 0, 4,
                     230000, 460000, &new->SR13, &new->SR12);
   } /* end TRIO_3D if() */
   else if(ps3v->Chipset == S3_ViRGE_DXGX) {
      if (pScrn->bitsPerPixel == 8) {
         if(dclk > 80000) {                     /* We need pixmux */
            new->CR67 = 0x10;
            new->SR15 |= 0x10;                   /* Set DCLK/2 bit */
            new->SR18 = 0x80;                   /* Enable pixmux */
            }
         }
      else if ((pScrn->bitsPerPixel == 16) && (pScrn->weight.green == 5)) {
         new->CR67 = 0x30;                       /* 15bpp */
         }
      else if (pScrn->bitsPerPixel == 16) {
	if(mode->Flags & V_DBLSCAN)
	  {
	    new->CR67 = 0x50;
	  }
	else
	  {
	    new->CR67 = 0x50 | 0x0c;
	    /* Flag STREAMS proc. required */
	    /* XV support needs STREAMS in depth 16 */
	    ps3v->NeedSTREAMS = TRUE;
	    S3VInitSTREAMS(pScrn, new->STREAMS, mode);
	  }
	 if( ps3v->XVideo )
	   {
	     new->MMPR0 = 0x107c02;            /* Adjust FIFO slots, overlay */
	   }
	 else
	   {
	     new->MMPR0 = 0xc000;            /* Adjust FIFO slots */
	   }
         }
      else if (pScrn->bitsPerPixel == 24) {
         new->CR67 = 0xd0 | 0x0c;
	  					/* Flag STREAMS proc. required */
         ps3v->NeedSTREAMS = TRUE;
         S3VInitSTREAMS(pScrn, new->STREAMS, mode);
	 if( ps3v->XVideo )
	   {
	     new->MMPR0 = 0x107c02;            /* Adjust FIFO slots, overlay */
	   }
	 else
	   {
	     new->MMPR0 = 0xc000;            /* Adjust FIFO slots */
	   }
         }
      else if (pScrn->bitsPerPixel == 32) {
         new->CR67 = 0xd0 | 0x0c;
	  					/* Flag STREAMS proc. required */
         ps3v->NeedSTREAMS = TRUE;
         S3VInitSTREAMS(pScrn, new->STREAMS, mode);
         new->MMPR0 = 0x10000;            /* Still more FIFO slots */
         }
      S3VCommonCalcClock(pScrn, mode, dclk, 1, 1, 31, 0, 3,
	135000, 270000, &new->SR13, &new->SR12);
   } /* end DXGX if() */
   else {           /* Everything else ... (only ViRGE) */
      if (pScrn->bitsPerPixel == 8) {
         if(dclk > 80000) {                     /* We need pixmux */
            new->CR67 = 0x10;
            new->SR15 |= 0x10;                   /* Set DCLK/2 bit */
            new->SR18 = 0x80;                   /* Enable pixmux */
            }
         }
      else if ((pScrn->bitsPerPixel == 16) && (pScrn->weight.green == 5)) {
         new->CR67 = 0x30;                       /* 15bpp */
         }
      else if (pScrn->bitsPerPixel == 16) {
         new->CR67 = 0x50;
         }
      else if (pScrn->bitsPerPixel == 24) {
         new->CR67 = 0xd0 | 0x0c;
	  					/* Flag STREAMS proc. required */
         ps3v->NeedSTREAMS = TRUE;
         S3VInitSTREAMS(pScrn, new->STREAMS, mode);
	 new->MMPR0 = 0xc000;            /* Adjust FIFO slots */
         }
      else if (pScrn->bitsPerPixel == 32) {
         new->CR67 = 0xd0 | 0x0c;
	  					/* Flag STREAMS proc. required */
         ps3v->NeedSTREAMS = TRUE;
         S3VInitSTREAMS(pScrn, new->STREAMS, mode);
         new->MMPR0 = 0x10000;            /* Still more FIFO slots */
         }
      S3VCommonCalcClock(pScrn, mode, dclk, 1, 1, 31, 0, 3,
	135000, 270000, &new->SR13, &new->SR12);
      } /* end great big if()... */


   /* Now adjust the value of the FIFO based upon options specified */
   if( ps3v->fifo_moderate ) {
      if(pScrn->bitsPerPixel < 24)
         new->MMPR0 -= 0x8000;
      else
         new->MMPR0 -= 0x4000;
      }
   else if( ps3v->fifo_aggressive ) {
      if(pScrn->bitsPerPixel < 24)
         new->MMPR0 -= 0xc000;
      else
         new->MMPR0 -= 0x6000;
      }

   /* If we have an interlace mode, set the interlace bit. Note that mode
    * vertical timings are already adjusted by the standard VGA code
    */
   if(mode->Flags & V_INTERLACE) {
        new->CR42 = 0x20; /* Set interlace mode */
        }
   else {
        new->CR42 = 0x00;
        }

   if(S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
      S3_ViRGE_MX_SERIES(ps3v->Chipset) )
     {
       new->CR34 = 0;
     }
   else
     {
       /* Set display fifo */
       new->CR34 = 0x10;
     }
   /* Now we adjust registers for extended mode timings */
   /* This is taken without change from the accel/s3_virge code */

   i = ((((mode->CrtcHTotal >> 3) - 5) & 0x100) >> 8) |
       ((((mode->CrtcHDisplay >> 3) - 1) & 0x100) >> 7) |
       ((((mode->CrtcHSyncStart >> 3) - 1) & 0x100) >> 6) |
       ((mode->CrtcHSyncStart & 0x800) >> 7);

   if ((mode->CrtcHSyncEnd >> 3) - (mode->CrtcHSyncStart >> 3) > 64)
      i |= 0x08;   /* add another 64 DCLKs to blank pulse width */

   if ((mode->CrtcHSyncEnd >> 3) - (mode->CrtcHSyncStart >> 3) > 32)
      i |= 0x20;   /* add another 32 DCLKs to hsync pulse width */

   /* video playback chokes if sync start and display end are equal */
   if (mode->CrtcHSyncStart - mode->CrtcHDisplay < ps3v->HorizScaleFactor) {
       int tmp = vganew->CRTC[4] + ((i&0x10)<<4) + ps3v->HorizScaleFactor;
       vganew->CRTC[4] = tmp & 0xff;
       i |= ((tmp >> 4) & 0x10);
   }

   j = (  vganew->CRTC[0] + ((i&0x01)<<8)
        + vganew->CRTC[4] + ((i&0x10)<<4) + 1) / 2;

   if (j-(vganew->CRTC[4] + ((i&0x10)<<4)) < 4) {
      if (vganew->CRTC[4] + ((i&0x10)<<4) + 4 <= vganew->CRTC[0]+ ((i&0x01)<<8))
         j = vganew->CRTC[4] + ((i&0x10)<<4) + 4;
      else
         j = vganew->CRTC[0]+ ((i&0x01)<<8) + 1;
   }
   new->CR3B = j & 0xFF;
   i |= (j & 0x100) >> 2;
   new->CR3C = (vganew->CRTC[0] + ((i&0x01)<<8))/2;
   new->CR5D = i;

   new->CR5E = (((mode->CrtcVTotal - 2) & 0x400) >> 10)  |
               (((mode->CrtcVDisplay - 1) & 0x400) >> 9) |
               (((mode->CrtcVSyncStart) & 0x400) >> 8)   |
               (((mode->CrtcVSyncStart) & 0x400) >> 6)   | 0x40;


   width = (pScrn->displayWidth * (pScrn->bitsPerPixel / 8))>> 3;
   vganew->CRTC[19] = 0xFF & width;
   new->CR51 = (0x300 & width) >> 4; /* Extension bits */

   /* Set doublescan */
   if( mode->Flags & V_DBLSCAN)
     vganew->CRTC[9] |= 0x80;

   /* And finally, select clock source 2 for programmable PLL */
   vganew->MiscOutReg |= 0x0c;


   new->CR33 = 0x20;
   if (S3_TRIO_3D_2X_SERIES(ps3v->Chipset) || S3_ViRGE_GX2_SERIES(ps3v->Chipset)
       /* MXTESTME */ || S3_ViRGE_MX_SERIES(ps3v->Chipset) )
   {
     new->CR85 = 0x12;  /* avoid sreen flickering */
      /* by increasing FIFO filling, larger # fills FIFO from memory earlier */
      /* on GX2 this affects all depths, not just those running STREAMS. */
      /* new, secondary stream settings. */
      new->CR87 = 0x10;
      /* gx2 - set up in XV init code */
      new->CR92 = 0x00;
      new->CR93 = 0x00;
      /* gx2 primary mclk timeout, def=0xb */
      new->CR7B = 0xb;
      /* gx2 secondary mclk timeout, def=0xb */
      new->CR7D = 0xb;
   }
   if (ps3v->Chipset == S3_ViRGE_DXGX || S3_TRIO_3D_SERIES(ps3v->Chipset)) {
      new->CR86 = 0x80;  /* disable DAC power saving to avoid bright left edge */
   }
   if (ps3v->Chipset == S3_ViRGE_DXGX || S3_ViRGE_GX2_SERIES(ps3v->Chipset) ||
       S3_ViRGE_MX_SERIES(ps3v->Chipset) || S3_TRIO_3D_SERIES(ps3v->Chipset)) {
      int dbytes = pScrn->displayWidth * ((pScrn->bitsPerPixel+7)/8);
      new->CR91 =   (dbytes + 7) / 8;
      new->CR90 = (((dbytes + 7) / 8) >> 8) | 0x80;
   }


   /* Now we handle various XConfig memory options and others */

   VGAOUT8(vgaCRIndex, 0x36);
   new->CR36 = VGAIN8(vgaCRReg);
   /* option "slow_edodram" sets EDO to 2 cycle mode on ViRGE */
   if (ps3v->Chipset == S3_ViRGE) {
      if( ps3v->slow_edodram )
         new->CR36 = (new->CR36 & 0xf3) | 0x08;
      else
         new->CR36 &= 0xf3;
      }

   /* Option "fpm_vram" for ViRGE_VX sets memory in fast page mode */
   if (ps3v->Chipset == S3_ViRGE_VX) {
      if( ps3v->fpm_vram )
         new->CR36 |=  0x0c;
      else
         new->CR36 &= ~0x0c;
   }

   				/* S3_INVERT_VCLK was defaulted to 0 	*/
				/* in 3.3.3 and never changed. 		*/
				/* Also, bit 0 is never set in 3.9Nm,	*/
				/* so I left this out for 4.0.			*/
#if 0
      if (mode->Private[0] & (1 << S3_INVERT_VCLK)) {
	 if (mode->Private[S3_INVERT_VCLK])
	    new->CR67 |= 1;
	 else
	    new->CR67 &= ~1;
      }
#endif
      				/* S3_BLANK_DELAY settings based on 	*/
				/* defaults only. From 3.3.3 		*/
   {
      int blank_delay;

      if(ps3v->Chipset == S3_ViRGE_VX)
	    /* these values need to be changed once CR67_1 is set
	       for gamma correction (see S3V server) ! */
	    if (pScrn->bitsPerPixel == 8)
	       blank_delay = 0x00;
	    else if (pScrn->bitsPerPixel == 16)
	       blank_delay = 0x00;
	    else
	       blank_delay = 0x51;
      else
	    if (pScrn->bitsPerPixel == 8)
	       blank_delay = 0x00;
	    else if (pScrn->bitsPerPixel == 16)
	       blank_delay = 0x02;
	    else
	       blank_delay = 0x04;

      if (ps3v->Chipset == S3_ViRGE_VX)
	    new->CR6D = blank_delay;
      else {
	    new->CR65 = (new->CR65 & ~0x38)
	       | (blank_delay & 0x07) << 3;
	    VGAOUT8(vgaCRIndex, 0x6d);
	    new->CR6D = VGAIN8(vgaCRReg);
      }
   }
   				/* S3_EARLY_SC was defaulted to 0 	*/
				/* in 3.3.3 and never changed. 		*/
				/* Also, bit 1 is never set in 3.9Nm,	*/
				/* so I left this out for 4.0.			*/
#if 0
      if (mode->Private[0] & (1 << S3_EARLY_SC)) {
	 if (mode->Private[S3_EARLY_SC])
	    new->CR65 |= 2;
	 else
	    new->CR65 &= ~2;
      }
#endif

   VGAOUT8(vgaCRIndex, 0x68);
   new->CR68 = VGAIN8(vgaCRReg);
   new->CR69 = 0;

   /* Flat panel centering and expansion registers */
   if (S3_ViRGE_MX_SERIES(ps3v->Chipset) && (ps3v->lcd_center)) {
     new->SR54 = 0x10 ;
     new->SR55 = 0x80 ;
     new->SR56 = 0x10 ;
     new->SR57 = 0x80 ;
   } else {
     new->SR54 = 0x1f ;
     new->SR55 = 0x9f ;
     new->SR56 = 0x1f ;
     new->SR57 = 0xff ;
   }

   pScrn->vtSema = TRUE;

   					/* Do it!  Write the mode registers */
					/* to hardware, start STREAMS if    */
					/* needed, etc.		    	    */
   S3VWriteMode( pScrn, vganew, new );
   					/* Adjust the viewport */
   S3VAdjustFrame(pScrn, pScrn->frameX0, pScrn->frameY0);

   return TRUE;
}


/*
 * This is called at the end of each server generation.  It restores the
 * original (text) mode.  It should also unmap the video memory, and free
 * any per-generation data allocated by the driver.  It should finish
 * by unwrapping and calling the saved CloseScreen function.
 */

/* Mandatory */
static Bool
S3VCloseScreen(ScreenPtr pScreen)
{
  ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
  vgaHWPtr hwp = VGAHWPTR(pScrn);
  S3VPtr ps3v = S3VPTR(pScrn);
  vgaRegPtr vgaSavePtr = &hwp->SavedReg;
  S3VRegPtr S3VSavePtr = &ps3v->SavedReg;

    					/* Like S3VRestore, but uses passed */
					/* mode registers.		    */
  if (pScrn->vtSema) {
      S3VWriteMode(pScrn, vgaSavePtr, S3VSavePtr);
      vgaHWLock(hwp);
      S3VDisableMmio(pScrn);
      S3VUnmapMem(pScrn);
  }

  if (ps3v->DGAModes)
  	free(ps3v->DGAModes);

  pScrn->vtSema = FALSE;

  pScreen->CloseScreen = ps3v->CloseScreen;

  return (*pScreen->CloseScreen)(pScreen);
}




/* Do screen blanking */

/* Mandatory */
static Bool
S3VSaveScreen(ScreenPtr pScreen, int mode)
{
  return vgaHWSaveScreen(pScreen, mode);
}





/* This function inits the STREAMS processor variables.
 * This has essentially been taken from the accel/s3_virge code and the databook.
 */
static void
S3VInitSTREAMS(ScrnInfoPtr pScrn, unsigned int *streams, DisplayModePtr mode)
{
  PVERB5("	S3VInitSTREAMS\n");

  switch (pScrn->bitsPerPixel)
    {
    case 16:
      streams[0] = 0x05000000;
      break;
    case 24:
                         /* data format 8.8.8 (24 bpp) */
      streams[0] = 0x06000000;
      break;
    case 32:
                         /* one more bit for X.8.8.8, 32 bpp */
      streams[0] = 0x07000000;
      break;
    }
                         /* NO chroma keying... */
   streams[1] = 0x0;
                         /* Secondary stream format KRGB-16 */
                         /* data book suggestion... */
   streams[2] = 0x03000000;

   streams[3] = 0x0;

   streams[4] = 0x0;
                         /* use 0x01000000 for primary over second. */
                         /* use 0x0 for second over prim. */
   streams[5] = 0x01000000;

   streams[6] = 0x0;

   streams[7] = 0x0;
                                /* Stride is 3 bytes for 24 bpp mode and */
                                /* 4 bytes for 32 bpp. */
   switch(pScrn->bitsPerPixel)
     {
     case 16:
       streams[8] =
	 pScrn->displayWidth * 2;
       break;
     case 24:
       streams[8] =
	 pScrn->displayWidth * 3;
      break;
     case 32:
       streams[8] =
	 pScrn->displayWidth * 4;
      break;
     }
                                /* Choose fbaddr0 as stream source. */
   streams[9] = 0x0;
   streams[10] = 0x0;
   streams[11] = 0x0;
   streams[12] = 0x1;

                                /* Set primary stream on top of secondary */
                                /* stream. */
   streams[13] = 0xc0000000;
                               /* Vertical scale factor. */
   streams[14] = 0x0;

   streams[15] = 0x0;
                                /* Vertical accum. initial value. */
   streams[16] = 0x0;
                                /* X and Y start coords + 1. */
   streams[18] =  0x00010001;

         /* Specify window Width -1 and Height of */
         /* stream. */
   streams[19] =
         (mode->HDisplay - 1) << 16 |
         (mode->VDisplay);

                                /* Book says 0x07ff07ff. */
   streams[20] = 0x07ff07ff;

   streams[21] = 0x00010001;

}




/* Used to adjust start address in frame buffer. We use the new
 * CR69 reg for this purpose instead of the older CR31/CR51 combo.
 * If STREAMS is running, we program the STREAMS start addr. registers.
 */

void
S3VAdjustFrame(ScrnInfoPtr pScrn, int x, int y)
{
   vgaHWPtr hwp = VGAHWPTR(pScrn);
   S3VPtr ps3v = S3VPTR(pScrn);
   int Base;
   int vgaCRIndex, vgaCRReg, vgaIOBase;
   vgaIOBase = hwp->IOBase;
   vgaCRIndex = vgaIOBase + 4;
   vgaCRReg = vgaIOBase + 5;

   if(ps3v->ShowCache && y)
	y += pScrn->virtualY - 1;

   if( (ps3v->STREAMSRunning == FALSE) ||
      S3_ViRGE_GX2_SERIES(ps3v->Chipset) || S3_ViRGE_MX_SERIES(ps3v->Chipset)) {
      Base = ((y * pScrn->displayWidth + x)
		* (pScrn->bitsPerPixel / 8)) >> 2;
      if (pScrn->bitsPerPixel == 24)
	Base = Base+2 - (Base+2) % 3;
      if (pScrn->bitsPerPixel == 16)
	if (S3_TRIO_3D_SERIES(ps3v->Chipset) && pScrn->modes->Clock > 115000)
	  Base &= ~1;

      /* Now program the start address registers */
      VGAOUT16(vgaCRIndex, (Base & 0x00FF00) | 0x0C);
      VGAOUT16(vgaCRIndex, ((Base & 0x00FF) << 8) | 0x0D);
      VGAOUT8(vgaCRIndex, 0x69);
      VGAOUT8(vgaCRReg, (Base & 0x0F0000) >> 16);
      }
   else {          /* Change start address for STREAMS case */
      VerticalRetraceWait();
      if(ps3v->Chipset == S3_ViRGE_VX)
	OUTREG(PSTREAM_FBADDR0_REG,
		   ((y * pScrn->displayWidth + (x & ~7)) *
		    pScrn->bitsPerPixel / 8));
      else
	OUTREG(PSTREAM_FBADDR0_REG,
		   ((y * pScrn->displayWidth + (x & ~3)) *
		    pScrn->bitsPerPixel / 8));
      }

   return;
}




/* Usually mandatory */
Bool
S3VSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    return S3VModeInit(pScrn, mode);
}



void S3VLoadPalette(
    ScrnInfoPtr pScrn,
    int numColors,
    int *indices,
    LOCO *colors,
    VisualPtr pVisual
){
    S3VPtr ps3v = S3VPTR(pScrn);
    int i, index;

    for(i = 0; i < numColors; i++) {
	index = indices[i];
        VGAOUT8(0x3c8, index);
        VGAOUT8(0x3c9, colors[index].red);
        VGAOUT8(0x3c9, colors[index].green);
        VGAOUT8(0x3c9, colors[index].blue);
    }
}


/*
 * Functions to support getting a ViRGE card into MMIO mode if it fails to
 * default to MMIO enabled.
 */

void
S3VEnableMmio(ScrnInfoPtr pScrn)
{
  vgaHWPtr hwp;
  S3VPtr ps3v;
  unsigned int vgaCRIndex, vgaCRReg;
  unsigned char val;
  unsigned int PIOOffset = 0;

  PVERB5("	S3VEnableMmio\n");

  hwp = VGAHWPTR(pScrn);
  ps3v = S3VPTR(pScrn);

  /*
   * enable chipset (seen on uninitialized secondary cards)
   * might not be needed once we use the VGA softbooter
   * (EE 05/04/99)
   */
  vgaHWSetStdFuncs(hwp);
  /*
   * any access to the legacy VGA ports is done here.
   * If legacy VGA is inaccessible the MMIO base _has_
   * to be set correctly already and MMIO _has_ to be
   * enabled.
   */
  val = inb(PIOOffset + 0x3C3);               /*@@@EE*/
  outb(PIOOffset + 0x3C3, val | 0x01);
  /*
   * set CR registers to color mode
   * in mono mode extended CR registers
   * are not accessible. (EE 05/04/99)
   */
  val = inb(PIOOffset + VGA_MISC_OUT_R);      /*@@@EE*/
  outb(PIOOffset + VGA_MISC_OUT_W, val | 0x01);
  vgaHWGetIOBase(hwp);             	/* Get VGA I/O base */
  vgaCRIndex = PIOOffset + hwp->IOBase + 4;
  vgaCRReg = vgaCRIndex + 1;
#if 1
  /*
   * set linear base register to the PCI register values
   * some DX chipsets don't seem to do it automatically
   * (EE 06/03/99)
   */
  outb(vgaCRIndex, 0x59);         /*@@@EE*/
  outb(vgaCRReg, PCI_REGION_BASE(ps3v->PciInfo, 0, REGION_MEM) >> 24);
  outb(vgaCRIndex, 0x5A);
  outb(vgaCRReg, PCI_REGION_BASE(ps3v->PciInfo, 0, REGION_MEM) >> 16);
  outb(vgaCRIndex, 0x53);
#endif
  /* Save register for restore */
  ps3v->EnableMmioCR53 = inb(vgaCRReg);
  			      	/* Enable new MMIO, if TRIO mmio is already */
				/* enabled, then it stays enabled. */
  outb(vgaCRReg, ps3v->EnableMmioCR53 | 0x08);
  outb(PIOOffset + VGA_MISC_OUT_W, val);
  if (S3_TRIO_3D_SERIES(ps3v->Chipset)) {
    outb(vgaCRIndex, 0x40);
    val = inb(vgaCRReg);
    outb(vgaCRReg, val | 1);
  }
}



void
S3VDisableMmio(ScrnInfoPtr pScrn)
{
  vgaHWPtr hwp;
  S3VPtr ps3v;
  unsigned int vgaCRIndex, vgaCRReg;

  PVERB5("	S3VDisableMmio\n");

  hwp = VGAHWPTR(pScrn);
  ps3v = S3VPTR(pScrn);

  vgaCRIndex = hwp->IOBase + 4;
  vgaCRReg = vgaCRIndex + 1;
  outb(vgaCRIndex, 0x53);
				/* Restore register's original state */
  outb(vgaCRReg, ps3v->EnableMmioCR53);
  if (S3_TRIO_3D_SERIES(ps3v->Chipset)) {
    unsigned char val;
    outb(vgaCRIndex, 0x40);
    val = inb(vgaCRReg);
    outb(vgaCRReg, val | 1);
  }
}



/* This function is used to debug, it prints out the contents of s3 regs */

static void
S3VPrintRegs(ScrnInfoPtr pScrn)
{
    unsigned char tmp1, tmp2;
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    S3VPtr ps3v = S3VPTR(pScrn);
    int vgaCRIndex, vgaCRReg, vgaIOBase, vgaIR;
    vgaIOBase = hwp->IOBase;
    vgaCRIndex = vgaIOBase + 4;
    vgaCRReg = vgaIOBase + 5;
    vgaIR = vgaIOBase + 0xa;

/* All registers */
/* New formatted registers, matches s3rc (sort of) */
    xf86DrvMsgVerb( pScrn->scrnIndex, X_INFO, VERBLEV, "START register dump ------------------\n");
    xf86ErrorFVerb(VERBLEV, "Misc Out[3CC]\n  ");
    xf86ErrorFVerb(VERBLEV, "%02x\n",VGAIN8(0x3cc));

    xf86ErrorFVerb(VERBLEV, "\nCR[00-2f]\n  ");
    for(tmp1=0x0;tmp1<=0x2f;tmp1++){
	VGAOUT8(vgaCRIndex, tmp1);
	xf86ErrorFVerb(VERBLEV, "%02x ",VGAIN8(vgaCRReg));
	if((tmp1 & 0x3) == 0x3) xf86ErrorFVerb(VERBLEV, " ");
	if((tmp1 & 0xf) == 0xf) xf86ErrorFVerb(VERBLEV, "\n  ");
    }

    xf86ErrorFVerb(VERBLEV, "\nSR[00-27]\n  ");
    for(tmp1=0x0;tmp1<=0x27;tmp1++){
	VGAOUT8(0x3c4, tmp1);
	xf86ErrorFVerb(VERBLEV, "%02x ",VGAIN8(0x3c5));
	if((tmp1 & 0x3) == 0x3) xf86ErrorFVerb(VERBLEV, " ");
	if((tmp1 & 0xf) == 0xf) xf86ErrorFVerb(VERBLEV, "\n  ");
    }
    xf86ErrorFVerb(VERBLEV, "\n"); /* odd hex number of digits... */

    xf86ErrorFVerb(VERBLEV, "\nGr Cont GR[00-0f]\n  ");
    for(tmp1=0x0;tmp1<=0x0f;tmp1++){
	VGAOUT8(0x3ce, tmp1);
	xf86ErrorFVerb(VERBLEV, "%02x ",VGAIN8(0x3cf));
	if((tmp1 & 0x3) == 0x3) xf86ErrorFVerb(VERBLEV, " ");
	if((tmp1 & 0xf) == 0xf) xf86ErrorFVerb(VERBLEV, "\n  ");
    }

    xf86ErrorFVerb(VERBLEV, "\nAtt Cont AR[00-1f]\n  ");
    VGAIN8(vgaIR); /* preset AR flip-flop by reading 3DA, ignore return value */
    tmp2=VGAIN8(0x3c0) & 0x20;
    for(tmp1=0x0;tmp1<=0x1f;tmp1++){
    VGAIN8(vgaIR); /* preset AR flip-flop by reading 3DA, ignore return value */
	VGAOUT8(0x3c0, (tmp1 & ~0x20) | tmp2);
	xf86ErrorFVerb(VERBLEV, "%02x ",VGAIN8(0x3c1));
	if((tmp1 & 0x3) == 0x3) xf86ErrorFVerb(VERBLEV, " ");
	if((tmp1 & 0xf) == 0xf) xf86ErrorFVerb(VERBLEV, "\n  ");
    }

    xf86ErrorFVerb(VERBLEV, "\nCR[30-6f]\n  ");
    for(tmp1=0x30;tmp1<=0x6f;tmp1++){
	VGAOUT8(vgaCRIndex, tmp1);
	xf86ErrorFVerb(VERBLEV, "%02x ",VGAIN8(vgaCRReg));
	if((tmp1 & 0x3) == 0x3) xf86ErrorFVerb(VERBLEV, " ");
	if((tmp1 & 0xf) == 0xf) xf86ErrorFVerb(VERBLEV, "\n  ");
    }

    xf86ErrorFVerb(VERBLEV, "\n");
    xf86DrvMsgVerb( pScrn->scrnIndex, X_INFO, VERBLEV, "END register dump --------------------\n");
}

/* this is just a debugger hook */
/*
void print_subsys_stat(void *s3vMmioMem);
void
print_subsys_stat(void *s3vMmioMem)
{
  ErrorF("IN_SUBSYS_STAT() = %x\n", IN_SUBSYS_STAT());
  return;
}
*/

/*
 * S3VDisplayPowerManagementSet --
 *
 * Sets VESA Display Power Management Signaling (DPMS) Mode.
 */
static void
S3VDisplayPowerManagementSet(ScrnInfoPtr pScrn, int PowerManagementMode,
			     int flags)
{
  S3VPtr ps3v;
  unsigned char sr8 = 0x0, srd = 0x0;
  char modestr[][40] = { "On","Standby","Suspend","Off" };

  ps3v = S3VPTR(pScrn);

  /* unlock extended sequence registers */

  VGAOUT8(0x3c4, 0x08);
  sr8 = VGAIN8(0x3c5);
  sr8 |= 0x6;
  VGAOUT8(0x3c5, sr8);

  /* load SRD */
  VGAOUT8(0x3c4, 0x0d);
  srd = VGAIN8(0x3c5);

  srd &= 0x03; /* clear the sync control bits of srd */

  switch (PowerManagementMode) {
  case DPMSModeOn:
    /* Screen: On; HSync: On, VSync: On */
    break;
  case DPMSModeStandby:
    /* Screen: Off; HSync: Off, VSync: On */
    srd |= 0x10;
    break;
  case DPMSModeSuspend:
    /* Screen: Off; HSync: On, VSync: Off */
    srd |= 0x40;
    break;
  case DPMSModeOff:
    /* Screen: Off; HSync: Off, VSync: Off */
    srd |= 0x50;
    break;
  default:
    xf86ErrorFVerb(VERBLEV, "Invalid PowerManagementMode %d passed to S3VDisplayPowerManagementSet\n", PowerManagementMode);
    break;
  }

  VGAOUT8(0x3c4, 0x0d);
  VGAOUT8(0x3c5, srd);

  xf86ErrorFVerb(VERBLEV, "Power Manag: set:%s\n",
		 modestr[PowerManagementMode]);

  return;
}

static unsigned int
S3Vddc1Read(ScrnInfoPtr pScrn)
{
    register vgaHWPtr hwp = VGAHWPTR(pScrn);
    register CARD32 tmp;
    S3VPtr ps3v = S3VPTR(pScrn);

    while (hwp->readST01(hwp)&0x8) {};
    while (!(hwp->readST01(hwp)&0x8)) {};

    tmp = (INREG(DDC_REG));
    return ((unsigned int) (tmp & 0x08));
}

static void
S3Vddc1SetSpeed(ScrnInfoPtr pScrn, xf86ddcSpeed speed)
{
    vgaHWddc1SetSpeed(pScrn, speed);
}

static Bool
S3Vddc1(ScrnInfoPtr pScrn)
{
    S3VPtr ps3v = S3VPTR(pScrn);
    CARD32 tmp;
    Bool success = FALSE;
    xf86MonPtr pMon;

    /* initialize chipset */
    tmp = INREG(DDC_REG);
    OUTREG(DDC_REG,(tmp | 0x12));

    if ((pMon = xf86PrintEDID(
		xf86DoEDID_DDC1(pScrn, S3Vddc1SetSpeed, S3Vddc1Read))) != NULL)
	success = TRUE;
    xf86SetDDCproperties(pScrn,pMon);

    /* undo initialization */
    OUTREG(DDC_REG,(tmp));
    return success;
}

static Bool
S3Vddc2(ScrnInfoPtr pScrn)
{
    S3VPtr ps3v = S3VPTR(pScrn);

    if ( xf86LoadSubModule(pScrn, "i2c") ) {
	if (S3V_I2CInit(pScrn)) {
	    CARD32 tmp = (INREG(DDC_REG));
	    OUTREG(DDC_REG,(tmp | 0x13));
	    xf86SetDDCproperties(pScrn,xf86PrintEDID(
			     xf86DoEDID_DDC2(pScrn,ps3v->I2C)));
	    OUTREG(DDC_REG,tmp);
	    return TRUE;
	}
    }
    return FALSE;
}

static void
S3VProbeDDC(ScrnInfoPtr pScrn, int index)
{
    vbeInfoPtr pVbe;
    if (xf86LoadSubModule(pScrn, "vbe")) {
        pVbe = VBEInit(NULL,index);
        ConfiguredMonitor = vbeDoEDID(pVbe, NULL);
	vbeFree(pVbe);
    }
}

/*EOF*/


