/*
 * Copyright 2001 by Alan Hourihane, Sychdyn, North Wales, UK.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Alan Hourihane not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Alan Hourihane makes no representations
 * about the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * ALAN HOURIHANE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL ALAN HOURIHANE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors:  Alan Hourihane, <alanh@fairlite.demon.co.uk>
 *
 * A driver for the following PCMCIA cards...
 * 		Hewlett Packards HP VGA Out (Model F1252A)
 *		Colorgraphics Voyager VGA
 *
 * Tested running under a Compaq IPAQ Pocket PC running Linux
 */
/* $XFree86: xc/programs/Xserver/hw/kdrive/pcmcia/pcmcia.c,v 1.5 2001/07/20 19:35:30 keithp Exp $ */

#include "pcmcia.h"
#define extern
#include <asm/io.h>
#undef extern

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

#include "modes.h"

extern void
tridentUpdatePacked (ScreenPtr pScreen,
		    shadowBufPtr pBuf);
extern void
cirrusUpdatePacked (ScreenPtr pScreen,
		    shadowBufPtr pBuf);

Bool
pcmciaCardInit (KdCardInfo *card)
{
    pcmciaCardInfo	*pcmciac;
    CARD8		r9;

    pcmciac = (pcmciaCardInfo *) xalloc (sizeof (pcmciaCardInfo));
    if (!pcmciac)
	return FALSE;
    
    pcmciac->cop_base = (CARD8 *) KdMapDevice (PCMCIA_COP_BASE(card),
					       PCMCIA_COP_SIZE(card));
    
    r9 = pcmciaReadIndex (pcmciac, 0x3c4, 0x09);
    /* 
     * Crude detection....
     * The trident chip has a read only register at 0x09, which returns 0x4.
     * If it's not that, we assume the cirrus chip.
     * BREAKAGE.! If we have an anonymous PCMCIA card inserted, we could 
     * potentially smash something here. FIXME !
     */
    if (r9 == 0x04) {
    	ErrorF("PCMCIA: Found HP VGA card\n");
	pcmciac->HP = TRUE;	/* Select HP VGA Out Card */
    } else {
    	ErrorF("PCMCIA: Found Voyager VGA card\n");
	pcmciac->HP = FALSE;	/* Select Voyager VGA Card */
    }

    if (pcmciac->HP) {
    	/* needed by the accelerator - later */
    	pcmciac->cop = (Cop *) (pcmciac->cop_base + TRIDENT_COP_OFF(card));
    }

    /*
     * Map frame buffer 
     */
    if (pcmciac->HP)
    	pcmciac->fb = KdMapDevice (0x2ce00000, 0x80000);
    else
    	pcmciac->fb = KdMapDevice (0x2c0a0000, 0x10000); /*64K bank switched*/

    if (!pcmciac->fb)
	return FALSE;

    pcmciac->window = 0;

    card->driver = pcmciac;

    return TRUE;
}

Bool
pcmciaScreenInit (KdScreenInfo *screen)
{
    pcmciaCardInfo	*pcmciac = screen->card->driver;
    pcmciaScreenInfo	*pcmcias;
    int			screen_size, memory;
    int			i;

    pcmcias = (pcmciaScreenInfo *) xalloc (sizeof (pcmciaScreenInfo));
    if (!pcmcias)
	return FALSE;
    memset (pcmcias, '\0', sizeof (pcmciaScreenInfo));

    /* if (!pcmciac->cop) */
	screen->dumb = TRUE;

    /* default to 8bpp */
    if (!screen->fb[0].depth)
	screen->fb[0].depth = 8;

    /* default to 60Hz refresh */
    if (!screen->rate)
	screen->rate = 60;

    i = 0;
    pcmcias->Mode = -1;
    while (pcmciaDefaultModes[i].Width != 0) {
	if ( (screen->width  == pcmciaDefaultModes[i].Width) &&
	     (screen->height == pcmciaDefaultModes[i].Height) &&
	     (screen->rate   == pcmciaDefaultModes[i].Refresh) ) {
		pcmcias->Mode = i;
		break;
	}
	i++;
    }

    if ( pcmcias->Mode == -1 ) {
	ErrorF("PCMCIA: no matching vesa mode for screen selection, aborting.\n");
	ErrorF("PCMCIA: use -listmodes to check for supported list of modes.\n");
	return FALSE; /* end of list */
    }

    pcmcias->rotation = screen->rotation;

    memory = 512 * 1024;
    pcmcias->screen = pcmciac->fb;

    if (pcmciac->HP && !screen->softCursor && screen->fb[0].depth == 8) {
	/* Let's do hw cursor for the HP card, only in 8bit mode though */
    	pcmcias->cursor_base = pcmcias->screen + memory - 4096; 
    	memory -= 4096;
    }

    if (screen->fb[0].depth == 4) {
	ErrorF("PCMCIA: depth 4 isn't supported.\n");
    	return FALSE; /* screen->fb[0].bitsPerPixel = 4; need fb to support it*/
    } else
    if (screen->fb[0].depth == 8) 
    	screen->fb[0].bitsPerPixel = 8;
    else
    if (screen->fb[0].depth == 15 ||
        screen->fb[0].depth == 16)
    	screen->fb[0].bitsPerPixel = 16;

    if ( (screen->width * screen->height * 
	 (screen->fb[0].bitsPerPixel / 8)) > memory) {
	ErrorF("PCMCIA: Not enough memory for resolution requested, aborting.\n");
	return FALSE;
    }

    screen->fb[0].pixelStride = screen->width;
    screen->fb[0].byteStride = screen->width * (screen->fb[0].bitsPerPixel >>3);

    screen->fb[0].frameBuffer = pcmciac->fb;
    switch (screen->fb[0].depth) {
    case 4:
	screen->fb[0].visuals = ((1 << StaticGray) |
			   (1 << GrayScale) |
			   (1 << StaticColor));
	screen->fb[0].blueMask  = 0x00;
	screen->fb[0].greenMask = 0x00;
	screen->fb[0].redMask   = 0x00;
	break;
    case 8:
	screen->fb[0].visuals = ((1 << StaticGray) |
			   (1 << GrayScale) |
			   (1 << StaticColor) |
			   (1 << PseudoColor) |
			   (1 << TrueColor) |
			   (1 << DirectColor));
	screen->fb[0].blueMask  = 0x00;
	screen->fb[0].greenMask = 0x00;
	screen->fb[0].redMask   = 0x00;
	break;
    case 15:
	screen->fb[0].visuals = (1 << TrueColor);
	screen->fb[0].blueMask  = 0x001f;
	screen->fb[0].greenMask = 0x03e0;
	screen->fb[0].redMask   = 0x7c00;
	break;
    case 16:
	screen->fb[0].visuals = (1 << TrueColor);
	screen->fb[0].blueMask  = 0x001f;
	screen->fb[0].greenMask = 0x07e0;
	screen->fb[0].redMask   = 0xf800;
	break;
    }
    screen_size = screen->fb[0].byteStride * screen->height;

    screen->driver = pcmcias;

    return TRUE;
}

