/**********************************************************************
Copyright 1998, 1999 by Precision Insight, Inc., Cedar Park, Texas.

                        All Rights Reserved

Permission to use, copy, modify, distribute, and sell this software and
its documentation for any purpose is hereby granted without fee,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Precision Insight not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  Precision Insight
and its suppliers make no representations about the suitability of this
software for any purpose.  It is provided "as is" without express or
implied warranty.

PRECISION INSIGHT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**********************************************************************/

/*
 * Copyright 1998, 1999 Egbert Eich
 * Copyright 2000, 2001 SuSE GmbH, Author: Egbert Eich
 * Copyright 2002 SuSE Linux AG, Author: Egbert Eich
 * Copyright 2002 Shigehiro Nomura
 */


/*
 * The original Precision Insight driver for
 * XFree86 v.3.3 has been sponsored by Red Hat.
 *
 * Authors:
 *   Jens Owen (jens@tungstengraphics.com)
 *   Kevin E. Martin (kevin@precisioninsight.com)
 *
 * Port to Xfree86 v.4.0
 *   1998, 1999 by Egbert Eich (Egbert.Eich@Physik.TU-Darmstadt.DE)
 */
#include "config.h"

/* All drivers should typically include these */
#include "xf86.h"
#include "xf86_OSproc.h"

/* Everything using inb/outb, etc needs "compiler.h" */
#include "compiler.h"

/* Drivers that need to access the PCI config space directly need this */
#include "xf86Pci.h"

/* All drivers using the vgahw module need this */
#include "vgaHW.h"

/* All drivers initialising the SW cursor need this */
#include "mipointer.h"

/* All drivers using the mi colormap manipulation need this */
#include "micmap.h"

#include "xf86cmap.h"

#include "fb.h"

/* int10 */
#include "xf86int10.h"
#include "vbe.h"

/* Needed for Device Data Channel (DDC) support */
#include "xf86DDC.h"

#include "picturestr.h"

#include "xf86xv.h"
#include <X11/extensions/Xv.h>

/*
 * Driver data structures.
 */
#include "neo.h"
#include "neo_reg.h"
#include "neo_macros.h"

/* These need to be checked */
#include <X11/X.h>
#include <X11/Xproto.h>
#include "scrnintstr.h"
#include "servermd.h"

#include <stdlib.h>
#include <unistd.h>

/* Mandatory functions */
static const OptionInfoRec *	NEOAvailableOptions(int chipid, int busid);
static void     NEOIdentify(int flags);
static Bool     NEOProbe(DriverPtr drv, int flags);
static Bool     NEOPreInit(ScrnInfoPtr pScrn, int flags);
static Bool     NEOScreenInit(ScreenPtr pScreen, int argc, char **argv);
static Bool     NEOEnterVT(ScrnInfoPtr arg);
static void     NEOLeaveVT(ScrnInfoPtr arg);
static Bool     NEOCloseScreen(ScreenPtr pScreen);
static void     NEOFreeScreen(ScrnInfoPtr arg);
static ModeStatus NEOValidMode(ScrnInfoPtr arg, DisplayModePtr mode,
                               Bool verbose, int flags);

/* Internally used functions */
static Bool     neoModeInit(ScrnInfoPtr pScrn, DisplayModePtr mode);
static void     neoSave(ScrnInfoPtr pScrn);
static void     neoRestore(ScrnInfoPtr pScrn, vgaRegPtr VgaReg,
				 NeoRegPtr NeoReg, Bool restoreText);
static void     neoLock(ScrnInfoPtr pScrn);
static void     neoUnlock(ScrnInfoPtr pScrn);
static Bool	neoMapMem(ScrnInfoPtr pScrn);
static Bool	neoUnmapMem(ScrnInfoPtr pScrn);
static void     neoProgramShadowRegs(ScrnInfoPtr pScrn, vgaRegPtr VgaReg,
				     NeoRegPtr restore);
static void     neoCalcVCLK(ScrnInfoPtr pScrn, long freq);
static xf86MonPtr  neo_ddc1(ScrnInfoPtr pScrn);
static Bool     neoDoDDC1(ScrnInfoPtr pScrn);
static Bool     neoDoDDC2(ScrnInfoPtr pScrn);
static Bool     neoDoDDCVBE(ScrnInfoPtr pScrn);
static void     neoProbeDDC(ScrnInfoPtr pScrn, int index);
static void     NeoDisplayPowerManagementSet(ScrnInfoPtr pScrn,
				int PowerManagementMode, int flags);
static int      neoFindMode(int xres, int yres, int depth);

#define NEO_VERSION 4000
#define NEO_NAME "NEOMAGIC"
#define NEO_DRIVER_NAME "neomagic"

#define NEO_MAJOR_VERSION PACKAGE_VERSION_MAJOR
#define NEO_MINOR_VERSION PACKAGE_VERSION_MINOR
#define NEO_PATCHLEVEL PACKAGE_VERSION_PATCHLEVEL

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


static biosMode bios8[] = {
    { 320, 240, 0x40 },
    { 300, 400, 0x42 },
    { 640, 400, 0x20 },
    { 640, 480, 0x21 },
    { 800, 600, 0x23 },
    { 1024, 768, 0x25 }
};

static biosMode bios15[] = {
    { 320, 200, 0x2D },
    { 640, 480, 0x30 },
    { 800, 600, 0x33 },
    { 1024, 768, 0x36 }
};

static biosMode bios16[] = {
    { 320, 200, 0x2e },
    { 320, 240, 0x41 },
    { 300, 400, 0x43 },
    { 640, 480, 0x31 },
    { 800, 600, 0x34 },
    { 1024, 768, 0x37 }
};

static biosMode bios24[] = {
    { 640, 480, 0x32 },
    { 800, 600, 0x35 },
    { 1024, 768, 0x38 }
};

static DisplayModeRec neo800x480Mode = {
	NULL,           /* prev */
	NULL,           /* next */
	"800x480",      /* identifier of this mode */
	MODE_OK,        /* mode status */
	M_T_BUILTIN,    /* mode type */
	35260,		/* Clock frequency */
	800,		/* HDisplay */
	856,		/* HSyncStart */
	1040,		/* HSyncEnd */
	1056,		/* HTotal */
	0,		/* HSkew */
	480,		/* VDisplay */
	480,		/* VSyncStart */
	486,		/* VSyncEnd */
	488,		/* VTotal */
	0,		/* VScan */
	V_PHSYNC | V_PVSYNC,	/* Flags */
	-1,		/* ClockIndex */
	35260,		/* SynthClock */
	800,		/* CRTC HDisplay */
	800,            /* CRTC HBlankStart */
	856,            /* CRTC HSyncStart */
	1040,           /* CRTC HSyncEnd */
	872,            /* CRTC HBlankEnd */
	1048,           /* CRTC HTotal */
	0,              /* CRTC HSkew */
	480,		/* CRTC VDisplay */
	480,		/* CRTC VBlankStart */
	480,		/* CRTC VSyncStart */
	486,		/* CRTC VSyncEnd */
	487,		/* CRTC VBlankEnd */
	488,		/* CRTC VTotal */
	FALSE,		/* CrtcHAdjusted */
	FALSE,		/* CrtcVAdjusted */
	0,		/* PrivSize */
	NULL,		/* Private */
	0.0,		/* HSync */
	0.0		/* VRefresh */
};

static DisplayModeRec neo1024x480Mode = {
	NULL,           /* prev */
	NULL,           /* next */
	"1024x480",      /* identifier of this mode */
	MODE_OK,        /* mode status */
	M_T_BUILTIN,    /* mode type */
	45900,		/* Clock frequency */
	1024,		/* HDisplay */
	1048,		/* HSyncStart */
	1184,		/* HSyncEnd */
	1344,		/* HTotal */
	0,		/* HSkew */
	480,		/* VDisplay */
	480,		/* VSyncStart */
	486,		/* VSyncEnd */
	488,		/* VTotal */
	0,		/* VScan */
	V_PHSYNC | V_PVSYNC,	/* Flags */
	-1,		/* ClockIndex */
	45900,		/* SynthClock */
	1024,		/* CRTC HDisplay */
	1024,            /* CRTC HBlankStart */
	1048,            /* CRTC HSyncStart */
	1184,           /* CRTC HSyncEnd */
	1072,            /* CRTC HBlankEnd */
	1344,           /* CRTC HTotal */
	0,              /* CRTC HSkew */
	480,		/* CRTC VDisplay */
	480,		/* CRTC VBlankStart */
	480,		/* CRTC VSyncStart */
	486,		/* CRTC VSyncEnd */
	487,		/* CRTC VBlankEnd */
	488,		/* CRTC VTotal */
	FALSE,		/* CrtcHAdjusted */
	FALSE,		/* CrtcVAdjusted */
	0,		/* PrivSize */
	NULL,		/* Private */
	0.0,		/* HSync */
	0.0		/* VRefresh */
};

/*
 * This contains the functions needed by the server after loading the driver
 * module.  It must be supplied, and gets passed back by the SetupProc
 * 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 NEOMAGIC = {
    NEO_VERSION,
    NEO_DRIVER_NAME,
    NEOIdentify,
    NEOProbe,
    NEOAvailableOptions,
    NULL,
    0
};

static SymTabRec NEOChipsets[] = {
    { NM2070,   "neo2070" },
    { NM2090,   "neo2090" },
    { NM2093,   "neo2093" },
    { NM2097,   "neo2097" },
    { NM2160,   "neo2160" },
    { NM2200,   "neo2200" },
    { NM2230,   "neo2230" },
    { NM2360,   "neo2360" },
    { NM2380,   "neo2380" },
    { -1,		 NULL }
};

/* Conversion PCI ID to chipset name */
static PciChipsets NEOPCIchipsets[] = {
    { NM2070,  PCI_CHIP_NM2070,  RES_SHARED_VGA },
    { NM2090,  PCI_CHIP_NM2090,  RES_SHARED_VGA },
    { NM2093,  PCI_CHIP_NM2093,  RES_SHARED_VGA },
    { NM2097,  PCI_CHIP_NM2097,  RES_SHARED_VGA },
    { NM2160,  PCI_CHIP_NM2160,  RES_SHARED_VGA },
    { NM2200,  PCI_CHIP_NM2200,  RES_SHARED_VGA },
    { NM2230,  PCI_CHIP_NM2230,  RES_SHARED_VGA },
    { NM2360,  PCI_CHIP_NM2360,  RES_SHARED_VGA },
    { NM2380,  PCI_CHIP_NM2380,  RES_SHARED_VGA },
    { -1,	     -1,	     RES_UNDEFINED}
};

/* The options supported by the Neomagic Driver */
typedef enum {
    OPTION_NOACCEL,
    OPTION_SW_CURSOR,
    OPTION_NO_MMIO,
    OPTION_INTERN_DISP,
    OPTION_EXTERN_DISP,
    OPTION_LCD_CENTER,
    OPTION_LCD_STRETCH,
    OPTION_SHADOW_FB,
    OPTION_PCI_BURST,
    OPTION_PROG_LCD_MODE_REGS,
    OPTION_PROG_LCD_MODE_STRETCH,
    OPTION_OVERRIDE_VALIDATE_MODE,
    OPTION_SHOWCACHE,
    OPTION_ROTATE,
    OPTION_VIDEO_KEY,
    OPTION_OVERLAYMEM,
    OPTION_VIDEO_INTERLACE,
    OPTION_DISPLAY_HEIGHT_480,
    OPTION_STRANGE_LOCKUPS
} NEOOpts;

static const OptionInfoRec NEO_2070_Options[] = {
    { OPTION_NOACCEL,	"NoAccel",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_SW_CURSOR,	"SWcursor",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_NO_MMIO,	"noMMIO",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_INTERN_DISP,"internDisp",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_EXTERN_DISP,"externDisp",  OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_LCD_CENTER, "LcdCenter",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_LCD_STRETCH, "NoStretch",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_SHADOW_FB,   "ShadowFB",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_PCI_BURST,	 "pciBurst",	OPTV_BOOLEAN,   {0}, FALSE },
    { OPTION_SHOWCACHE,  "ShowCache",   OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_ROTATE, 	 "Rotate",	OPTV_ANYSTR,	{0}, FALSE },
    { OPTION_PROG_LCD_MODE_REGS, "progLcdModeRegs",
      OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_PROG_LCD_MODE_STRETCH, "progLcdModeStretch",
      OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_OVERRIDE_VALIDATE_MODE, "overrideValidateMode",
      OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_VIDEO_KEY, "VideoKey",     OPTV_INTEGER,   {0}, FALSE },
    { OPTION_OVERLAYMEM, "OverlayMem",  OPTV_INTEGER,   {0}, FALSE },
    { OPTION_VIDEO_INTERLACE, "Interlace",
      OPTV_INTEGER,   {0}, FALSE },
    { -1,                  NULL,           OPTV_NONE,	{0}, FALSE }
};