void *
tridentWindowLinear (ScreenPtr	pScreen,
		     CARD32	row,
		     CARD32	offset,
		     int	mode,
		     CARD32	*size,
		     void 	*closure)
{
    KdScreenPriv(pScreen);
    pcmciaCardInfo	*pcmciac = pScreenPriv->card->driver;

    if (!pScreenPriv->enabled)
	return 0;

    *size = pScreenPriv->screen->fb[0].byteStride;
    return (CARD8 *) pcmciac->fb + row * pScreenPriv->screen->fb[0].byteStride + offset;
}

void *
cirrusWindowWindowed (ScreenPtr	pScreen,
		     CARD32	row,
		     CARD32	offset,
		     int	mode,
		     CARD32	*size,
		     void 	*closure)
{
    KdScreenPriv(pScreen);
    pcmciaCardInfo	*pcmciac = pScreenPriv->card->driver;
    int bank, boffset;

    if (!pScreenPriv->enabled)
	return 0;

    bank = (row * pScreenPriv->screen->fb[0].byteStride) / 0x1000;
    pcmciaWriteIndex(pcmciac, 0x3ce, 0x0B, 0x0c);
    pcmciaWriteIndex(pcmciac, 0x3ce, 0x09, bank);
    pcmciaWriteIndex(pcmciac, 0x3ce, 0x0A, bank);
    *size = pScreenPriv->screen->fb[0].byteStride;
    return (CARD8 *) pcmciac->fb + (row * pScreenPriv->screen->fb[0].byteStride) - (bank * 0x1000) + offset;
}

LayerPtr
pcmciaLayerCreate (ScreenPtr pScreen)
{
    KdScreenPriv(pScreen);
    KdScreenInfo	*screen = pScreenPriv->screen;
    pcmciaCardInfo	*pcmciac = pScreenPriv->card->driver;
    pcmciaScreenInfo	*pcmcias = (pcmciaScreenInfo *) pScreenPriv->screen->driver;
    ShadowUpdateProc	update;
    ShadowWindowProc	window;
    KdMouseMatrix	m;
    PixmapPtr		pPixmap;
    int			kind;

    switch (pcmcias->rotation) {
    case 0:
	pScreen->width = screen->width;
	pScreen->height = screen->height;
	pScreen->mmWidth = screen->width_mm;
	pScreen->mmHeight = screen->height_mm;
	m.matrix[0][0] = 1; m.matrix[0][1] = 0; m.matrix[0][2] = 0;
	m.matrix[1][0] = 0; m.matrix[1][1] = 1; m.matrix[1][2] = 0;
	break;
    case 90:
	pScreen->width = screen->height;
	pScreen->height = screen->width;
	pScreen->mmWidth = screen->height_mm;
	pScreen->mmHeight = screen->width_mm;
	m.matrix[0][0] = 0; m.matrix[0][1] = -1; m.matrix[0][2] = screen->height - 1;
	m.matrix[1][0] = 1; m.matrix[1][1] = 0; m.matrix[1][2] = 0;
	break;
    case 180:
	pScreen->width = screen->width;
	pScreen->height = screen->height;
	pScreen->mmWidth = screen->width_mm;
	pScreen->mmHeight = screen->height_mm;
	m.matrix[0][0] = -1; m.matrix[0][1] = 0; m.matrix[0][2] = screen->width - 1;
	m.matrix[1][0] = 0; m.matrix[1][1] = -1; m.matrix[1][2] = screen->height - 1;
	break;
    case 270:
	pScreen->width = screen->height;
	pScreen->height = screen->width;
	pScreen->mmWidth = screen->height_mm;
	pScreen->mmHeight = screen->width_mm;
	m.matrix[0][0] = 0; m.matrix[0][1] = 1; m.matrix[0][2] = 0;
	m.matrix[1][0] = -1; m.matrix[1][1] = 0; m.matrix[1][2] = screen->width - 1;
	break;
    }
    KdSetMouseMatrix (&m);

    if (pcmciac->HP) {
    	window = tridentWindowLinear;
	switch (pcmcias->rotation) {
	case 0:
    		update = tridentUpdatePacked;
		break;
	case 90:
 		switch (pScreenPriv->screen->fb[0].bitsPerPixel) {
 		case 8:
		    update = shadowUpdateRotate8_90; break;
 		case 16:
		    update = shadowUpdateRotate16_90; break;
 		}
		break;
	    case 180:
		switch (pScreenPriv->screen->fb[0].bitsPerPixel) {
		case 8:
		    update = shadowUpdateRotate8_180; break;
		case 16:
		    update = shadowUpdateRotate16_180; break;
		}
		break;
	    case 270:
		switch (pScreenPriv->screen->fb[0].bitsPerPixel) {
		case 8:
		    update = shadowUpdateRotate8_270; break;
		case 16:
		    update = shadowUpdateRotate16_270; break;
		}
		break;
	}
    } else {
    	window = cirrusWindowWindowed;
	switch (pcmcias->rotation) {
	case 0:
    		update = cirrusUpdatePacked;
		break;
	case 90:
 		switch (pScreenPriv->screen->fb[0].bitsPerPixel) {
 		case 8:
		    update = shadowUpdateRotate8_90; break;
 		case 16:
		    update = shadowUpdateRotate16_90; break;
 		}
		break;
	    case 180:
		switch (pScreenPriv->screen->fb[0].bitsPerPixel) {
		case 8:
		    update = shadowUpdateRotate8_180; break;
		case 16:
		    update = shadowUpdateRotate16_180; break;
		}
		break;
	    case 270:
		switch (pScreenPriv->screen->fb[0].bitsPerPixel) {
		case 8:
		    update = shadowUpdateRotate8_270; break;
		case 16:
		    update = shadowUpdateRotate16_270; break;
		}
		break;
	}
    }

    if (!update)
	abort ();

    kind = LAYER_SHADOW;
    pPixmap = 0;

    return LayerCreate (pScreen, kind, screen->fb[0].depth, 
			pPixmap, update, window, pcmcias->rotation, 0);
}

#ifdef RANDR
Bool
pcmciaRandRGetInfo (ScreenPtr pScreen, Rotation *rotations)
{
    KdScreenPriv(pScreen);
    pcmciaCardInfo	    *pcmciac = pScreenPriv->card->driver;
    KdScreenInfo	    *screen = pScreenPriv->screen;
    pcmciaScreenInfo	*pcmcias = (pcmciaScreenInfo *) pScreenPriv->screen->driver;
    RRVisualGroupPtr	    pVisualGroup;
    RRGroupOfVisualGroupPtr pGroupOfVisualGroup;
    RRScreenSizePtr	    pSize;
    Rotation		    rotateKind;
    int			    rotation;
    int			    n;
    
    *rotations = RR_Rotate_0|RR_Rotate_90|RR_Rotate_180|RR_Rotate_270;
    
    for (n = 0; n < pScreen->numDepths; n++)
	if (pScreen->allowedDepths[n].numVids)
	    break;
    if (n == pScreen->numDepths)
	return FALSE;
    
    pVisualGroup = RRCreateVisualGroup (pScreen);
    if (!pVisualGroup)
	return FALSE;
    if (!RRAddDepthToVisualGroup (pScreen,
				pVisualGroup,
				&pScreen->allowedDepths[n]))
    {
	RRDestroyVisualGroup (pScreen, pVisualGroup);
	return FALSE;
    }

    pVisualGroup = RRRegisterVisualGroup (pScreen, pVisualGroup);
    if (!pVisualGroup)
	return FALSE;
    
    pGroupOfVisualGroup = RRCreateGroupOfVisualGroup (pScreen);

    if (!RRAddVisualGroupToGroupOfVisualGroup (pScreen,
					 pGroupOfVisualGroup,
					 pVisualGroup))
    {
	RRDestroyGroupOfVisualGroup (pScreen, pGroupOfVisualGroup);
	/* pVisualGroup left until screen closed */
	return FALSE;
    }

    pGroupOfVisualGroup = RRRegisterGroupOfVisualGroup (pScreen, pGroupOfVisualGroup);
    if (!pGroupOfVisualGroup)
	return FALSE;
    
    pSize = RRRegisterSize (pScreen,
			    screen->width,
			    screen->height,
			    screen->width_mm,
			    screen->height_mm,
			    pGroupOfVisualGroup);
    
    rotation = pcmcias->rotation - screen->rotation;
    if (rotation < 0)
	rotation += 360;

    switch (rotation)
    {
    case 0:
	rotateKind = RR_Rotate_0;
	break;
    case 90:
	rotateKind = RR_Rotate_90;
	break;
    case 180:
	rotateKind = RR_Rotate_180;
	break;
    case 270:
	rotateKind = RR_Rotate_270;
	break;
    }

    RRSetCurrentConfig (pScreen, rotateKind, pSize, pVisualGroup);
    
     return TRUE;
 }
 
int
pcmciaLayerAdd (WindowPtr pWin, pointer value)
{
    ScreenPtr	    pScreen = pWin->drawable.pScreen;
    LayerPtr	    pLayer = (LayerPtr) value;

    if (!LayerWindowAdd (pScreen, pLayer, pWin))
	return WT_STOPWALKING;

    return WT_WALKCHILDREN;
}

int
pcmciaLayerRemove (WindowPtr pWin, pointer value)
{
    ScreenPtr	    pScreen = pWin->drawable.pScreen;
    LayerPtr	    pLayer = (LayerPtr) value;

    LayerWindowRemove (pScreen, pLayer, pWin);

    return WT_WALKCHILDREN;
}