static const OptionInfoRec NEOOptions[] = {
    { OPTION_NOACCEL,	"NoAccel",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_SW_CURSOR,	"SWcursor",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_NO_MMIO,	"noMMIO",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_INTERN_DISP,"internDisp",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_EXTERN_DISP,"externDisp",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_LCD_CENTER, "LcdCenter",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_SHADOW_FB,  "ShadowFB",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_LCD_STRETCH,"NoStretch",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_PCI_BURST,	 "pciBurst",	OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_SHOWCACHE,  "ShowCache",   OPTV_BOOLEAN,	{0}, FALSE },
    { OPTION_ROTATE, 	 "Rotate",	OPTV_ANYSTR,	{0}, FALSE },
    { OPTION_STRANGE_LOCKUPS, "StrangeLockups", OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_DISPLAY_HEIGHT_480, "DisplayHeight480",
      OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_PROG_LCD_MODE_REGS, "progLcdModeRegs",
      OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_PROG_LCD_MODE_STRETCH, "progLcdModeStretch",
      OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_OVERRIDE_VALIDATE_MODE, "overrideValidateMode",
      OPTV_BOOLEAN, {0}, FALSE },
    { OPTION_VIDEO_KEY, "VideoKey",     OPTV_INTEGER,   {0}, FALSE },
    { OPTION_OVERLAYMEM, "OverlayMem",  OPTV_INTEGER,   {0}, FALSE },
    { OPTION_VIDEO_INTERLACE, "Interlace",
      OPTV_INTEGER,   {0}, FALSE },
    { -1,                  NULL,           OPTV_NONE,	{0}, FALSE }
};

static MODULESETUPPROTO(neoSetup);

static XF86ModuleVersionInfo neoVersRec =
{
	"neomagic",
	MODULEVENDORSTRING,
	MODINFOSTRING1,
	MODINFOSTRING2,
	XORG_VERSION_CURRENT,
	NEO_MAJOR_VERSION, NEO_MINOR_VERSION, NEO_PATCHLEVEL,
	ABI_CLASS_VIDEODRV,
	ABI_VIDEODRV_VERSION,
	MOD_CLASS_VIDEODRV,
	{0,0,0,0}
};

/*
 * This is the module init data.
 * Its name has to be the driver name followed by ModuleData
 */