pcmciaRandRSetConfig (ScreenPtr		pScreen,
		     Rotation		rotateKind,
		     RRScreenSizePtr	pSize,
		     RRVisualGroupPtr	pVisualGroup)
{
    KdScreenPriv(pScreen);
    KdScreenInfo	*screen = pScreenPriv->screen;
    FbdevPriv		*priv = pScreenPriv->card->driver;
    pcmciaScreenInfo	*pcmcias = (pcmciaScreenInfo *) pScreenPriv->screen->driver;
    int			rotation;
    Bool		wasEnabled = pScreenPriv->enabled;

    /*
     * The only thing that can change is rotation
     */
    switch (rotateKind)
    {
    case RR_Rotate_0:
	rotation = screen->rotation;
	break;
    case RR_Rotate_90:
	rotation = screen->rotation + 90;
	break;
    case RR_Rotate_180:
	rotation = screen->rotation + 180;
	break;
    case RR_Rotate_270:
	rotation = screen->rotation + 270;
	break;
    }
    if (rotation >= 360)
	rotation -= 360;

    if (pcmcias->rotation != rotation)
    {
	LayerPtr	pNewLayer;
	int		kind;
	int		oldrotation = pcmcias->rotation;
	int		oldwidth = pScreen->width;
	int		oldheight = pScreen->height;
	PixmapPtr	pPixmap;

	if (wasEnabled)
	    KdDisableScreen (pScreen);
	
	pcmcias->rotation = rotation;
	pNewLayer = pcmciaLayerCreate (pScreen);
	if (!pNewLayer)
	{
	    pcmcias->rotation = oldrotation;
	}
	if (WalkTree (pScreen, pcmciaLayerAdd, (pointer) pNewLayer) == WT_STOPWALKING)
	{
	    WalkTree (pScreen, pcmciaLayerRemove, (pointer) pNewLayer);
	    LayerDestroy (pScreen, pNewLayer);
	    pcmcias->rotation = oldrotation;
	    pScreen->width = oldwidth;
	    pScreen->height = oldheight;
	    if (wasEnabled)
		KdEnableScreen (pScreen);
	    return FALSE;
	}
        WalkTree (pScreen, pcmciaLayerRemove, (pointer) pcmcias->pLayer);
	LayerDestroy (pScreen, pcmcias->pLayer);
	pcmcias->pLayer = pNewLayer;
	if (wasEnabled)
	    KdEnableScreen (pScreen);
    }
    return TRUE;
}

Bool
pcmciaRandRInit (ScreenPtr pScreen)
{
    rrScrPrivPtr    pScrPriv;
    
    if (!RRScreenInit (pScreen))
	return FALSE;

    pScrPriv = rrGetScrPriv(pScreen);
    pScrPriv->rrGetInfo = pcmciaRandRGetInfo;
    pScrPriv->rrSetConfig = pcmciaRandRSetConfig;
    return TRUE;
}
#endif

Bool
pcmciaInitScreen (ScreenPtr pScreen)
{
    KdScreenPriv(pScreen);
    FbdevPriv		*priv = pScreenPriv->card->driver;
    pcmciaScreenInfo	*pcmcias = (pcmciaScreenInfo *) pScreenPriv->screen->driver;

    if (!LayerStartInit (pScreen))
	return FALSE;
    if (!LayerFinishInit (pScreen))
	return FALSE;
    pcmcias->pLayer = pcmciaLayerCreate (pScreen);
    if (!pcmcias->pLayer)
	return FALSE;
#ifdef RANDR
    if (!pcmciaRandRInit (pScreen))
	return FALSE;
#endif
    return TRUE;
}

CARD8
pcmciaReadIndex (pcmciaCardInfo *pcmciac, CARD16 port, CARD8 index)
{
    CARD8   value;
    
    pcmciac->cop_base[port] = index;
    value = pcmciac->cop_base[port+1];
    return value;
}

void
pcmciaWriteIndex (pcmciaCardInfo *pcmciac, CARD16 port, CARD8 index, CARD8 value)
{
    pcmciac->cop_base[port] = index;
    pcmciac->cop_base[port+1] = value;
}

CARD8
pcmciaReadReg (pcmciaCardInfo *pcmciac, CARD16 port)
{
    CARD8   value;

    value = pcmciac->cop_base[port];

    return value;
}

void
pcmciaWriteReg (pcmciaCardInfo *pcmciac, CARD16 port, CARD8 value)
{
    pcmciac->cop_base[port] = value;
}


void
pcmciaPause ()
{
    struct timeval  tv;

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

void
pcmciaPreserve (KdCardInfo *card)
{
}

/* CLOCK_FACTOR is double the osc freq in kHz (osc = 14.31818 MHz) */
#define CLOCK_FACTOR 28636

/* stability constraints for internal VCO -- MAX_VCO also determines the maximum Video pixel clock */
#define MIN_VCO CLOCK_FACTOR
#define MAX_VCO 111000

/* clock in kHz is (numer * CLOCK_FACTOR / (denom & 0x3E)) >> (denom & 1) */
#define VCOVAL(n, d) \
     ((((n) & 0x7F) * CLOCK_FACTOR / ((d) & 0x3E)) )

#define CLOCKVAL(n, d) \
     (VCOVAL(n, d) >> ((d) & 1))

int CirrusFindClock(freq, max_clock, num_out, den_out)
	int freq;
	int max_clock;
	int *num_out;
	int *den_out;
{
	int n;
	int num = 0, den = 0;
	int mindiff;

	/*
	 * If max_clock is greater than the MAX_VCO default, ignore
	 * MAX_VCO. On the other hand, if MAX_VCO is higher than max_clock,
	 * make use of the higher MAX_VCO value.
	 */
	if (MAX_VCO > max_clock)
		max_clock = MAX_VCO;

	mindiff = freq; 
	for (n = 0x10; n < 0x7f; n++) {
		int d;
		for (d = 0x14; d < 0x3f; d++) {
			int c, diff;
			/* Avoid combinations that can be unstable. */
			if ((VCOVAL(n, d) < MIN_VCO) || (VCOVAL(n, d) > max_clock))
				continue;
			c = CLOCKVAL(n, d);
			diff = abs(c - freq);
			if (diff < mindiff) {
				mindiff = diff;
				num = n;
				den = d;
			}
		}
	}

	*num_out = num;
	*den_out = den;

	return 0;
}


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

    p = q = r = s = 0;

    startn = 0;
    endn = 121;
    endm = 31;
    endk = 1;

    freq = clock;

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

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

    /* N is first 7bits, first M bit is 8th bit */
    *a = ((1 & q) << 7) | p;
    /* first 4bits are rest of M, 1bit for K value */
    *b = (((q & 0xFE) >> 1) | (r << 4));
}

Bool
pcmciaEnable (ScreenPtr pScreen)
{
    KdScreenPriv(pScreen);
    pcmciaCardInfo	*pcmciac = pScreenPriv->card->driver;
    pcmciaScreenInfo	*pcmcias = (pcmciaScreenInfo *) pScreenPriv->screen->driver;
    int i,j;
    unsigned char Sequencer[6];
    unsigned char CRTC[31];
    unsigned char Graphics[9];
    unsigned char Attribute[21];
    unsigned char MiscOutReg;
    pcmciaDisplayModeRec mode = pcmciaDefaultModes[pcmcias->Mode];

    /*
     * compute correct Hsync & Vsync polarity 
     */
    if ((mode.Flags & (V_PHSYNC | V_NHSYNC))
        && (mode.Flags & (V_PVSYNC | V_NVSYNC)))
    {
        MiscOutReg = 0x23;
        if (mode.Flags & V_NHSYNC) MiscOutReg |= 0x40;
        if (mode.Flags & V_NVSYNC) MiscOutReg |= 0x80;
    }
    else
    {
        int VDisplay = mode.VDisplay;
        if (VDisplay < 400)
            MiscOutReg = 0xA3;		/* +hsync -vsync */
        else if (VDisplay < 480)
            MiscOutReg = 0x63;		/* -hsync +vsync */
        else if (VDisplay < 768)
            MiscOutReg = 0xE3;		/* -hsync -vsync */
        else
            MiscOutReg = 0x23;		/* +hsync +vsync */
    }
    
    /*
     * Time Sequencer
     */
    if (pScreenPriv->screen->fb[0].depth == 4)
        Sequencer[0] = 0x02;
    else
        Sequencer[0] = 0x00;
    Sequencer[1] = 0x01;
    Sequencer[2] = 0x0F;
    Sequencer[3] = 0x00;                             /* Font select */
    if (pScreenPriv->screen->fb[0].depth < 8)
        Sequencer[4] = 0x06;                             /* Misc */
    else
        Sequencer[4] = 0x0E;                             /* Misc */
    Sequencer[5] = 0x00;

    /*
     * CRTC Controller
     */
    CRTC[0]  = (mode.HTotal >> 3) - 5;
    CRTC[1]  = (mode.HDisplay >> 3) - 1;
    CRTC[2]  = ((min(mode.HSyncStart,mode.HDisplay)) >> 3) - 1;
    CRTC[3]  = ((((min(mode.HSyncEnd,mode.HTotal)) >> 3) - 1) & 0x1F) | 0x80;
    i = (((mode.HSkew << 2) + 0x10) & ~0x1F);
    if (i < 0x80)
	CRTC[3] |= i;
    CRTC[4]  = (mode.HSyncStart >> 3);
    CRTC[5]  = (((((min(mode.HSyncEnd,mode.HTotal)) >> 3) - 1) & 0x20) << 2)
	| (((mode.HSyncEnd >> 3)) & 0x1F);
    CRTC[6]  = (mode.VTotal - 2) & 0xFF;
    CRTC[7]  = (((mode.VTotal - 2) & 0x100) >> 8)
	| (((mode.VDisplay - 1) & 0x100) >> 7)
	| ((mode.VSyncStart & 0x100) >> 6)
	| ((((min(mode.VSyncStart,mode.VDisplay)) - 1) & 0x100) >> 5)
	| 0x10
	| (((mode.VTotal - 2) & 0x200)   >> 4)
	| (((mode.VDisplay - 1) & 0x200) >> 3)
	| ((mode.VSyncStart & 0x200) >> 2);
    CRTC[8]  = 0x00;
    CRTC[9]  = ((((min(mode.VSyncStart,mode.VDisplay))-1) & 0x200) >> 4) | 0x40;
    CRTC[10] = 0x00;
    CRTC[11] = 0x00;
    CRTC[12] = 0x00;
    CRTC[13] = 0x00;
    CRTC[14] = 0x00;
    CRTC[15] = 0x00;
    CRTC[16] = mode.VSyncStart & 0xFF;
    CRTC[17] = (mode.VSyncEnd & 0x0F) | 0x20;
    CRTC[18] = (mode.VDisplay - 1) & 0xFF;
    if (pScreenPriv->screen->fb[0].depth == 4)
        CRTC[19] = pScreenPriv->screen->fb[0].pixelStride >> 4;
    else
    if (pScreenPriv->screen->fb[0].depth == 8)
        CRTC[19] = pScreenPriv->screen->fb[0].pixelStride >> 3;
    else
    if (pScreenPriv->screen->fb[0].depth == 16 ||
        pScreenPriv->screen->fb[0].depth == 15)
        CRTC[19] = pScreenPriv->screen->fb[0].pixelStride >> 2;
    CRTC[20] = 0x00;
    CRTC[21] = ((min(mode.VSyncStart,mode.VDisplay)) - 1) & 0xFF; 
    CRTC[22] = ((min(mode.VSyncEnd,mode.VDisplay)) - 1) & 0xFF;
    if (pScreenPriv->screen->fb[0].depth < 8)
	CRTC[23] = 0xE3;
    else
	CRTC[23] = 0xC3;
    CRTC[24] = 0xFF;
    CRTC[25] = 0x00;
    CRTC[26] = 0x00;
    if (!pcmciac->HP)
    	if (mode.Flags & V_INTERLACE) CRTC[26] |= 0x01;
    if (pcmciac->HP)
    	CRTC[27] = 0x00;
    else
    	CRTC[27] = 0x22;
    CRTC[28] = 0x00;
    CRTC[29] = 0x00;
    CRTC[30] = 0x80;
    if (pcmciac->HP)
    	if (mode.Flags & V_INTERLACE) CRTC[30] |= 0x04;

{
    int nExtBits = 0;
    CARD32 ExtBits;
    CARD32 ExtBitMask = ((1 << nExtBits) - 1) << 6;

    CRTC[3]  = (CRTC[3] & ~0x1F) 
                     | ((((min(mode.HSyncEnd,mode.HTotal)) >> 3) - 1) & 0x1F);
    CRTC[5]  = (CRTC[5] & ~0x80) 
                     | (((((min(mode.HSyncEnd,mode.HTotal)) >> 3) - 1) & 0x20) << 2);
    ExtBits        = (((min(mode.HSyncEnd,mode.HTotal)) >> 3) - 1) & ExtBitMask;

    /* First the horizontal case */
    if ((((min(mode.HSyncEnd,mode.HTotal)) >> 3) == (mode.HTotal >> 3)))
    {
	int i = (CRTC[3] & 0x1F) 
	    | ((CRTC[5] & 0x80) >> 2)
	    | ExtBits;
	if ((i-- > ((((min(mode.HSyncStart,mode.HDisplay)) >> 3) - 1) 
		       & (0x3F | ExtBitMask)))
	    && ((min(mode.HSyncEnd,mode.HTotal)) == mode.HTotal))
	    i = 0;
	CRTC[3] = (CRTC[3] & ~0x1F) | (i & 0x1F);
	CRTC[5] = (CRTC[5] & ~0x80) | ((i << 2) & 0x80);
	ExtBits = i & ExtBitMask;
    }
}
{
    CARD32 ExtBits;
    CARD32 ExtBitMask = 0;
    /* If width is not known nBits should be 0. In this 
     * case BitMask is set to 0 so we can check for it. */
    CARD32 BitMask = 0;
    int VBlankStart = ((min(mode.VSyncStart,mode.VDisplay)) - 1) & 0xFF; 
    CRTC[22] = ((min(mode.VSyncEnd,mode.VTotal)) - 1) & 0xFF;
    ExtBits        = ((min(mode.VSyncEnd,mode.VTotal)) - 1) & ExtBitMask;

    if ((min(mode.VSyncEnd,mode.VTotal)) == mode.VTotal)
      /* Null top overscan */
    {
	int i = CRTC[22] | ExtBits;
	if (((BitMask && ((i & BitMask) > (VBlankStart & BitMask)))
	     || ((i > VBlankStart)  &&  		/* 8-bit case */
	    ((i & 0x7F) > (VBlankStart & 0x7F)))) &&	/* 7-bit case */
	    !(CRTC[9] & 0x9F))			/* 1 scanline/row */
	    i = 0;
	else
	    i = (i - 1);
	CRTC[22] = i & 0xFF;
	ExtBits = i & 0xFF00;
    }
}

    /*
     * Graphics Display Controller
     */
    Graphics[0] = 0x00;
    Graphics[1] = 0x00;
    Graphics[2] = 0x00;
    Graphics[3] = 0x00;
    Graphics[4] = 0x00;
    if (pScreenPriv->screen->fb[0].depth == 4)
        Graphics[5] = 0x02;
    else
        Graphics[5] = 0x40;
    Graphics[6] = 0x05;   /* only map 64k VGA memory !!!! */
    Graphics[7] = 0x0F;
    Graphics[8] = 0xFF;
  
    Attribute[0]  = 0x00; /* standard colormap translation */
    Attribute[1]  = 0x01;
    Attribute[2]  = 0x02;
    Attribute[3]  = 0x03;
    Attribute[4]  = 0x04;
    Attribute[5]  = 0x05;
    Attribute[6]  = 0x06;
    Attribute[7]  = 0x07;
    Attribute[8]  = 0x08;
    Attribute[9]  = 0x09;
    Attribute[10] = 0x0A;
    Attribute[11] = 0x0B;
    Attribute[12] = 0x0C;
    Attribute[13] = 0x0D;
    Attribute[14] = 0x0E;
    Attribute[15] = 0x0F;
    if (pScreenPriv->screen->fb[0].depth == 4)
        Attribute[16] = 0x81;
    else
        Attribute[16] = 0x41;
    if (pScreenPriv->screen->fb[0].bitsPerPixel == 16)
    	Attribute[17] = 0x00;
    else
    	Attribute[17] = 0xFF;
    Attribute[18] = 0x0F;
    Attribute[19] = 0x00;
    Attribute[20] = 0x00;

    /* Wake up the card */
    if (pcmciac->HP) {
	pcmciaWriteReg(pcmciac, 0x3c3, 0x1);
	pcmciaWriteReg(pcmciac, 0x46e8, 0x10);
    } else {
	pcmciaWriteReg(pcmciac, 0x105, 0x1);
	pcmciaWriteReg(pcmciac, 0x46e8, 0x1f);
	pcmciaWriteReg(pcmciac, 0x102, 0x1);
	pcmciaWriteReg(pcmciac, 0x46e8, 0xf);
	pcmciaWriteReg(pcmciac, 0x3c3, 0x1);
    }

    if (pcmciac->HP) {
    	/* unlock */
	pcmciaWriteIndex(pcmciac, 0x3c4, 0x11, 0x92);
	j = pcmciaReadIndex(pcmciac, 0x3c4, 0xb);
	pcmciaWriteIndex(pcmciac, 0x3c4, 0xe, 0xc2);

	/* switch on dac */
	pcmciaWriteIndex(pcmciac, 0x3d4, 0x29, 0x24);
	/* switch on the accelerator */
	pcmciaWriteIndex(pcmciac, 0x3d4, 0x36, 0x80);

	/* bump up memory clk */
	pcmciaWriteReg(pcmciac, 0x43c6, 0x65);
	pcmciaWriteReg(pcmciac, 0x43c7, 0x00);
    } else {
    	/* unlock */
	pcmciaWriteIndex(pcmciac, 0x3c4, 0x06, 0x12);
    	pcmciaWriteReg(pcmciac, 0x3c2, MiscOutReg);
    }

    /* synchronous reset */
    pcmciaWriteIndex(pcmciac, 0x3c4, 0, 0);

    pcmciaWriteReg(pcmciac, 0x3da, 0x10);

    for (i=0;i<6;i++)
	pcmciaWriteIndex(pcmciac, 0x3c4, i, Sequencer[i]);

    if (pcmciac->HP) { 
    	/* Stick chip into color mode */
	pcmciaWriteIndex(pcmciac, 0x3ce, 0x2f, 0x06);
    	/* Switch on Linear addressing */
	pcmciaWriteIndex(pcmciac, 0x3d4, 0x21, 0x2e);
    } else {
    	/* Stick chip into 8bit access mode - ugh! */
	pcmciaWriteIndex(pcmciac, 0x3c4, 0x0F, 0x20); /* 0x26 ? */
	/* reset mclk */
	pcmciaWriteIndex(pcmciac, 0x3c4, 0x1F, 0);
    }

    pcmciaWriteIndex(pcmciac, 0x3c4, 0, 0x3);

    for (i=0;i<31;i++)
	pcmciaWriteIndex(pcmciac, 0x3d4, i, CRTC[i]);

    for (i=0;i<9;i++)
	pcmciaWriteIndex(pcmciac, 0x3ce, i, Graphics[i]);

    j = pcmciaReadReg(pcmciac, 0x3da);

    for (i=0;i<21;i++) {
	pcmciaWriteReg(pcmciac, 0x3c0, i);
	pcmciaWriteReg(pcmciac, 0x3c0, Attribute[i]);
    }

    j = pcmciaReadReg(pcmciac, 0x3da);
    pcmciaWriteReg(pcmciac, 0x3c0, 0x20);

    j = pcmciaReadReg(pcmciac, 0x3c8);
    j = pcmciaReadReg(pcmciac, 0x3c6);
    j = pcmciaReadReg(pcmciac, 0x3c6);
    j = pcmciaReadReg(pcmciac, 0x3c6);
    j = pcmciaReadReg(pcmciac, 0x3c6);
    switch (pScreenPriv->screen->fb[0].depth) {
	/* This is here for completeness, when/if we ever do 4bpp */
	case 4:
		pcmciaWriteReg(pcmciac, 0x3c6, 0x0);
		if (pcmciac->HP) {
		    pcmciaWriteIndex(pcmciac, 0x3ce, 0x0f, 0x90);
		    pcmciaWriteIndex(pcmciac, 0x3d4, 0x38, 0x00);
		} else
		    pcmciaWriteIndex(pcmciac, 0x3c4, 0x07, 0x00);
		break;
	case 8:
		pcmciaWriteReg(pcmciac, 0x3c6, 0x0);
		if (pcmciac->HP) {
		    pcmciaWriteIndex(pcmciac, 0x3ce, 0x0f, 0x92);
		    pcmciaWriteIndex(pcmciac, 0x3d4, 0x38, 0x00);
		} else
		    pcmciaWriteIndex(pcmciac, 0x3c4, 0x07, 0x01);
		break;
	case 15:
		if (pcmciac->HP) {
		    pcmciaWriteReg(pcmciac, 0x3c6, 0x10);
		    pcmciaWriteIndex(pcmciac, 0x3ce, 0x0f, 0x9a);
		    pcmciaWriteIndex(pcmciac, 0x3d4, 0x38, 0x04);
		} else {
		    pcmciaWriteReg(pcmciac, 0x3c6, 0xC0);
		    pcmciaWriteIndex(pcmciac, 0x3c4, 0x07, 0x03);
		}
		break;
	case 16:
		if (pcmciac->HP) {
		    pcmciaWriteReg(pcmciac, 0x3c6, 0x30);
		    pcmciaWriteIndex(pcmciac, 0x3ce, 0x0f, 0x9a);
		    pcmciaWriteIndex(pcmciac, 0x3d4, 0x38, 0x04);
		} else {
		    pcmciaWriteReg(pcmciac, 0x3c6, 0xC1);
		    pcmciaWriteIndex(pcmciac, 0x3c4, 0x07, 0x03);
		}
		break;
    }
    j = pcmciaReadReg(pcmciac, 0x3c8);

    pcmciaWriteReg(pcmciac, 0x3c6, 0xff);

    for (i=0;i<256;i++)  {
	pcmciaWriteReg(pcmciac, 0x3c8, i);
	pcmciaWriteReg(pcmciac, 0x3c9, i);
	pcmciaWriteReg(pcmciac, 0x3c9, i);
	pcmciaWriteReg(pcmciac, 0x3c9, i);
    }

    /* Set the Clock */
    if (pcmciac->HP) {
	CARD8 a,b;
	int clock = mode.Clock;
    	if (pScreenPriv->screen->fb[0].bitsPerPixel == 16)
		clock *= 2;
	tridentSetCLK(clock, &a, &b);
	pcmciaWriteReg(pcmciac, 0x43c8, a);
	pcmciaWriteReg(pcmciac, 0x43c9, b);
    } else {
	int num, den;
	unsigned char tmp;
	int clock = mode.Clock;
    	if (pScreenPriv->screen->fb[0].bitsPerPixel == 16)
		clock *= 2;

	CirrusFindClock(clock, MAX_VCO, &num, &den);

	tmp = pcmciaReadIndex(pcmciac, 0x3c4, 0x0d);
	pcmciaWriteIndex(pcmciac, 0x3c4, 0x0d, (tmp & 0x80) | num);
	tmp = pcmciaReadIndex(pcmciac, 0x3c4, 0x1d);
	pcmciaWriteIndex(pcmciac, 0x3c4, 0x1d, (tmp & 0xc0) | den);
    }
    pcmciaWriteReg(pcmciac, 0x3c2, MiscOutReg | 0x08);

#if 0  /* for debugging */
    for (i=1;i<0x3f;i++)
	ErrorF("0x%x, ",pcmciaReadIndex(pcmciac, 0x3c4, i));

    ErrorF("\n");

    for (i=0;i<0x3f;i++)
	ErrorF("0x%x, ",pcmciaReadIndex(pcmciac, 0x3ce, i));

    ErrorF("\n");

    for (i=0;i<0x3f;i++)
	ErrorF("0x%x, ",pcmciaReadIndex(pcmciac, 0x3d4, i));
#endif

    return TRUE;
}