_X_EXPORT XF86ModuleData neomagicModuleData = { &neoVersRec, neoSetup, NULL };

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

    if (!setupDone) {
	setupDone = TRUE;
        xf86AddDriver(&NEOMAGIC, 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 Bool
NEOGetRec(ScrnInfoPtr pScrn)
{
    /*
     * Allocate a NEORec, 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(NEORec), 1);

    if (pScrn->driverPrivate == NULL)
	return FALSE;
    else
        return TRUE;
}

static void
NEOFreeRec(ScrnInfoPtr pScrn)
{
    if (pScrn->driverPrivate == NULL)
	return;
    free(pScrn->driverPrivate);
    pScrn->driverPrivate = NULL;
}

static const OptionInfoRec *
NEOAvailableOptions(int chipid, int busid)
{
    int chip = (chipid & 0x0000ffff);

    if (chip == PCI_CHIP_NM2070)
	return NEO_2070_Options;
    else
    	return NEOOptions;
}

/* Mandatory */
static void
NEOIdentify(int flags)
{
    xf86PrintChipsets(NEO_NAME, "Driver for Neomagic chipsets",
			NEOChipsets);
}

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

    /*
     * Find the config file Device sections that match this
     * driver, and return if there are none.
     */
    if ((numDevSections = xf86MatchDevice(NEO_DRIVER_NAME,
					  &devSections)) <= 0) {
	return FALSE;
    }

    /* PCI BUS */
    {
	numUsed = xf86MatchPciInstances(NEO_NAME, PCI_VENDOR_NEOMAGIC,
					NEOChipsets, NEOPCIchipsets,
					devSections,numDevSections,
					drv, &usedChips);

	if (numUsed > 0) {
	    if (flags & PROBE_DETECT)
		foundScreen = TRUE;
	    else for (i = 0; i < numUsed; i++) {
		ScrnInfoPtr pScrn = NULL;
		/* Allocate a ScrnInfoRec and claim the slot */
		if ((pScrn = xf86ConfigPciEntity(pScrn, 0, usedChips[i],
						       NEOPCIchipsets,NULL, NULL,
						       NULL, NULL, NULL))) {
		    pScrn->driverVersion = NEO_VERSION;
		    pScrn->driverName    = NEO_DRIVER_NAME;
		    pScrn->name          = NEO_NAME;
		    pScrn->Probe         = NEOProbe;
		    pScrn->PreInit       = NEOPreInit;
		    pScrn->ScreenInit    = NEOScreenInit;
		    pScrn->SwitchMode    = NEOSwitchMode;
		    pScrn->AdjustFrame   = NEOAdjustFrame;
		    pScrn->EnterVT       = NEOEnterVT;
		    pScrn->LeaveVT       = NEOLeaveVT;
		    pScrn->FreeScreen    = NEOFreeScreen;
		    pScrn->ValidMode     = NEOValidMode;
		    foundScreen = TRUE;
		}
	    }
	    free(usedChips);
	}
    }

    free(devSections);
    return foundScreen;
}

/* Mandatory */
Bool
NEOPreInit(ScrnInfoPtr pScrn, int flags)
{
    ClockRangePtr clockRanges;
    int i;
    NEOPtr nPtr;
    vgaHWPtr hwp;
    int bppSupport = NoDepth24Support;
    int videoRam = 896;
    int maxClock = 65000;
    int CursorMem = 1024;
    int CursorOff = 0x100;
    int linearSize = 1024;
    int maxWidth = 1024;
    int maxHeight = 1024;
    unsigned char type, dpy;
    int w;
    int apertureSize;
    Bool height_480 = FALSE;
    Bool lcdCenterOptSet = FALSE;
    const char *s;

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

    /* The vgahw module should be loaded here when needed */
    if (!xf86LoadSubModule(pScrn, "vgahw"))
	return FALSE;

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

    /* Allocate the NeoRec driverPrivate */
    if (!NEOGetRec(pScrn)) {
	return FALSE;
    }
# define RETURN \
    { NEOFreeRec(pScrn);\
			    return FALSE;\
					     }

    nPtr = NEOPTR(pScrn);

    /* Since, the capabilities are determined by the chipset the very
     * first thing to do is, figure out the chipset and its capabilities
     */
    /* This driver doesn't expect more than one entity per screen */
    if (pScrn->numEntities > 1)
	RETURN;

    /* This is the general case */
    for (i = 0; i<pScrn->numEntities; i++) {
	nPtr->pEnt = xf86GetEntityInfo(pScrn->entityList[i]);
	nPtr->NeoChipset = nPtr->pEnt->chipset;
	pScrn->chipset = (char *)xf86TokenToString(NEOChipsets,
						   nPtr->pEnt->chipset);
	/* This driver can handle PCI buses */
	if (nPtr->pEnt->location.type == BUS_PCI) {
	    nPtr->PciInfo = xf86GetPciInfoForEntity(nPtr->pEnt->index);
	}
    }
    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Chipset is a ");
    switch(nPtr->NeoChipset){
    case NM2070:
	xf86ErrorF("MagicGraph 128 (NM2070)");
	break;
    case NM2090 :
	xf86ErrorF("MagicGraph 128V (NM2090)");
	break;
    case NM2093 :
	xf86ErrorF("MagicGraph 128ZV (NM2093)");
	break;
    case NM2097 :
	xf86ErrorF("MagicGraph 128ZV+ (NM2097)");
	break;
    case NM2160 :
	xf86ErrorF("MagicGraph 128XD (NM2160)");
	break;
    case NM2200 :
        xf86ErrorF("MagicMedia 256AV (NM2200)");
	break;
    case NM2230 :
        xf86ErrorF("MagicMedia 256AV+ (NM2230)");
	break;
    case NM2360 :
        xf86ErrorF("MagicMedia 256ZX (NM2360)");
	break;
    case NM2380 :
        xf86ErrorF("MagicMedia 256XL+ (NM2380)");
	break;
    }
    xf86ErrorF("\n");

    vgaHWGetIOBase(hwp);
    nPtr->vgaIOBase = hwp->IOBase;
    vgaHWSetStdFuncs(hwp);

    /* Determine the panel type */
    VGAwGR(0x09,0x26);
    type = VGArGR(0x21);
    dpy = VGArGR(0x20);

    /* Determine panel width -- used in NeoValidMode. */
    w = VGArGR(0x20);
    VGAwGR(0x09,0x00);
    switch ((w & 0x18) >> 3) {
    case 0x00 :
	nPtr->NeoPanelWidth  = 640;
	nPtr->NeoPanelHeight = 480;
	break;
    case 0x01 :
	nPtr->NeoPanelWidth  = 800;
	nPtr->NeoPanelHeight = 600;
	break;
    case 0x02 :
	nPtr->NeoPanelWidth  = 1024;
	nPtr->NeoPanelHeight = 768;
	break;
    case 0x03 :
        /* 1280x1024 panel support needs to be added */
#ifdef NOT_DONE
	nPtr->NeoPanelWidth  = 1280;
	nPtr->NeoPanelHeight = 1024;
	break;
#else
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		     "Only 640x480,\n"
                     "     800x600,\n"
                     " and 1024x768 panels are currently supported\n");
	return FALSE;
#endif
    default :
	nPtr->NeoPanelWidth  = 640;
	nPtr->NeoPanelHeight = 480;
	break;
    }

    xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "Panel is a %dx%d %s %s display\n",
	       nPtr->NeoPanelWidth,
	       nPtr->NeoPanelHeight,
	       (type & 0x02) ? "color" : "monochrome",
	       (type & 0x10) ? "TFT" : "dual scan");


    switch (nPtr->NeoChipset){
    case NM2070:
	bppSupport = NoDepth24Support;
	videoRam   = 896;
	maxClock   = 65000;
	CursorMem  = 2048;
	CursorOff  = 0x100;
	linearSize = 1024;
	maxWidth   = 1024;
	maxHeight  = 1024;
	break;
    case NM2090:
    case NM2093:
	bppSupport = Support24bppFb | Support32bppFb |
	    SupportConvert32to24 | PreferConvert32to24;
	videoRam   = 1152;
	maxClock   = 80000;
	CursorMem  = 2048;
	CursorOff  = 0x100;
	linearSize = 2048;
	maxWidth   = 1024;
	maxHeight  = 1024;
	break;
    case NM2097:
	bppSupport = Support24bppFb | Support32bppFb |
	  SupportConvert32to24 | PreferConvert32to24;
	videoRam   = 1152;
	maxClock   = 80000;
	CursorMem  = 1024;
	CursorOff  = 0x100;
	linearSize = 2048;
	maxWidth   = 1024;
	maxHeight  = 1024;
	break;
    case NM2160:
	bppSupport = Support24bppFb | Support32bppFb |
	    SupportConvert32to24 | PreferConvert32to24;
	videoRam   = 2048;
	maxClock   = 90000;
	CursorMem  = 1024;
	CursorOff  = 0x100;
	linearSize = 2048;
	maxWidth   = 1024;
	maxHeight  = 1024;
	break;
    case NM2200:
	bppSupport = Support24bppFb | Support32bppFb |
	    SupportConvert32to24 | PreferConvert32to24;
	videoRam   = 2560;
	maxClock   = 110000;
	CursorMem  = 1024;
	CursorOff  = 0x1000;
	linearSize = 4096;
	maxWidth   = 1280;
	maxHeight  = 1024;  /* ???? */
	break;
    case NM2230:
	bppSupport = Support24bppFb | Support32bppFb |
	    SupportConvert32to24 | PreferConvert32to24;
	videoRam   = 3008;
	maxClock   = 110000;
	CursorMem  = 1024;
	CursorOff  = 0x1000;
	linearSize = 4096;
	maxWidth   = 1280;
	maxHeight  = 1024;  /* ???? */
	break;
    case NM2360:
	bppSupport = Support24bppFb | Support32bppFb |
	    SupportConvert32to24 | PreferConvert32to24;
	videoRam   = 4096;
	maxClock   = 110000;
	CursorMem  = 1024;
	CursorOff  = 0x1000;
	linearSize = 4096;
	maxWidth   = 1280;
	maxHeight  = 1024;  /* ???? */
	break;
    case NM2380:
	bppSupport = Support24bppFb | Support32bppFb |
	    SupportConvert32to24 | PreferConvert32to24;
	videoRam   = 6144;
	maxClock   = 110000;
	CursorMem  = 1024;
	CursorOff  = 0x1000;
	linearSize = 8192;
	maxWidth   = 1280;
	maxHeight  = 1024;  /* ???? */
	break;
    }

    pScrn->monitor = pScrn->confScreen->monitor;

    if (xf86LoadSubModule(pScrn, "ddc")) {
#if 1 /* for DDC1 testing */
	if (!neoDoDDCVBE(pScrn))
	  if (!neoDoDDC2(pScrn))
#endif
	      neoDoDDC1(pScrn);
    }

    if (!xf86SetDepthBpp(pScrn, 16, 0, 0, bppSupport ))
	return FALSE;
    else {
	/* Check that the returned depth is one we support */
	switch (pScrn->depth) {
	case 8:
	case 15:
	case 16:
	    break;
	case 24:
	    if (nPtr->NeoChipset != NM2070)
		break;
	    /* FALLTHROUGH */
	default:
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		       "Given depth (%d) is not supported by this driver\n",
		       pScrn->depth);
	    return FALSE;
	}
    }
    xf86PrintDepthBpp(pScrn);
    if (pScrn->depth == 8)
	pScrn->rgbBits = 6;

    /* 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;

    if (pScrn->depth > 1) {
	Gamma zeros = {0.0, 0.0, 0.0};

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

    nPtr->strangeLockups = TRUE;

    /* Collect all of the relevant option flags (fill in pScrn->options) */
    xf86CollectOptions(pScrn, NULL);
    /* Process the options */
    if (nPtr->NeoChipset == NM2070) {
	if (!(nPtr->Options = malloc(sizeof(NEO_2070_Options))))
	    return FALSE;
	memcpy(nPtr->Options, NEO_2070_Options, sizeof(NEO_2070_Options));
    } else {
	if (!(nPtr->Options = malloc(sizeof(NEOOptions))))
	    return FALSE;
	memcpy(nPtr->Options, NEOOptions, sizeof(NEOOptions));
    }

    xf86ProcessOptions(pScrn->scrnIndex, pScrn->options, nPtr->Options);

    xf86GetOptValBool(nPtr->Options, OPTION_SW_CURSOR,&nPtr->swCursor);
    xf86GetOptValBool(nPtr->Options, OPTION_NO_MMIO,&nPtr->noMMIO);
    xf86GetOptValBool(nPtr->Options, OPTION_INTERN_DISP,&nPtr->internDisp);
    xf86GetOptValBool(nPtr->Options, OPTION_EXTERN_DISP,&nPtr->externDisp);
    if (xf86GetOptValBool(nPtr->Options, OPTION_LCD_CENTER,&nPtr->lcdCenter))
	lcdCenterOptSet = TRUE;
    xf86GetOptValBool(nPtr->Options, OPTION_LCD_STRETCH,&nPtr->noLcdStretch);
    xf86GetOptValBool(nPtr->Options, OPTION_SHADOW_FB,&nPtr->shadowFB);
    xf86GetOptValBool(nPtr->Options, OPTION_SHOWCACHE,&nPtr->showcache);
    nPtr->onPciBurst = TRUE;
    xf86GetOptValBool(nPtr->Options, OPTION_PCI_BURST,&nPtr->onPciBurst);
    xf86GetOptValBool(nPtr->Options,
		      OPTION_PROG_LCD_MODE_REGS,&nPtr->progLcdRegs);
    if (xf86GetOptValBool(nPtr->Options,
		      OPTION_PROG_LCD_MODE_STRETCH,&nPtr->progLcdStretch))
	nPtr->progLcdStretchOpt = TRUE;
    xf86GetOptValBool(nPtr->Options,
		      OPTION_OVERRIDE_VALIDATE_MODE, &nPtr->overrideValidate);
    xf86GetOptValBool(nPtr->Options, OPTION_DISPLAY_HEIGHT_480,&height_480);
    xf86GetOptValBool(nPtr->Options, OPTION_STRANGE_LOCKUPS,
		      &nPtr->strangeLockups);
    nPtr->noAccelSet =
	xf86GetOptValBool(nPtr->Options, OPTION_NOACCEL,&nPtr->noAccel);

    nPtr->rotate = 0;
    if ((s = xf86GetOptValString(nPtr->Options, OPTION_ROTATE))) {
	if(!xf86NameCmp(s, "CW")) {
	    /* accel is disabled below for shadowFB */
	    nPtr->shadowFB = TRUE;
	    nPtr->swCursor = TRUE;
	    nPtr->rotate = 1;
	    xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
		       "Rotating screen clockwise - acceleration disabled\n");
	} else if(!xf86NameCmp(s, "CCW")) {
	    nPtr->shadowFB = TRUE;
	    nPtr->swCursor = TRUE;
	    nPtr->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(xf86GetOptValInteger(nPtr->Options,
			    OPTION_VIDEO_KEY, &(nPtr->videoKey))) {
        xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "video key set to 0x%x\n",
		   nPtr->videoKey);
    } else {
        nPtr->videoKey = (1 << pScrn->offset.red) |
	    (1 << pScrn->offset.green) |
	    (((pScrn->mask.blue >> pScrn->offset.blue) - 1)
	     << pScrn->offset.blue);
    }
    if(xf86GetOptValInteger(nPtr->Options, OPTION_OVERLAYMEM,
			    &(nPtr->overlay))) {
        xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
		   "reserve %d bytes for overlay.\n", nPtr->overlay);
    } else {
	nPtr->overlay = 0;
    }
    nPtr->interlace = 0;
    if(xf86GetOptValInteger(nPtr->Options, OPTION_VIDEO_INTERLACE,
			    &(nPtr->interlace))) {
	if (nPtr->interlace >= 0  &&  nPtr->interlace <= 2){
	    xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "interlace flag = %d\n",
		       nPtr->interlace);
	} else {
	    xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
		       "\"%s\" is not a valid value for "
		       "Option \"Interlaced\"\n", s);
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Valid options are  0..2\n");
        }
    }

    if (height_480
	&& (nPtr->NeoPanelWidth == 800 || nPtr->NeoPanelWidth == 1024)) {
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Overriding Panel height: Set to 480\n");
	nPtr->NeoPanelHeight = 480;
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Disabling LCD stretching for panel height 480\n");
	nPtr->noLcdStretch = TRUE;
	if (!lcdCenterOptSet)
	    nPtr->lcdCenter = TRUE;
    }

    if (nPtr->internDisp && nPtr->externDisp)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Simultaneous LCD/CRT display mode\n");
    else if (nPtr->externDisp)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "External CRT only display mode\n");
    else  if (nPtr->internDisp)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Internal LCD only display mode\n");
    else {
	nPtr->internDisp = ((dpy & 0x02) == 0x02);
    	nPtr->externDisp = ((dpy & 0x01) == 0x01);
	if (nPtr->internDisp && nPtr->externDisp)
	    xf86DrvMsg(pScrn->scrnIndex,X_PROBED,
		       "Simultaneous LCD/CRT display mode\n");
	else if (nPtr->externDisp)
	    xf86DrvMsg(pScrn->scrnIndex,X_PROBED,
		       "External CRT only display mode\n");
	else if (nPtr->internDisp)
	    xf86DrvMsg(pScrn->scrnIndex,X_PROBED,
		       "Internal LCD only display mode\n");
	else {
	    /* this is a fallback if probed values are bogus */
	    nPtr->internDisp = TRUE;
	    xf86DrvMsg(pScrn->scrnIndex,X_DEFAULT,
		       "Internal LCD only display mode\n");
	}
    }

    if (nPtr->noLcdStretch)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Low resolution video modes are not stretched\n");
    if (nPtr->lcdCenter)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Video modes are centered on the display\n");
    if (nPtr->swCursor)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG, "using software cursor\n");
    if (nPtr->noMMIO)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG, "MMIO mode disabled\n");
    if (nPtr->onPciBurst)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG, "using PCI Burst mode\n");
    if (nPtr->strangeLockups)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Option StrangeLockups set: disabling some acceleration\n");
    if (nPtr->showcache)
	xf86DrvMsg(pScrn->scrnIndex,X_CONFIG,
		   "Show cache for debugging\n");

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Falling back to shadow\n");
    nPtr->shadowFB = 1;

    if (nPtr->shadowFB) {
	if (!xf86LoadSubModule(pScrn, "shadow")) {
	    RETURN;
	}
    }

    if (nPtr->shadowFB) {
        nPtr->noAccel = TRUE;
        xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                "Using \"Shadow Framebuffer\" - acceleration disabled\n");
    }

    nPtr->NeoFbMapSize = linearSize * 1024;
    nPtr->NeoCursorOffset = CursorOff;

    if (nPtr->pEnt->device->MemBase) {
	/* XXX Check this matches a PCI base address */
	nPtr->NeoLinearAddr = nPtr->pEnt->device->MemBase;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
		   "FB base address is set at 0x%lX.\n",
		   nPtr->NeoLinearAddr);
    } else {
	nPtr->NeoLinearAddr = 0;
    }

    nPtr->NeoMMIOAddr2 = 0;
    nPtr->NeoMMIOBase2 = NULL;
    if (nPtr->pEnt->device->IOBase && !nPtr->noMMIO) {
	/* XXX Check this matches a PCI base address */
	nPtr->NeoMMIOAddr = nPtr->pEnt->device->IOBase;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
		   "MMIO base address is set at 0x%lX.\n",
		   nPtr->NeoMMIOAddr);
    } else {
	nPtr->NeoMMIOAddr = 0;
    }

    if (nPtr->pEnt->location.type == BUS_PCI) {
	if (!nPtr->NeoLinearAddr) {
	    nPtr->NeoLinearAddr = PCI_REGION_BASE(nPtr->PciInfo, 0, REGION_MEM);
	    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
		       "FB base address is set at 0x%lX.\n",
		       nPtr->NeoLinearAddr);
	}
	if (!nPtr->NeoMMIOAddr && !nPtr->noMMIO) {
	    switch (nPtr->NeoChipset) {
	    case NM2070 :
		nPtr->NeoMMIOAddr = nPtr->NeoLinearAddr + 0x100000;
		break;
	    case NM2090:
	    case NM2093:
		nPtr->NeoMMIOAddr = nPtr->NeoLinearAddr + 0x200000;
		break;
	    case NM2160:
	    case NM2097:
	    case NM2200:
	    case NM2230:
	    case NM2360:
	    case NM2380:
		nPtr->NeoMMIOAddr = PCI_REGION_BASE(nPtr->PciInfo, 1, REGION_MEM);
		nPtr->NeoMMIOAddr2 = PCI_REGION_BASE(nPtr->PciInfo, 2, REGION_MEM);
		break;
	    }
	    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
		       "MMIO base address is set at 0x%lX.\n",
		       nPtr->NeoMMIOAddr);
	    if (nPtr->NeoMMIOAddr2 != 0){
	        xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
		           "MMIO base address2 is set at 0x%lX.\n",
		           nPtr->NeoMMIOAddr2);
	    }
	}
    }
    else
	RETURN;

    if (nPtr->pEnt->device->videoRam != 0) {
	pScrn->videoRam = nPtr->pEnt->device->videoRam;
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "VideoRAM: %d kByte\n",
		   pScrn->videoRam);
    } else {
	pScrn->videoRam = videoRam;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VideoRAM: %d kByte\n",
		   pScrn->videoRam);
    }

    if (nPtr->pEnt->device->dacSpeeds[0] != 0) {
	maxClock = nPtr->pEnt->device->dacSpeeds[0];
	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Max Clock: %d kHz\n",
		   maxClock);
    } else {
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "Max Clock: %d kHz\n",
		   maxClock);
    }

    pScrn->progClock = TRUE;
    /*
     * Setup the ClockRanges, which describe what clock ranges are available,
     * and what sort of modes they can be used for.
     */
    clockRanges = (ClockRangePtr)XNFcallocarray(sizeof(ClockRange), 1);
    clockRanges->next = NULL;
    clockRanges->ClockMulFactor = 1;
    clockRanges->minClock = 11000;   /* guessed §§§ */
    clockRanges->maxClock = maxClock;
    clockRanges->clockIndex = -1;		/* programmable */
	clockRanges->interlaceAllowed = FALSE;
    clockRanges->doubleScanAllowed = TRUE;

    /* Subtract memory for HW cursor */
    if (!nPtr->swCursor)
	nPtr->NeoCursorMem = CursorMem;
    else
	nPtr->NeoCursorMem = 0;
    apertureSize = (pScrn->videoRam * 1024) - nPtr->NeoCursorMem;

    if ((nPtr->NeoPanelWidth == 800) && (nPtr->NeoPanelHeight == 480)) {
	neo800x480Mode.next = pScrn->monitor->Modes;
	pScrn->monitor->Modes = &neo800x480Mode;
    }
    if ((nPtr->NeoPanelWidth == 1024) && (nPtr->NeoPanelHeight == 480)) {
	neo1024x480Mode.next = pScrn->monitor->Modes;
	pScrn->monitor->Modes = &neo1024x480Mode;
    }

    if (!pScrn->monitor->DDC) {
	/*
	 * If the monitor parameters are not specified explicitly, set them
	 * so that 60Hz modes up to the panel size are allowed.
	 */
	if (pScrn->monitor->nHsync == 0) {
	    pScrn->monitor->nHsync = 1;
	    pScrn->monitor->hsync[0].lo = 28;
	    pScrn->monitor->hsync[0].hi =
				60.0 * 1.07 * nPtr->NeoPanelHeight / 1000.0;
	    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
		       "Using hsync range matching panel size: %.2f-%.2f kHz\n",
		       pScrn->monitor->hsync[0].lo,
		       pScrn->monitor->hsync[0].hi);
	}
	if (pScrn->monitor->nVrefresh == 0) {
	    pScrn->monitor->nVrefresh = 1;
	    pScrn->monitor->vrefresh[0].lo = 55.0;
	    pScrn->monitor->vrefresh[0].hi = 65.0;
	    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
		       "Using vsync range for panel: %.2f-%.2f kHz\n",
		       pScrn->monitor->vrefresh[0].lo,
		       pScrn->monitor->vrefresh[0].hi);
	}
    }

    /*
     * For external displays, limit the width to 1024 pixels or less.
     */
    {
       i = xf86ValidateModes(pScrn, pScrn->monitor->Modes,
			  pScrn->display->modes, clockRanges,
			  NULL, 256, maxWidth,(8 * pScrn->bitsPerPixel),/*§§§*/
			  128, maxHeight, pScrn->display->virtualX,
			  pScrn->display->virtualY, apertureSize,
			  LOOKUP_BEST_REFRESH);

       if (i == -1)
           RETURN;
    }

    /* Prune the modes marked as invalid */
    xf86PruneDriverModes(pScrn);

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

    /*
     * Set the CRTC parameters for all of the modes based on the type
     * of mode, and the chipset's interlace requirements.
     *
     * Calling this is required if the mode->Crtc* values are used by the
     * driver and if the driver doesn't provide code to set them.  They
     * are not pre-initialised at all.
     */
    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);

    /* If monitor resolution is set on the command line, use it */
    xf86SetDpi(pScrn, 0, 0);

    if (xf86LoadSubModule(pScrn, "fb") == NULL) {
	RETURN;
    }

    if (!nPtr->swCursor) {
	if (!xf86LoadSubModule(pScrn, "ramdac"))
	    RETURN;
    }
    return TRUE;
}
#undef RETURN