void
pcmciaDisable (ScreenPtr pScreen)
{
}

const CARD8	tridentDPMSModes[4] = {
    0x00,	    /* KD_DPMS_NORMAL */
    0x01,	    /* KD_DPMS_STANDBY */
    0x02,	    /* KD_DPMS_SUSPEND */
    0x03,	    /* KD_DPMS_POWERDOWN */
};

Bool
pcmciaDPMS (ScreenPtr pScreen, int mode)
{
    KdScreenPriv(pScreen);
    pcmciaCardInfo	*pcmciac = pScreenPriv->card->driver;

    if (pcmciac->HP) {
	pcmciaWriteIndex (pcmciac, 0x3ce, 0x23, tridentDPMSModes[mode]);
    	pcmciaPause ();
    } else {
	/* Voyager */
    } 

    return TRUE;
}

void
pcmciaRestore (KdCardInfo *card)
{
}

void
pcmciaScreenFini (KdScreenInfo *screen)
{
    pcmciaScreenInfo	*pcmcias = (pcmciaScreenInfo *) screen->driver;

    xfree (pcmcias);
    screen->driver = 0;
}

void
pcmciaCardFini (KdCardInfo *card)
{
    pcmciaCardInfo	*pcmciac = card->driver;

    if (pcmciac->cop_base)
	KdUnmapDevice ((void *) pcmciac->cop_base, PCMCIA_COP_SIZE(card));
}

void
pcmciaGetColors (ScreenPtr pScreen, int fb, int ndef, xColorItem *pdefs)
{
    KdScreenPriv(pScreen);
    pcmciaCardInfo	*pcmciac = pScreenPriv->card->driver;

    while (ndef--)
    {
        pcmciaWriteReg (pcmciac, 0x3C7, pdefs->pixel);
	pdefs->red = pcmciaReadReg (pcmciac, 0x3C9) << 10;
	pdefs->green = pcmciaReadReg (pcmciac, 0x3C9) << 10;
	pdefs->blue = pcmciaReadReg (pcmciac, 0x3C9) << 10;
	pdefs++;
    }
}

void
pcmciaPutColors (ScreenPtr pScreen, int fb, int ndef, xColorItem *pdefs)
{
    KdScreenPriv(pScreen);
    pcmciaCardInfo	*pcmciac = pScreenPriv->card->driver;

    while (ndef--)
    {
        pcmciaWriteReg (pcmciac, 0x3C8, pdefs->pixel);
	pcmciaWriteReg (pcmciac, 0x3C9, pdefs->red >> 10);
	pcmciaWriteReg (pcmciac, 0x3C9, pdefs->green >> 10);
	pcmciaWriteReg (pcmciac, 0x3C9, pdefs->blue >> 10);
	pdefs++;
    }
}