/* Mandatory */
static Bool
NEOEnterVT(ScrnInfoPtr pScrn)
{
    NEOPtr nPtr = NEOPTR(pScrn);

    /* Should we re-save the text mode on each VT enter? */
    if(!neoModeInit(pScrn, pScrn->currentMode))
      return FALSE;

    if (nPtr->video)
	NEOResetVideo(pScrn);

    if (nPtr->NeoHWCursorShown)
	NeoShowCursor(pScrn);
    NEOAdjustFrame(pScrn, pScrn->frameX0, pScrn->frameY0);

    return TRUE;
}

/* Mandatory */
static void
NEOLeaveVT(ScrnInfoPtr pScrn)
{
    NEOPtr nPtr = NEOPTR(pScrn);

    /* Invalidate the cached acceleration registers */
    if (nPtr->NeoHWCursorShown)
	NeoHideCursor(pScrn);
    neoRestore(pScrn, &(VGAHWPTR(pScrn))->SavedReg, &nPtr->NeoSavedReg, TRUE);
    neoLock(pScrn);

}

static void
NEOLoadPalette(
   ScrnInfoPtr pScrn,
   int numColors,
   int *indices,
   LOCO *colors,
   VisualPtr pVisual
){
   int i, index, shift, Gshift;
   vgaHWPtr hwp = VGAHWPTR(pScrn);

   switch(pScrn->depth) {
   case 15:
	shift = Gshift = 1;
	break;
   case 16:
	shift = 0;
        Gshift = 0;
	break;
   default:
	shift = Gshift = 0;
	break;
   }

   for(i = 0; i < numColors; i++) {
        index = indices[i];
        hwp->writeDacWriteAddr(hwp, index);
	DACDelay(hwp);
        hwp->writeDacData(hwp, colors[index].red << shift);
	DACDelay(hwp);
        hwp->writeDacData(hwp, colors[index].green << Gshift);
	DACDelay(hwp);
        hwp->writeDacData(hwp, colors[index].blue << shift);
	DACDelay(hwp);
   }
}

static Bool
NEOCreateScreenResources(ScreenPtr pScreen)
{
	ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
	NEOPtr pNeo = NEOPTR(pScrn);
	PixmapPtr pPixmap;
	Bool ret;

	pScreen->CreateScreenResources = pNeo->CreateScreenResources;
	ret = pScreen->CreateScreenResources(pScreen);
	pScreen->CreateScreenResources = NEOCreateScreenResources;

	if (!ret)
		return FALSE;

	pPixmap = pScreen->GetScreenPixmap(pScreen);

	if (!shadowAdd(pScreen, pPixmap, neoShadowUpdate,
		NULL, 0, NULL)) {
		return FALSE;
	}
	return TRUE;
}

static Bool
NEOShadowInit(ScreenPtr pScreen)
{
	ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
	NEOPtr pNeo = NEOPTR(pScrn);

	if (!shadowSetup(pScreen))
		return FALSE;
	pNeo->CreateScreenResources = pScreen->CreateScreenResources;
	pScreen->CreateScreenResources = NEOCreateScreenResources;

	return TRUE;
}

static Bool
NEOSaveScreen(ScreenPtr pScreen, int mode)
{
    return vgaHWSaveScreen(pScreen, mode);
}

/* Mandatory */
static Bool
NEOScreenInit(ScreenPtr pScreen, int argc, char **argv)
{
    ScrnInfoPtr pScrn;
    vgaHWPtr hwp;
    NEOPtr nPtr;
    NEOACLPtr nAcl;
    VisualPtr visual;
    int allocatebase, freespace, currentaddr;
    unsigned char *FBStart;
    int height, width, displayWidth;

    /*
     * we need to get the ScrnInfoRec for this screen, so let's allocate
     * one first thing
     */
    pScrn = xf86ScreenToScrn(pScreen);
    nPtr = NEOPTR(pScrn);
    nAcl = NEOACLPTR(pScrn);

    hwp = VGAHWPTR(pScrn);
    hwp->MapSize = 0x10000;		/* Standard 64k VGA window */
    /* Map the VGA memory */
    if (!vgaHWMapMem(pScrn))
	return FALSE;

    /* Map the Neo memory and possible MMIO areas */
    if (!neoMapMem(pScrn))
	return FALSE;

    /*
     * next we save the current state and setup the first mode
     */
    neoSave(pScrn);

    if (!neoModeInit(pScrn,pScrn->currentMode))
	return FALSE;
    vgaHWSaveScreen(pScreen,SCREEN_SAVER_ON);
    NEOAdjustFrame(pScrn, pScrn->frameX0, pScrn->frameY0);

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

    /* Setup the visuals we support. */
    if (!miSetVisualTypes(pScrn->depth,
      		      miGetDefaultVisualMask(pScrn->depth),
		      pScrn->rgbBits, pScrn->defaultVisual))
         return FALSE;

    if (!miSetPixmapDepths ()) return FALSE;

    /*
     * Call the framebuffer layer's ScreenInit function, and fill in other
     * pScreen fields.
     */
    displayWidth = pScrn->displayWidth;
    if (nPtr->rotate) {
	height = pScrn->virtualX;
	width = pScrn->virtualY;
    } else {
	width = pScrn->virtualX;
	height = pScrn->virtualY;
    }

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

    {
        int ret = fbScreenInit(pScreen, FBStart,
                               width, height,
                               pScrn->xDpi, pScrn->yDpi,
                               displayWidth, pScrn->bitsPerPixel);
        if (!ret)
            return FALSE;
    }
    if (pScrn->depth > 8) {
        /* 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);

    xf86SetBlackWhitePixels(pScreen);

    if (!nPtr->shadowFB)
	NEODGAInit(pScreen);

    nPtr->NeoHWCursorShown = FALSE;
    nPtr->NeoHWCursorInitialized = FALSE;
    nAcl->UseHWCursor = FALSE;
    nAcl->CursorAddress = -1;

    nAcl->cacheStart = -1;
    nAcl->cacheEnd = -1;
    xf86DrvMsg(pScrn->scrnIndex,X_INFO,
               "Using linear framebuffer at: 0x%08lX\n",
               nPtr->NeoLinearAddr);
    /* Setup pointers to free space in video ram */
    allocatebase = (pScrn->videoRam << 10);
    freespace = allocatebase - pScrn->displayWidth *
        pScrn->virtualY * (pScrn->bitsPerPixel >> 3);
    currentaddr = allocatebase;
    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
               "%d bytes off-screen memory available\n", freespace);

    if (nPtr->swCursor || !nPtr->NeoMMIOBase) {
        xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                   "Using Software Cursor.\n");
    } else if (nPtr->NeoCursorMem <= freespace) {
        currentaddr -= nPtr->NeoCursorMem;
        freespace  -= nPtr->NeoCursorMem;
        /* alignment */
        freespace  -= currentaddr & 0x3FF;
        currentaddr &= 0xfffffc00;
        nAcl->CursorAddress = currentaddr;
        xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                   "Using H/W Cursor.\n");
    } else {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "Too little space for H/W cursor.\n");
    }

    if (!nPtr->noAccel && !nPtr->NeoMMIOBase)
      xf86DrvMsg(pScrn->scrnIndex,X_INFO,
                 "Acceleration disabled when not using MMIO\n");

    if (nPtr->overlay > 0){
        if (nPtr->overlay > freespace){
            xf86DrvMsg(pScrn->scrnIndex,X_INFO,
                       "Can not reserve %d bytes for overlay. "
                       "Resize to %d bytes.\n",
                       nPtr->overlay, freespace);
            nPtr->overlay = freespace;
        }
        currentaddr -= nPtr->overlay;
        freespace -= nPtr->overlay;
        nPtr->overlay_offset = currentaddr;
        xf86DrvMsg(pScrn->scrnIndex,X_INFO,"Overlay at 0x%x\n",
                   nPtr->overlay_offset);
    }

    nAcl->cacheStart = currentaddr - freespace;
    nAcl->cacheEnd = currentaddr;
    freespace = 0;
    if (nAcl->cacheStart < nAcl->cacheEnd) {
        BoxRec AvailFBArea;
        int lines = nAcl->cacheEnd /
            (pScrn->displayWidth * (pScrn->bitsPerPixel >> 3));
        if (!nPtr->noAccel && nPtr->NeoMMIOBase && lines > 1024)
            lines = 1024;
        AvailFBArea.x1 = 0;
        AvailFBArea.y1 = 0;
        AvailFBArea.x2 = pScrn->displayWidth;
        AvailFBArea.y2 = lines;
        xf86InitFBManager(pScreen, &AvailFBArea);

        xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                   "Using %i scanlines of offscreen memory \n",
                   lines - pScrn->virtualY);
    }

    /* Setup the acceleration primitives */
    if (!nPtr->noAccel && nPtr->NeoMMIOBase) {
        Bool ret = FALSE;
        if (nAcl->cacheStart >= nAcl->cacheEnd) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "Too little space for pixmap cache.\n");
        }

        xf86DrvMsg(pScrn->scrnIndex,X_INFO,
                   "Acceleration not available\n");
    }

    xf86SetBackingStore(pScreen);
    xf86SetSilkenMouse(pScreen);

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

    if (nAcl->CursorAddress != -1) {
      /* HW cursor functions */
      if (!NeoCursorInit(pScreen)) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "Hardware cursor initialization failed\n");
	return FALSE;
      }
      nAcl->UseHWCursor = TRUE;
      nPtr->NeoHWCursorInitialized = TRUE;
    } else
      nAcl->UseHWCursor = FALSE;

    if (nPtr->shadowFB) {
        nPtr->refreshArea = neoRefreshArea;

	if(nPtr->rotate) {
	    if (!nPtr->PointerMoved) {
		nPtr->PointerMoved = pScrn->PointerMoved;
		pScrn->PointerMoved = neoPointerMoved;
	    }
	    switch(pScrn->bitsPerPixel) {
	    case 8:	nPtr->refreshArea = neoRefreshArea8;	break;
	    case 16:	nPtr->refreshArea = neoRefreshArea16;	break;
	    case 24:	nPtr->refreshArea = neoRefreshArea24;	break;
	    case 32:	nPtr->refreshArea = neoRefreshArea32;	break;
	    }
	}
#if 0
	ShadowFBInit(pScreen, nPtr->refreshArea);
#else
	NEOShadowInit (pScreen);
#endif
    }

    /* Initialise default colourmap */
    if(!miCreateDefColormap(pScreen))
	return FALSE;

    if (!xf86HandleColormaps(pScreen, 256, pScrn->rgbBits,
                         NEOLoadPalette, NULL,
                         CMAP_PALETTED_TRUECOLOR | CMAP_RELOAD_ON_MODE_SWITCH))
	return FALSE;

    NEOInitVideo(pScreen);

    pScreen->SaveScreen = NEOSaveScreen;

    /* Setup DPMS mode */
    if (nPtr->NeoChipset != NM2070)
	xf86DPMSInit(pScreen, (DPMSSetProcPtr)NeoDisplayPowerManagementSet,
		     0);

    pScrn->memPhysBase = (unsigned long)nPtr->NeoLinearAddr;
    pScrn->fbOffset = 0;

    /* Wrap the current CloseScreen function */
    nPtr->CloseScreen = pScreen->CloseScreen;
    pScreen->CloseScreen = NEOCloseScreen;

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

    return TRUE;
}

/* Mandatory */
Bool
NEOSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    return neoModeInit(pScrn, mode);
}

/* Mandatory */
void
NEOAdjustFrame(ScrnInfoPtr pScrn, int x, int y)
{
    NEOPtr nPtr;
    vgaHWPtr hwp;
    int oldExtCRTDispAddr;
    int Base;

    hwp = VGAHWPTR(pScrn);
    nPtr = NEOPTR(pScrn);

    if (nPtr->showcache && y) {
	int lastline = nPtr->NeoFbMapSize /
	    ((pScrn->displayWidth * pScrn->bitsPerPixel) / 8);

	lastline -= pScrn->currentMode->VDisplay;
	y += pScrn->virtualY - 1;
        if (y > lastline) y = lastline;
    }

    Base = (y * pScrn->displayWidth + x) >> 2;

    /* Scale Base by the number of bytes per pixel. */
    switch (pScrn->depth) {
    case  8 :
	break;
    case 15 :
    case 16 :
	Base *= 2;
	break;
    case 24 :
	Base *= 3;
	break;
    default :
	break;
    }
    /*
     * These are the generic starting address registers.
     */
    VGAwCR(0x0C, (Base & 0x00FF00) >> 8);
    VGAwCR(0x0D, (Base & 0x00FF));

    /*
     * Make sure we don't clobber some other bits that might already
     * have been set. NOTE: NM2200 has a writable bit 3, but it shouldn't
     * be needed.
     */
    oldExtCRTDispAddr = VGArGR(0x0E);
    VGAwGR(0x0E,(((Base >> 16) & 0x07) | (oldExtCRTDispAddr & 0xf8)));
#if 0
    /*
     * This is a workaround for a higher level bug that causes the cursor
     * to be at the wrong position after a virtual screen resolution change
     */
    if (nPtr->NeoHWCursorInitialized) { /*§§§ do we still need this?*/
	NeoRepositionCursor();
    }
#endif
}

/* Mandatory */
static Bool
NEOCloseScreen(ScreenPtr pScreen)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    NEOPtr nPtr = NEOPTR(pScrn);

    if(pScrn->vtSema){
	if (nPtr->NeoHWCursorShown)
	    NeoHideCursor(pScrn);
	neoRestore(pScrn, &(VGAHWPTR(pScrn))->SavedReg, &nPtr->NeoSavedReg, TRUE);

	neoLock(pScrn);
	neoUnmapMem(pScrn);
    }
    if (nPtr->CursorInfo)
	xf86DestroyCursorInfoRec(nPtr->CursorInfo);
    if (nPtr->ShadowPtr)
	free(nPtr->ShadowPtr);

    pScrn->vtSema = FALSE;
    pScreen->CloseScreen = nPtr->CloseScreen;
    return (*pScreen->CloseScreen)(pScreen);
}

/* Optional */
static void
NEOFreeScreen(ScrnInfoPtr pScrn)
{
    if (xf86LoaderCheckSymbol("vgaHWFreeHWRec"))
	vgaHWFreeHWRec(pScrn);
    NEOFreeRec(pScrn);
}

/* Optional */
static ModeStatus
NEOValidMode(ScrnInfoPtr pScrn, DisplayModePtr mode, Bool verbose, int flags)
{
    NEOPtr nPtr = NEOPTR(pScrn);
    int vDisplay = mode->VDisplay * ((mode->Flags & V_DBLSCAN) ? 2 : 1);

    /*
     * Is there any LineCompare Bit 10? Where?
     * The 9 well known VGA bits give us a maximum height of 1024
     */
    if (vDisplay > 1024)
	return MODE_BAD;

    /*
     * Limit the modes to just those allowed by the various NeoMagic
     * chips.
     */

    if (nPtr->overrideValidate) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "display mode validation disabled\n");
    } else {
	/*
	 * When the LCD is active, only allow modes that are (1) equal to
	 * or smaller than the size of the panel and (2) are one of the
	 * following sizes: 1024x768, 800x600, 640x480.
	 */
	if (nPtr->internDisp || !nPtr->externDisp) {
	    /* Is the mode larger than the LCD panel? */
	    if ((mode->HDisplay > nPtr->NeoPanelWidth) ||
		(vDisplay > nPtr->NeoPanelHeight)) {
		xf86DrvMsg(pScrn->scrnIndex,X_INFO, "Removing mode (%dx%d) "
			   "larger than the LCD panel (%dx%d)\n",
			   mode->HDisplay,
			   mode->VDisplay,
			   nPtr->NeoPanelWidth,
			   nPtr->NeoPanelHeight);
		return(MODE_BAD);
	    }

	    /* Is the mode one of the acceptable sizes? */
	    switch (mode->HDisplay) {
	    case 1280:
		if (mode->VDisplay == 1024)
		    return(MODE_OK);
		break;
	    case 1024 :
		if (mode->VDisplay == 768)
		    return(MODE_OK);
		if ((mode->VDisplay == 480) && (nPtr->NeoPanelHeight == 480))
		    return(MODE_OK);
		break;
	    case  800 :
		if (mode->VDisplay == 600)
		    return(MODE_OK);
		if ((mode->VDisplay == 480) && (nPtr->NeoPanelHeight == 480))
		    return(MODE_OK);
		break;
	    case  640 :
		if (mode->VDisplay == 480)
		    return(MODE_OK);
		break;
#if 1
	    case 320:
		if (mode->VDisplay == 240)
		    return(MODE_OK);
		break;
#endif
	    default:
		break;
	    }

	    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Removing mode (%dx%d) that won't "
		       "display properly on LCD\n",
		       mode->HDisplay,
		       mode->VDisplay);
	    return(MODE_BAD);
	}
    }
    return(MODE_OK);
}

static void
neoLock(ScrnInfoPtr pScrn)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);

    VGAwGR(0x09,0x00);
    vgaHWLock(hwp);
}

static void
neoUnlock(ScrnInfoPtr pScrn)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);

    vgaHWUnlock(hwp);
    VGAwGR(0x09,0x26);
}

static Bool
neoMapMem(ScrnInfoPtr pScrn)
{
    NEOPtr nPtr = NEOPTR(pScrn);

    if (!nPtr->noMMIO) {
        if (nPtr->pEnt->location.type == BUS_PCI){
            void** result = (void**)&nPtr->NeoMMIOBase;
            int err = pci_device_map_range(nPtr->PciInfo,
                                           nPtr->NeoMMIOAddr,
                                           0x200000L,
                                           PCI_DEV_MAP_FLAG_WRITABLE,
                                           result);
            if (err)
                return FALSE;

            if (nPtr->NeoMMIOAddr2 != 0){
                result = (void**)&nPtr->NeoMMIOBase2;
                err = pci_device_map_range(nPtr->PciInfo,
                                               nPtr->NeoMMIOAddr2,
                                               0x100000L,
                                               PCI_DEV_MAP_FLAG_WRITABLE,
                                               result);

                if (err)
                    return FALSE;
            }
        } else
#ifdef VIDMEM_MMIO
            nPtr->NeoMMIOBase =
                xf86MapVidMem(pScrn->scrnIndex,
                              VIDMEM_MMIO, nPtr->NeoMMIOAddr,
                              0x200000L);
#endif
        if (nPtr->NeoMMIOBase == NULL)
            return FALSE;
    }

    if (nPtr->pEnt->location.type == BUS_PCI)
    {
        void** result = (void**)&nPtr->NeoFbBase;
        int err = pci_device_map_range(nPtr->PciInfo,
                                       nPtr->NeoLinearAddr,
                                       nPtr->NeoFbMapSize,
                                       PCI_DEV_MAP_FLAG_WRITABLE |
                                       PCI_DEV_MAP_FLAG_WRITE_COMBINE,
                                       result);
        if (err)
            return FALSE;
    }
    else
#ifdef VIDMEM_FRAMEBUFFER
        nPtr->NeoFbBase =
            xf86MapVidMem(pScrn->scrnIndex, VIDMEM_FRAMEBUFFER,
                          (unsigned long)nPtr->NeoLinearAddr,
                          nPtr->NeoFbMapSize);
#endif
    if (nPtr->NeoFbBase == NULL)
        return FALSE;
    return TRUE;
}

/*
 * Unmap the framebuffer and MMIO memory.
 */

static Bool
neoUnmapMem(ScrnInfoPtr pScrn)
{
    NEOPtr nPtr = NEOPTR(pScrn);

    if (nPtr->NeoMMIOBase)
        pci_device_unmap_range(nPtr->PciInfo, (void*)nPtr->NeoMMIOBase, 0x200000L);
    nPtr->NeoMMIOBase = NULL;
    if (nPtr->NeoMMIOBase2)
        pci_device_unmap_range(nPtr->PciInfo, (void*)nPtr->NeoMMIOBase2, 0x100000L);
    nPtr->NeoMMIOBase2 = NULL;
    pci_device_unmap_range(nPtr->PciInfo, (void*)nPtr->NeoFbBase, nPtr->NeoFbMapSize);
    nPtr->NeoFbBase = NULL;

    return TRUE;
}

static void
neoSave(ScrnInfoPtr pScrn)
{
    vgaRegPtr VgaSave = &VGAHWPTR(pScrn)->SavedReg;
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    NEOPtr nPtr = NEOPTR(pScrn);
    NeoRegPtr save;
    int i;

    save = &nPtr->NeoSavedReg;

    VGAwGR(0x09,0x26);
    /*
     * Whatever code is needed to get back to bank zero goes here.
     */
    VGAwGR(0x15,0x00);

    /* get generic registers */
    vgaHWSave(pScrn, VgaSave, VGA_SR_ALL);

    /*
     * The port I/O code necessary to read in the extended registers
     * into the fields of the vgaNeoRec structure goes here.
     */

    save->GeneralLockReg = VGArGR(0x0A);

    save->ExtCRTDispAddr = VGArGR(0x0E);
    if (nPtr->NeoChipset != NM2070) {
	save->ExtCRTOffset = VGArGR(0x0F);
    }
    save->SysIfaceCntl1 = VGArGR(0x10);
    save->SysIfaceCntl2 = VGArGR(0x11);
    save->SingleAddrPage = VGArGR(0x15);
    save->DualAddrPage = VGArGR(0x16);
    save->PanelDispCntlReg1 = VGArGR(0x20);
    save->PanelDispCntlReg2 = VGArGR(0x25);
    save->PanelDispCntlReg3 = VGArGR(0x30);
    save->PanelVertCenterReg1 = VGArGR(0x28);
    save->PanelVertCenterReg2 = VGArGR(0x29);
    save->PanelVertCenterReg3 = VGArGR(0x2A);
    if (nPtr->NeoChipset != NM2070) {
        save->PanelVertCenterReg4 = VGArGR(0x32);
	save->PanelHorizCenterReg1 = VGArGR(0x33);
	save->PanelHorizCenterReg2 = VGArGR(0x34);
	save->PanelHorizCenterReg3 = VGArGR(0x35);
    }
    if (nPtr->NeoChipset == NM2160) {
        save->PanelHorizCenterReg4 = VGArGR(0x36);
    }
    if (nPtr->NeoChipset == NM2200 || nPtr->NeoChipset == NM2230
	|| nPtr->NeoChipset == NM2360 || nPtr->NeoChipset == NM2380) {
	save->PanelHorizCenterReg4 = VGArGR(0x36);
	save->PanelVertCenterReg5  = VGArGR(0x37);
	save->PanelHorizCenterReg5 = VGArGR(0x38);
    }
    save->ExtColorModeSelect = VGArGR(0x90);
    save->VCLK3NumeratorLow = VGArGR(0x9B);
    if (nPtr->NeoChipset == NM2200 || nPtr->NeoChipset == NM2230
	|| nPtr->NeoChipset == NM2360 || nPtr->NeoChipset == NM2380)
	save->VCLK3NumeratorHigh = VGArGR(0x8F);
    save->VCLK3Denominator = VGArGR(0x9F);
    save->ProgramVCLK = TRUE;

    if (save->reg == NULL)
        save->reg = (regSavePtr)XNFcallocarray(sizeof(regSaveRec), 1);
    else
        xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Non-NULL reg in NeoSave: reg=%p\n", (void *)save->reg);

    save->reg->CR[0x23] = VGArCR(0x23);
    save->reg->CR[0x25] = VGArCR(0x25);
    save->reg->CR[0x2F] = VGArCR(0x2F);
    for (i = 0x40; i <= 0x59; i++) {
	save->reg->CR[i] = VGArCR(i);
    }
    for (i = 0x60; i <= 0x69; i++) {
	save->reg->CR[i] = VGArCR(i);
    }
    for (i = 0x70; i <= NEO_EXT_CR_MAX; i++) {
	save->reg->CR[i] = VGArCR(i);
    }

    for (i = 0x0A; i <= NEO_EXT_GR_MAX; i++) {
        save->reg->GR[i] = VGArGR(i);
    }
}