KdCardFuncs	pcmciaFuncs = {
    pcmciaCardInit,	    /* cardinit */
    pcmciaScreenInit,	    /* scrinit */
    pcmciaInitScreen,	    /* initScreen */
    pcmciaPreserve,	    /* preserve */
    pcmciaEnable,	    /* enable */
    pcmciaDPMS,	    /* dpms */
    pcmciaDisable,	    /* disable */
    pcmciaRestore,	    /* restore */
    pcmciaScreenFini,	    /* scrfini */
    pcmciaCardFini,	    /* cardfini */
    
    pcmciaCursorInit,	    /* initCursor */
    pcmciaCursorEnable,    /* enableCursor */
    pcmciaCursorDisable,   /* disableCursor */
    pcmciaCursorFini,	    /* finiCursor */
    pcmciaRecolorCursor,   /* recolorCursor */
    
#if 0 /* not yet */
    pcmciaDrawInit,        /* initAccel */
    pcmciaDrawEnable,      /* enableAccel */
    pcmciaDrawSync,	    /* syncAccel */
    pcmciaDrawDisable,     /* disableAccel */
    pcmciaDrawFini,        /* finiAccel */
#else 
    0,
    0,
    0,
    0,
    0,
#endif
    
    pcmciaGetColors,  	    /* getColors */
    pcmciaPutColors,	    /* putColors */
};