/*
 * neoProgramShadowRegs
 *
 * Setup the shadow registers to their default values.  The NeoSave
 * routines will restore the proper values on server exit.
 */
static void
neoProgramShadowRegs(ScrnInfoPtr pScrn, vgaRegPtr VgaReg, NeoRegPtr restore)
{
    int i;
    Bool noProgramShadowRegs;
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    NEOPtr nPtr = NEOPTR(pScrn);
    Bool prog_lcd;

    /*
     * If display is external only and we want internal
     * we need to program the shadow registers.
     */
    prog_lcd = (((VGArGR(0x20) & 0x3) == 0x1) && nPtr->internDisp);


    /*
     * Convoluted logic for shadow register programming.
     *
     * As far as we know, shadow programming is needed for the 2070,
     * but not in stretched modes.  Special case this code.
     */
    switch (nPtr->NeoChipset) {
    case NM2070:
	/* Program the shadow regs by default */
	noProgramShadowRegs = FALSE;
	if (!nPtr->progLcdRegs && !prog_lcd)
	    noProgramShadowRegs = TRUE;

	if (restore->PanelDispCntlReg2 & 0x84) {
	    /* Don't program by default if in stretch mode */
	    noProgramShadowRegs = TRUE;
	    if (nPtr->progLcdStretch)
		noProgramShadowRegs = FALSE;
	}
	break;
    case NM2090:
    case NM2093:
    case NM2097:
    case NM2160:
    case NM2200:
    case NM2230:
    case NM2360:
    case NM2380:
    default:
	/* Don't program the shadow regs by default */
	noProgramShadowRegs = TRUE;
	if (nPtr->progLcdRegs || prog_lcd)
	    noProgramShadowRegs = FALSE;

	if (restore->PanelDispCntlReg2 & 0x84) {
	    /* Only change the behavior if an option is set */
	    if (nPtr->progLcdStretchOpt)
		noProgramShadowRegs = !nPtr->progLcdStretch;
	}
	break;
    }

    if (noProgramShadowRegs) {
	xf86DrvMsgVerb(pScrn->scrnIndex,X_INFO,5,"Not programming shadow registers\n");
	if (nPtr->NeoSavedReg.reg){
	    for (i = 0x40; i <= 0x59; i++) {
		VGAwCR(i, nPtr->NeoSavedReg.reg->CR[i]);
	    }
	    for (i = 0x60; i <= 0x64; i++) {
		VGAwCR(i, nPtr->NeoSavedReg.reg->CR[i]);
	    }
	}
    } else {
	/*
	 * Program the shadow regs based on the panel width.  This works
	 * fine for normal sized panels, but what about the odd ones like
	 * the Libretto 100 which has an 800x480 panel???
	 */
	switch (nPtr->NeoPanelWidth) {
	case 640 :
	    VGAwCR(0x40,0x5F);
	    VGAwCR(0x41,0x50);
	    VGAwCR(0x42,0x02);
	    VGAwCR(0x43,0x55);
	    VGAwCR(0x44,0x81);
	    VGAwCR(0x45,0x0B);
	    VGAwCR(0x46,0x2E);
	    VGAwCR(0x47,0xEA);
	    VGAwCR(0x48,0x0C);
	    VGAwCR(0x49,0xE7);
	    VGAwCR(0x4A,0x04);
	    VGAwCR(0x4B,0x2D);
	    VGAwCR(0x4C,0x28);
	    VGAwCR(0x4D,0x90);
	    VGAwCR(0x4E,0x2B);
	    VGAwCR(0x4F,0xA0);
	    break;
	case 800 :
	    switch (nPtr->NeoPanelHeight) {
	    case 600:
		VGAwCR(0x40,0x7F);
		VGAwCR(0x41,0x63);
		VGAwCR(0x42,0x02);
		VGAwCR(0x43,0x6C);
		VGAwCR(0x44,0x1C);
		VGAwCR(0x45,0x72);
		VGAwCR(0x46,0xE0);
		VGAwCR(0x47,0x58);
		VGAwCR(0x48,0x0C);
		VGAwCR(0x49,0x57);
		VGAwCR(0x4A,0x73);
		VGAwCR(0x4B,0x3D);
		VGAwCR(0x4C,0x31);
		VGAwCR(0x4D,0x01);
		VGAwCR(0x4E,0x36);
		VGAwCR(0x4F,0x1E);
		if (nPtr->NeoChipset != NM2070) {
		    VGAwCR(0x50,0x6B);
		    VGAwCR(0x51,0x4F);
		    VGAwCR(0x52,0x0E);
		    VGAwCR(0x53,0x58);
		    VGAwCR(0x54,0x88);
		    VGAwCR(0x55,0x33);
		    VGAwCR(0x56,0x27);
		    VGAwCR(0x57,0x16);
		    VGAwCR(0x58,0x2C);
		    VGAwCR(0x59,0x94);
		}
		break;
	    case 480:
		VGAwCR(0x40,0x7F);
		VGAwCR(0x41,0x63);
		VGAwCR(0x42,0x02);
		VGAwCR(0x43,0x6B);
		VGAwCR(0x44,0x1B);
		VGAwCR(0x45,0x72);
		VGAwCR(0x46,0xE0);
		VGAwCR(0x47,0x1C);
		VGAwCR(0x48,0x00);
		VGAwCR(0x49,0x57);
		VGAwCR(0x4A,0x73);
		VGAwCR(0x4B,0x3E);
		VGAwCR(0x4C,0x31);
		VGAwCR(0x4D,0x01);
		VGAwCR(0x4E,0x36);
		VGAwCR(0x4F,0x1E);
		VGAwCR(0x50,0x6B);
		VGAwCR(0x51,0x4F);
		VGAwCR(0x52,0x0E);
		VGAwCR(0x53,0x57);
		VGAwCR(0x54,0x87);
		VGAwCR(0x55,0x33);
		VGAwCR(0x56,0x27);
		VGAwCR(0x57,0x16);
		VGAwCR(0x58,0x2C);
		VGAwCR(0x59,0x94);
		break;
		break;
		/* Not done */
	    }
	    break;
	case 1024 :
	    switch (nPtr->NeoPanelHeight) {
	    case 768:
		VGAwCR(0x40,0xA3);
		VGAwCR(0x41,0x7F);
		VGAwCR(0x42,0x06);
		VGAwCR(0x43,0x85);
		VGAwCR(0x44,0x96);
		VGAwCR(0x45,0x24);
		VGAwCR(0x46,0xE5);
		VGAwCR(0x47,0x02);
		VGAwCR(0x48,0x08);
		VGAwCR(0x49,0xFF);
		VGAwCR(0x4A,0x25);
		VGAwCR(0x4B,0x4F);
		VGAwCR(0x4C,0x40);
		VGAwCR(0x4D,0x00);
		VGAwCR(0x4E,0x44);
		VGAwCR(0x4F,0x0C);
		VGAwCR(0x50,0x7A);
		VGAwCR(0x51,0x56);
		VGAwCR(0x52,0x00);
		VGAwCR(0x53,0x5D);
		VGAwCR(0x54,0x0E);
		VGAwCR(0x55,0x3B);
		VGAwCR(0x56,0x2B);
		VGAwCR(0x57,0x00);
		VGAwCR(0x58,0x2F);
		VGAwCR(0x59,0x18);
		VGAwCR(0x60,0x88);
		VGAwCR(0x61,0x63);
		VGAwCR(0x62,0x0B);
		VGAwCR(0x63,0x69);
		VGAwCR(0x64,0x1A);
		break;
	    case 480:
		VGAwCR(0x40,0xA3);
		VGAwCR(0x41,0x7F);
		VGAwCR(0x42,0x1B);
		VGAwCR(0x43,0x89);
		VGAwCR(0x44,0x16);
		VGAwCR(0x45,0x0B);
		VGAwCR(0x46,0x2C);
		VGAwCR(0x47,0xE8);
		VGAwCR(0x48,0x0C);
		VGAwCR(0x49,0xE7);
		VGAwCR(0x4A,0x09);
		VGAwCR(0x4B,0x4F);
		VGAwCR(0x4C,0x40);
		VGAwCR(0x4D,0x00);
		VGAwCR(0x4E,0x44);
		VGAwCR(0x4F,0x0C);
		VGAwCR(0x50,0x7A);
		VGAwCR(0x51,0x56);
		VGAwCR(0x52,0x00);
		VGAwCR(0x53,0x5D);
		VGAwCR(0x54,0x0E);
		VGAwCR(0x55,0x3B);
		VGAwCR(0x56,0x2A);
		VGAwCR(0x57,0x00);
		VGAwCR(0x58,0x2F);
		VGAwCR(0x59,0x18);
		VGAwCR(0x60,0x88);
		VGAwCR(0x61,0x63);
		VGAwCR(0x62,0x0B);
		VGAwCR(0x63,0x69);
		VGAwCR(0x64,0x1A);
		break;
	    }
	    break;
	case 1280:
#ifdef NOT_DONE
	    VGAwCR(0x40,0x?? );
            .
		.
		.
		VGAwCR(0x64,0x?? );
		break;
#else
		/* Probe should prevent this case for now */
		FatalError("1280 panel support incomplete\n");
#endif
	}
    }
}

static void
neoRestore(ScrnInfoPtr pScrn, vgaRegPtr VgaReg, NeoRegPtr restore,
	     Bool restoreText)
{
    NEOPtr nPtr = NEOPTR(pScrn);
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    unsigned char temp;
    int i;
    Bool clock_hi = FALSE;

    vgaHWProtect(pScrn,TRUE);		/* Blank the screen */

    VGAwGR(0x09,0x26);

    /* Init the shadow registers if necessary */
    neoProgramShadowRegs(pScrn, VgaReg, restore);

    VGAwGR(0x15,0x00);

    VGAwGR(0x0A,restore->GeneralLockReg);

    /*
     * The color mode needs to be set before calling vgaHWRestore
     * to ensure the DAC is initialized properly.
     *
     * NOTE: Make sure we don't change bits make sure we don't change
     * any reserved bits.
     */
    temp = VGArGR(0x90);

    switch (nPtr->NeoChipset) {
    case NM2070 :
	temp &= 0xF0; /* Save bits 7:4 */
	temp |= (restore->ExtColorModeSelect & ~0xF0);
	break;
    case NM2090 :
    case NM2093 :
    case NM2097 :
    case NM2160 :
    case NM2200 :
    case NM2230 :
    case NM2360 :
    case NM2380 :
	temp &= 0x70; /* Save bits 6:4 */
	temp |= (restore->ExtColorModeSelect & ~0x70);
	break;
    }
    VGAwGR(0x90,temp);

    /*
     * In some rare cases a lockup might occur if we don't delay
     * here. (Reported by Miles Lane)
     */
    usleep(200000);
    /*
     * Disable horizontal and vertical graphics and text expansions so
     * that vgaHWRestore works properly.
     */
    temp = VGArGR(0x25);
    temp &= 0x39;
    VGAwGR(0x25, temp);

    /*
     * Sleep for 200ms to make sure that the two operations above have
     * had time to take effect.
     */
    usleep(200000);
    /*
     * This function handles restoring the generic VGA registers.  */
    vgaHWRestore(pScrn, VgaReg,
		 VGA_SR_MODE
		 | (restoreText ? (VGA_SR_FONTS | VGA_SR_CMAP) : 0));

    VGAwGR(0x0E, restore->ExtCRTDispAddr);
    VGAwGR(0x0F, restore->ExtCRTOffset);
    temp = VGArGR(0x10);
    temp &= 0x0F; /* Save bits 3:0 */
    temp |= (restore->SysIfaceCntl1 & ~0x0F);
    VGAwGR(0x10, temp);

    VGAwGR(0x11, restore->SysIfaceCntl2);
    VGAwGR(0x15, restore->SingleAddrPage);
    VGAwGR(0x16, restore->DualAddrPage);

    temp = VGArGR(0x20);
    switch (nPtr->NeoChipset) {
    case NM2070 :
	temp &= 0xFC; /* Save bits 7:2 */
	temp |= (restore->PanelDispCntlReg1 & ~0xFC);
	break;
    case NM2090 :
    case NM2093 :
    case NM2097 :
    case NM2160 :
	temp &= 0xDC; /* Save bits 7:6,4:2 */
	temp |= (restore->PanelDispCntlReg1 & ~0xDC);
	break;
    case NM2200 :
    case NM2230 :
    case NM2360 :
    case NM2380 :
	temp &= 0x98; /* Save bits 7,4:3 */
	temp |= (restore->PanelDispCntlReg1 & ~0x98);
	break;
    }
    VGAwGR(0x20, temp);

    temp = VGArGR(0x25);
    temp &= 0x38; /* Save bits 5:3 */
    temp |= (restore->PanelDispCntlReg2 & ~0x38);
    VGAwGR(0x25, temp);

    if (nPtr->NeoChipset != NM2070) {
	temp = VGArGR(0x30);
	temp &= 0xEF; /* Save bits 7:5 and bits 3:0 */
	temp |= (restore->PanelDispCntlReg3 & ~0xEF);
	VGAwGR(0x30, temp);
    }

    VGAwGR(0x28, restore->PanelVertCenterReg1);
    VGAwGR(0x29, restore->PanelVertCenterReg2);
    VGAwGR(0x2a, restore->PanelVertCenterReg3);

    if (nPtr->NeoChipset != NM2070) {
	VGAwGR(0x32, restore->PanelVertCenterReg4);
	VGAwGR(0x33, restore->PanelHorizCenterReg1);
	VGAwGR(0x34, restore->PanelHorizCenterReg2);
	VGAwGR(0x35, restore->PanelHorizCenterReg3);
    }

    if (nPtr->NeoChipset == NM2160) {
	VGAwGR(0x36, restore->PanelHorizCenterReg4);
    }

    if (nPtr->NeoChipset == NM2200 || nPtr->NeoChipset == NM2230
	|| nPtr->NeoChipset == NM2360 || nPtr->NeoChipset == NM2380) {
        VGAwGR(0x36, restore->PanelHorizCenterReg4);
        VGAwGR(0x37, restore->PanelVertCenterReg5);
        VGAwGR(0x38, restore->PanelHorizCenterReg5);
    }
    if (nPtr->NeoChipset == NM2200 || nPtr->NeoChipset == NM2230
	|| nPtr->NeoChipset == NM2360 || nPtr->NeoChipset == NM2380)
	clock_hi = TRUE;

    /* Program VCLK3 if needed. */
    if (restore->ProgramVCLK
	 && ((VGArGR(0x9B) != restore->VCLK3NumeratorLow)
	    || (VGArGR(0x9F) !=  restore->VCLK3Denominator)
	    || (clock_hi && ((VGArGR(0x8F) & ~0x0f)
			     != (restore->VCLK3NumeratorHigh & ~0x0F))))) {
	VGAwGR(0x9B, restore->VCLK3NumeratorLow);
	if (clock_hi) {
	    temp = VGArGR(0x8F);
	    temp &= 0x0F; /* Save bits 3:0 */
	    temp |= (restore->VCLK3NumeratorHigh & ~0x0F);
	    VGAwGR(0x8F, temp);
	}
	VGAwGR(0x9F, restore->VCLK3Denominator);
    }
    if (restore->biosMode)
	VGAwCR(0x23,restore->biosMode);

    if (restore->reg) {
	VGAwCR(0x23,restore->reg->CR[0x23]);
	VGAwCR(0x25,restore->reg->CR[0x25]);
	VGAwCR(0x2F,restore->reg->CR[0x2F]);
	for (i = 0x40; i <= 0x59; i++) {
	    VGAwCR(i, restore->reg->CR[i]);
	}
	for (i = 0x60; i <= 0x69; i++) {
	    VGAwCR(i, restore->reg->CR[i]);
	}
	for (i = 0x70; i <= NEO_EXT_CR_MAX; i++) {
	    VGAwCR(i, restore->reg->CR[i]);
	}

	for (i = 0x0a; i <= 0x3f; i++) {
	    VGAwGR(i, restore->reg->GR[i]);
	}
	for (i = 0x90; i <= NEO_EXT_GR_MAX; i++) {
	    VGAwGR(i, restore->reg->GR[i]);
	}
    }

    VGAwGR (0x93, 0xc0); /* Gives faster framebuffer writes */

    /* Program vertical extension register */
    if (nPtr->NeoChipset == NM2200 || nPtr->NeoChipset == NM2230
	|| nPtr->NeoChipset == NM2360 || nPtr->NeoChipset == NM2380) {
	VGAwCR(0x70, restore->VerticalExt);
    }

    vgaHWProtect(pScrn, FALSE);		/* Turn on screen */
}

static Bool
neoModeInit(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    NEOPtr nPtr = NEOPTR(pScrn);
    NEOACLPtr nAcl = NEOACLPTR(pScrn);
    int hoffset, voffset;
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    NeoRegPtr NeoNew = &nPtr->NeoModeReg;
    vgaRegPtr NeoStd = &hwp->ModeReg;
    Bool noLcdStretch = nPtr->noLcdStretch;
    int clockMul = 1;

    neoUnlock(pScrn);

    /*
     * This will allocate the datastructure and initialize all of the
     * generic VGA registers.
     */

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

    /*
     * Several registers need to be corrected from the default values
     * assigned by vgaHWinit().
     */
    pScrn->vtSema = TRUE;

    /*
     * The default value assigned by vgaHW.c is 0x41, but this does
     * not work for NeoMagic.
     */
    NeoStd->Attribute[16] = 0x01;

    switch (pScrn->depth) {
    case  8 :
	NeoStd->CRTC[0x13] = pScrn->displayWidth >> 3;
	NeoNew->ExtCRTOffset   = pScrn->displayWidth >> 11;
	NeoNew->ExtColorModeSelect = 0x11;
	break;
    case 15 :
        NeoNew->ExtColorModeSelect = 0x12;
	NeoStd->CRTC[0x13] = pScrn->displayWidth >> 2;
	NeoNew->ExtCRTOffset   = pScrn->displayWidth >> 10;
        break;
    case 16 :
        NeoNew->ExtColorModeSelect = 0x13;
	NeoStd->CRTC[0x13] = pScrn->displayWidth >> 2;
	NeoNew->ExtCRTOffset   = pScrn->displayWidth >> 10;
	break;
    case 24 :
	NeoStd->CRTC[0x13] = (pScrn->displayWidth * 3) >> 3;
	NeoNew->ExtCRTOffset   = (pScrn->displayWidth * 3) >> 11;
	NeoNew->ExtColorModeSelect = 0x14;
	break;
    default :
	break;
    }

    NeoNew->ExtCRTDispAddr = 0x10;

    /* Vertical Extension */
    NeoNew->VerticalExt = (((mode->CrtcVTotal -2) & 0x400) >> 10 )
      | (((mode->CrtcVDisplay -1) & 0x400) >> 9 )
        | (((mode->CrtcVSyncStart) & 0x400) >> 8 )
          | (((mode->CrtcVBlankStart - 1) & 0x400) >> 7 );

    /* Fast write bursts on unless disabled. */
    if (nPtr->onPciBurst) {
	NeoNew->SysIfaceCntl1 = 0x30;
    } else {
	NeoNew->SysIfaceCntl1 = 0x00;
    }

    /* If they are used, enable linear addressing and/or enable MMIO. */
    NeoNew->SysIfaceCntl2 = 0x00;
    NeoNew->SysIfaceCntl2 |= 0x80;
    if (!nPtr->noMMIO)
	NeoNew->SysIfaceCntl2 |= 0x40;

    /* Enable any user specified display devices. */
    NeoNew->PanelDispCntlReg1 = 0x00;
    if (nPtr->internDisp) {
	NeoNew->PanelDispCntlReg1 |= 0x02;
    }
    if (nPtr->externDisp) {
	NeoNew->PanelDispCntlReg1 |= 0x01;
    }

#if 0
    /*
     * This was replaced: if no devices are specified take the
     * probed settings. If the probed settings are bogus fallback
     * to internal only.
     */
    /* If the user did not specify any display devices, then... */
    if (NeoNew->PanelDispCntlReg1 == 0x00) {
	/* Default to internal (i.e., LCD) only. */
	NeoNew->PanelDispCntlReg1 |= 0x02;
    }
#endif
    /* If we are using a fixed mode, then tell the chip we are. */
    switch (mode->HDisplay) {
    case 1280:
	NeoNew->PanelDispCntlReg1 |= 0x60;
        break;
    case 1024:
	NeoNew->PanelDispCntlReg1 |= 0x40;
        break;
    case 800:
	NeoNew->PanelDispCntlReg1 |= 0x20;
        break;
    case 640:
    default:
        break;
    }

    /* Setup shadow register locking. */
    switch (NeoNew->PanelDispCntlReg1 & 0x03) {
    case 0x01 : /* External CRT only mode: */
	NeoNew->GeneralLockReg = 0x00;
	/* We need to program the VCLK for external display only mode. */
	NeoNew->ProgramVCLK = TRUE;
	break;
    case 0x02 : /* Internal LCD only mode: */
    case 0x03 : /* Simultaneous internal/external (LCD/CRT) mode: */
	NeoNew->GeneralLockReg = 0x01;
	/* Don't program the VCLK when using the LCD. */
	NeoNew->ProgramVCLK = FALSE;
	break;
    }

    /*
     * If the screen is to be stretched, turn on stretching for the
     * various modes.
     *
     * OPTION_LCD_STRETCH means stretching should be turned off!
     */
    NeoNew->PanelDispCntlReg2 = 0x00;
    NeoNew->PanelDispCntlReg3 = 0x00;
    nAcl->NoCursorMode = FALSE;

    if ((!noLcdStretch) &&
	(NeoNew->PanelDispCntlReg1 & 0x02)) {
	if (mode->HDisplay == nPtr->NeoPanelWidth) {
	    /*
	     * Don't disable the flag.  It will be needed if another mode
	     * is selected.
	     */
	    /*
	     * No stretching required when the requested display width
	     * equals the panel width.
	     */
	    xf86DrvMsg(pScrn->scrnIndex,X_INFO,"Stretching disabled\n");
	        noLcdStretch = TRUE;
	} else {

	    switch (mode->HDisplay) {
	    case  320 : /* Needs testing.  KEM -- 24 May 98 */
	    case  400 : /* Needs testing.  KEM -- 24 May 98 */
	    case  640 :
	    case  800 :
	    case 1024 :
		NeoNew->PanelDispCntlReg2 |= 0xC6;
		nAcl->NoCursorMode = TRUE;
		break;
	    default   :
		/* No stretching in these modes. */
		xf86DrvMsg(pScrn->scrnIndex,X_INFO,
			   "Stretching disabled not supported in this mode\n");
		noLcdStretch = TRUE;
		break;
	    }
	}
    } else if (mode->Flags & V_DBLSCAN) {
	nAcl->NoCursorMode = TRUE;
    }

    /*
     * If the screen is to be centered, turn on the centering for the
     * various modes.
     */
    NeoNew->PanelVertCenterReg1  = 0x00;
    NeoNew->PanelVertCenterReg2  = 0x00;
    NeoNew->PanelVertCenterReg3  = 0x00;
    NeoNew->PanelVertCenterReg4  = 0x00;
    NeoNew->PanelVertCenterReg5  = 0x00;
    NeoNew->PanelHorizCenterReg1 = 0x00;
    NeoNew->PanelHorizCenterReg2 = 0x00;
    NeoNew->PanelHorizCenterReg3 = 0x00;
    NeoNew->PanelHorizCenterReg4 = 0x00;
    NeoNew->PanelHorizCenterReg5 = 0x00;

    if (nPtr->lcdCenter &&
	(NeoNew->PanelDispCntlReg1 & 0x02)) {
	Bool doCenter = FALSE;

	hoffset = 0;
	voffset = 0;
	if (mode->HDisplay == nPtr->NeoPanelWidth) {
	    /*
	     * No centering required when the requested display width
	     * equals the panel width.
	     */
	} else {
	    NeoNew->PanelDispCntlReg3 |= 0x10;
	    if (noLcdStretch) {
		/* Calculate the horizontal offsets. */
		int HDisplay = mode->HDisplay
		    << ((mode->VDisplay < 480) ? 1 : 0);
		hoffset = ((nPtr->NeoPanelWidth - HDisplay) >> 4) - 1;
		if (mode->VDisplay < 480)
		    hoffset >>= 1;
		doCenter = TRUE;
	    } else {
		/* Stretched modes cannot be centered. */
		hoffset = 0;
	    }
	}
	if (mode->VDisplay == nPtr->NeoPanelHeight) {
	    /*
	     * No centering required when the requested display width
	     * equals the panel width.
	     */
	} else {
	    NeoNew->PanelDispCntlReg2 |= 0x01;
	    if (noLcdStretch) {
		/* Calculate the vertical offsets. */
		int VDisplay = mode->VDisplay
		    << ((mode->Flags & V_DBLSCAN) ? 1 : 0);
		voffset = ((nPtr->NeoPanelHeight - VDisplay) >> 1) - 2;
		doCenter = TRUE;
	    } else {
		/* Stretched modes cannot be centered. */
		voffset = 0;
	    }
	}

	if (doCenter) {
	    switch (mode->HDisplay) {
	    case  320 : /* Needs testing.  KEM -- 24 May 98 */
  		NeoNew->PanelHorizCenterReg3 = hoffset;
		NeoNew->PanelVertCenterReg3  = voffset;
		break;
	    case  400 : /* Needs testing.  KEM -- 24 May 98 */
		NeoNew->PanelHorizCenterReg4 = hoffset;
		NeoNew->PanelVertCenterReg1  = voffset;
		break;
	    case  640 :
		NeoNew->PanelHorizCenterReg1 = hoffset;
		NeoNew->PanelVertCenterReg3  = voffset;
		break;
	    case  800 :
		NeoNew->PanelHorizCenterReg2 = hoffset;
		switch (mode->VDisplay) {
		case 600:
		    NeoNew->PanelVertCenterReg4  = voffset;
		    break;
		case 480:
		    /* Not sure if this is correct */
		    NeoNew->PanelVertCenterReg3  = voffset;
		    break;
		}
		break;
	    case 1024 :
		NeoNew->PanelHorizCenterReg5 = hoffset;
		NeoNew->PanelVertCenterReg5  = voffset;
		break;
	    case 1280 :
	    default   :
		/* No centering in these modes. */
		break;
	    }
	}
    }

    if (!noLcdStretch &&
	(NeoNew->PanelDispCntlReg1 & 0x02))  {
	if (mode->HDisplay != nPtr->NeoPanelWidth)
	    nPtr->videoHZoom = (double)nPtr->NeoPanelWidth/mode->HDisplay;
	if (mode->VDisplay != nPtr->NeoPanelHeight)
	    nPtr->videoVZoom = (double)nPtr->NeoPanelHeight/mode->VDisplay;
    } else {
	nPtr->videoHZoom = 1.0;
	nPtr->videoVZoom = 1.0;
    }
    /* Do double scan */
    if (mode->VDisplay < 480) {
	NeoStd->Sequencer[1] |= 0x8;
	clockMul = 2;
    }

    NeoNew->biosMode = neoFindMode(mode->HDisplay,mode->VDisplay,pScrn->depth);

    /*
     * New->reg should be empty.  Just in
     * case it isn't, warn us and clear it anyway.
     */
    if (NeoNew->reg) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Non-NULL reg in NeoInit: reg=%p\n", (void *)NeoNew->reg);
	free(NeoNew->reg);
	NeoNew->reg = NULL;
    }

    /*
     * Calculate the VCLK that most closely matches the requested dot
     * clock.
     */
    neoCalcVCLK(pScrn, mode->SynthClock*clockMul);

    /* Since we program the clocks ourselves, always use VCLK3. */
    NeoStd->MiscOutReg |= 0x0C;

    neoRestore(pScrn, NeoStd, NeoNew, FALSE);

    return(TRUE);
}

/*
 * neoCalcVCLK --
 *
 * Determine the closest clock frequency to the one requested.
 */
#define REF_FREQ 14.31818
#define MAX_N 127
#define MAX_D 31
#define MAX_F 1

static void
neoCalcVCLK(ScrnInfoPtr pScrn, long freq)
{
    NEOPtr nPtr = NEOPTR(pScrn);

    int n, d, f;
    double f_out;
    double f_diff;
    int n_best = 0, d_best = 1, f_best = 0;
    double f_best_diff = 999999.0;
    double f_target = freq/1000.0;

    for (f = 0; f <= MAX_F; f++)
	for (n = 0; n <= MAX_N; n++)
	    for (d = 1; d <= MAX_D; d++) {
		f_out = (n+1.0)/((d+1.0)*(1<<f))*REF_FREQ;
		f_diff = fabs(f_out-f_target);
		if (f_diff < f_best_diff) {
		    f_best_diff = f_diff;
		    n_best = n;
		    d_best = d;
		    f_best = f;
		}
	    }

    if (nPtr->NeoChipset == NM2200 || nPtr->NeoChipset == NM2230
	|| nPtr->NeoChipset == NM2360 || nPtr->NeoChipset == NM2380) {
        /* NOT_DONE:  We are trying the full range of the 2200 clock.
           We should be able to try n up to 2047 */
	nPtr->NeoModeReg.VCLK3NumeratorLow  = n_best;
	nPtr->NeoModeReg.VCLK3NumeratorHigh = (f_best << 7);
    }
    else {
	nPtr->NeoModeReg.VCLK3NumeratorLow  = n_best | (f_best << 7);
    }
    nPtr->NeoModeReg.VCLK3Denominator = d_best;
#ifdef DEBUG
    ErrorF("neoVCLK: f:%f NumLow=%i NumHi=%i Den=%i Df=%f\n",
	   f_target,
	   nPtr->NeoModeReg.VCLK3NumeratorLow,
	   nPtr->NeoModeReg.VCLK3NumeratorHigh,
	   nPtr->NeoModeReg.VCLK3Denominator,
	   f_best_diff);
#endif
}

/*
 * NeoDisplayPowerManagementSet --
 *
 * Sets VESA Display Power Management Signaling (DPMS) Mode.
 */
static void
NeoDisplayPowerManagementSet(ScrnInfoPtr pScrn, int PowerManagementMode,
			     int flags)
{
    NEOPtr nPtr = NEOPTR(pScrn);
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    unsigned char SEQ01 = 0;
    unsigned char LogicPowerMgmt = 0;
    unsigned char LCD_on = 0;

    if (!pScrn->vtSema)
	return;

    switch (PowerManagementMode) {
    case DPMSModeOn:
	/* Screen: On; HSync: On, VSync: On */
	SEQ01 = 0x00;
	LogicPowerMgmt = 0x00;
	if (nPtr->internDisp || ! nPtr->externDisp)
	    LCD_on = 0x02;
	else
	    LCD_on = 0x00;
	break;
    case DPMSModeStandby:
	/* Screen: Off; HSync: Off, VSync: On */
	SEQ01 = 0x20;
	LogicPowerMgmt = 0x10;
	LCD_on = 0x00;
	break;
    case DPMSModeSuspend:
	/* Screen: Off; HSync: On, VSync: Off */
	SEQ01 = 0x20;
	LogicPowerMgmt = 0x20;
	LCD_on = 0x00;
	break;
    case DPMSModeOff:
	/* Screen: Off; HSync: Off, VSync: Off */
	SEQ01 = 0x20;
	LogicPowerMgmt = 0x30;
	LCD_on = 0x00;
	break;
    }

    /* Turn the screen on/off */
    SEQ01 |= VGArSR(0x01) & ~0x20;
    VGAwSR(0x01, SEQ01);

    /* Turn the LCD on/off */
    LCD_on |= VGArGR(0x20) & ~0x02;
    VGAwGR(0x20, LCD_on);

    /* Set the DPMS mode */
    LogicPowerMgmt |= 0x80;
    LogicPowerMgmt |= VGArGR(0x01) & ~0xF0;
    VGAwGR(0x01,LogicPowerMgmt);
}

static unsigned int
neo_ddc1Read(ScrnInfoPtr pScrn)
{
    register vgaHWPtr hwp = VGAHWPTR(pScrn);
    register unsigned int tmp;

    /* This needs to be investigated: we may have to swap this around */
    while (!(hwp->readST01(hwp)&0x8)) {};
    while (hwp->readST01(hwp)&0x8) {};

    tmp = (VGArGR(0xA1) & 0x08);

    return (tmp);
}

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

static xf86MonPtr
neo_ddc1(ScrnInfoPtr pScrn)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    unsigned int reg1, reg2, reg3;
    xf86MonPtr ret;

    /* initialize chipset */
    reg1 = VGArCR(0x21);
    reg2 = VGArCR(0x1D);
    reg3 = VGArCR(0xA1);
    VGAwCR(0x21,0x00);
    VGAwCR(0x1D,0x01);  /* some Voodoo */
    VGAwGR(0xA1,0x2F);
    ret =  xf86DoEDID_DDC1(pScrn,neo_ddc1SetSpeed,neo_ddc1Read);
    /* undo initialization */
    VGAwCR(0x21,reg1);
    VGAwCR(0x1D,reg2);
    VGAwGR(0xA1,reg3);
    return ret;
}

static Bool
neoDoDDC1(ScrnInfoPtr pScrn)
{
    Bool ret = FALSE;
    vgaHWPtr hwp = VGAHWPTR(pScrn);

    VGAwGR(0x09,0x26);
    ret = xf86SetDDCproperties(pScrn,
				xf86PrintEDID(neo_ddc1(pScrn)));
    VGAwGR(0x09,0x00);

    return ret;
}

static Bool
neoDoDDC2(ScrnInfoPtr pScrn)
{
    NEOPtr nPtr = NEOPTR(pScrn);
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    Bool ret = FALSE;

    VGAwGR(0x09,0x26);
    if (xf86LoadSubModule(pScrn, "i2c")) {
	if (neo_I2CInit(pScrn)) {
	    ret = xf86SetDDCproperties(pScrn,xf86PrintEDID(xf86DoEDID_DDC2(
					   pScrn,nPtr->I2C)));
	}
    }
    VGAwGR(0x09,0x00);

    return ret;
}

static Bool
neoDoDDCVBE(ScrnInfoPtr pScrn)
{
    NEOPtr nPtr = NEOPTR(pScrn);
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    vbeInfoPtr pVbe;
    Bool ret = FALSE;

    VGAwGR(0x09,0x26);
    if (xf86LoadSubModule(pScrn, "vbe")) {
        if ((pVbe = VBEInit(NULL,nPtr->pEnt->index))) {
	  ret = xf86SetDDCproperties(
				     pScrn,xf86PrintEDID(vbeDoEDID(pVbe,NULL)));
	  vbeFree(pVbe);
	}
    }
    VGAwGR(0x09,0x00);
    return ret;
}

static int
neoFindMode(int xres, int yres, int depth)
{
    int xres_s;
    int i, size;
    biosMode *mode;

    switch (depth) {
    case 8:
	size = sizeof(bios8) / sizeof(biosMode);
	mode = bios8;
	break;
    case 15:
	size = sizeof(bios15) / sizeof(biosMode);
	mode = bios15;
	break;
    case 16:
	size = sizeof(bios16) / sizeof(biosMode);
	mode = bios16;
	break;
    case 24:
	size = sizeof(bios24) / sizeof(biosMode);
	mode = bios24;
	break;
    default:
	return 0;
    }

    for (i = 0; i < size; i++) {
	if (xres <= mode[i].x_res) {
	    xres_s = mode[i].x_res;
	    for (; i < size; i++) {
		if (mode[i].x_res != xres_s)
		    return mode[i-1].mode;
		if (yres <= mode[i].y_res)
		    return mode[i].mode;
	    }
	}
    }
    return mode[size - 1].mode;

}

static void
neoProbeDDC(ScrnInfoPtr pScrn, int index)
{
    vbeInfoPtr pVbe;

    if (xf86LoadSubModule(pScrn, "vbe")) {
        if ((pVbe = VBEInit(NULL,index))) {
	    ConfiguredMonitor = vbeDoEDID(pVbe, NULL);
	    vbeFree(pVbe);
	}
    }
}
