/*
 *       ______             ____  ______     _____  ______ 
 *      |  ____|           |  _ \|  ____|   / / _ \|  ____|
 *      | |__ _ __ ___  ___| |_) | |__     / / |_| | |__ 
 *      |  __| '__/ _ \/ _ \  _ <|  __|   / /|  _  |  __|
 *      | |  | | |  __/  __/ |_) | |____ / / | | | | |
 *      |_|  |_|  \___|\___|____/|______/_/  |_| |_|_|
 *
 *
 *      Accelerated Matrox driver, by Shawn Hargreaves.
 *
 *      See freebe.txt for copyright information.
 */


// #define NO_HWPTR


#include <pc.h>

#include "vbeaf.h"



/* driver function prototypes */
void SetBank32();
void SetBank32End();
int  ExtStub();
long GetVideoModeInfo(AF_DRIVER *af, short mode, AF_MODE_INFO *modeInfo);
long SetVideoMode(AF_DRIVER *af, short mode, long virtualX, long virtualY, long *bytesPerLine, int numBuffers, AF_CRTCInfo *crtc);
void RestoreTextMode(AF_DRIVER *af);
long GetClosestPixelClock(AF_DRIVER *af, short mode, unsigned long pixelClock);
void SaveRestoreState(AF_DRIVER *af, int subfunc, void *saveBuf);
void SetDisplayStart(AF_DRIVER *af, long x, long y, long waitVRT);
void SetActiveBuffer(AF_DRIVER *af, long index);
void SetVisibleBuffer(AF_DRIVER *af, long index, long waitVRT);
int  GetDisplayStartStatus(AF_DRIVER *af);
void SetPaletteData(AF_DRIVER *af, AF_PALETTE *pal, long num, long index, long waitVRT);
void SetBank(AF_DRIVER *af, long bank);
void SetCursor(AF_DRIVER *af, AF_CURSOR *cursor);
void SetCursorPos(AF_DRIVER *af, long x, long y);
void SetCursorColor(AF_DRIVER *af, unsigned char red, unsigned char green, unsigned char blue);
void ShowCursor(AF_DRIVER *af, long visible);
void WaitTillIdle(AF_DRIVER *af);
void SetMix(AF_DRIVER *af, long foreMix, long backMix);
void Set8x8MonoPattern(AF_DRIVER *af, unsigned char *pattern);
void Set8x8ColorPattern(AF_DRIVER *af, int index, unsigned long *pattern);
void Use8x8ColorPattern(AF_DRIVER *af, int index);
void DrawScan(AF_DRIVER *af, long color, long y, long x1, long x2);
void DrawPattScan(AF_DRIVER *af, long foreColor, long backColor, long y, long x1, long x2);
void DrawColorPattScan(AF_DRIVER *af, long y, long x1, long x2);
void DrawRect(AF_DRIVER *af, unsigned long color, long left, long top, long width, long height);
void DrawPattRect(AF_DRIVER *af, unsigned long foreColor, unsigned long backColor, long left, long top, long width, long height);
void DrawColorPattRect(AF_DRIVER *af, long left, long top, long width, long height);
void DrawLine(AF_DRIVER *af, unsigned long color, fixed x1, fixed y1, fixed x2, fixed y2);
void DrawTrap(AF_DRIVER *af, unsigned long color, AF_TRAP *trap);
void PutMonoImage(AF_DRIVER *af, long foreColor, long backColor, long dstX, long dstY, long byteWidth, long srcX, long srcY, long width, long height, unsigned char *image);
void BitBlt(AF_DRIVER *af, long left, long top, long width, long height, long dstLeft, long dstTop, long op);
void BitBltSys(AF_DRIVER *af, void *srcAddr, long srcPitch, long srcLeft, long srcTop, long width, long height, long dstLeft, long dstTop, long op);
void SrcTransBlt(AF_DRIVER *af, long left, long top, long width, long height, long dstLeft, long dstTop, long op, unsigned long transparent);
void SrcTransBltSys(AF_DRIVER *af, void *srcAddr, long srcPitch, long srcLeft, long srcTop, long width, long height, long dstLeft, long dstTop, long op, unsigned long transparent);



/* FreeBE/AF extension allowing farptr access to video memory */
FAF_HWPTR_DATA hwptr;



/* mode information from the underlying VESA driver */
typedef struct VIDEO_MODE
{
   int vesa_num;
   int linear;
   int w;
   int h;
   int bpp;
   int bytes_per_scanline;
   int redsize;
   int redpos;
   int greensize;
   int greenpos;
   int bluesize;
   int bluepos;
   int rsvdsize;
   int rsvdpos;
} VIDEO_MODE;


#define MAX_MODES    64

VIDEO_MODE mode_list[MAX_MODES];

short available_modes[MAX_MODES+1] = { -1 };

int num_modes = 0;


unsigned short ports_table[] = { 0xFFFF };


/* list of features, so the install program can disable some of them */
FAF_CONFIG_DATA config_data[] = 
{
   {
      FAF_CFG_FEATURES,

      (fafLinear | fafBanked | fafHWCursor |
       fafDrawScan | fafDrawPattScan | fafDrawColorPattScan |
       fafDrawRect | fafDrawPattRect | fafDrawColorPattRect |
       fafDrawLine | fafDrawTrap | fafPutMonoImage |
       fafBitBlt | fafBitBltSys |
       fafSrcTransBlt | fafSrcTransBltSys)
   },

   { 0, 0 }
};

#define CFG_FEATURES    config_data[0].value


/* cache eight 8x8 color patterns at the end of vram */
int pat_off_bytes[8];
int pat_off_pixels[8];

#define PATTERN_VRAM(bpp)  ((bpp == 8) ? 512 : ((bpp == 32) ? 3072 : 1536))


/* hardware cursors are only supported on the Mystique */
#define HAS_HW_CURSOR      (matrox_id == MATROX_MYST_ID)

#define HW_CURSOR_VRAM     ((HAS_HW_CURSOR) ? 1024 : 0)

int cur_color;

int cur_doublemode;

int cur_hot_x;
int cur_hot_y;


/* prevent applications from using our reserved video memory */
#define RESERVED_VRAM(bpp)    (PATTERN_VRAM(bpp) + HW_CURSOR_VRAM)


/* video mode and driver state information */
int af_bpp;
int af_width_bytes;
int af_width_pixels;
int af_height;
int af_visible_page;
int af_active_page;
int af_scroll_x;
int af_scroll_y;
int af_y;
int af_fore_mix;
int af_back_mix;
int af_color_pattern;

AF_PALETTE af_palette[256];


/* cached drawing engine state */
#define OP_NONE                  0
#define OP_DRAWRECT              1
#define OP_DRAWPATTRECT          2
#define OP_DRAWCOLORPATTRECT     3
#define OP_DRAWLINE              4
#define OP_DRAWTRAP              5
#define OP_DRAWSLOWTRAP          6
#define OP_PUTMONOIMAGE_LINEAR   7
#define OP_PUTMONOIMAGE_SLOW     8
#define OP_BITBLT_FORWARD        9
#define OP_BITBLT_BACKWARD       10
#define OP_BITBLTSYS             11
#define OP_SRCTRANSBLT           12
#define OP_SRCTRANSBLTSYS        13


int af_operation;



/* PCI device identifiers */
#define MATROX_VENDOR_ID         0x102B

#define MATROX_MILL_ID           0x0519
#define MATROX_MYST_ID           0x051A
#define MATROX_MII_PCI_ID        0x051B
#define MATROX_MII_AGP_ID        0x051F


int matrox_id_list[] = {
   MATROX_MILL_ID,
   MATROX_MYST_ID,
   MATROX_MII_PCI_ID,
   MATROX_MII_AGP_ID,
   0
};


int matrox_id;
int matrox_rev;



/* Matrox hardware registers: */

#define M_DWGCTL              0x1C00
#define M_MACCESS             0x1C04

#define M_PAT0                0x1C10
#define M_PAT1                0x1C14
#define M_PLNWT               0x1C1C

#define M_BCOL                0x1C20
#define M_FCOL                0x1C24

#define M_SRC0                0x1C30
#define M_SRC1                0x1C34
#define M_SRC2                0x1C38
#define M_SRC3                0x1C3C

#define M_XYSTRT              0x1C40
#define M_XYEND               0x1C44

#define M_SHIFT               0x1C50

#define M_SGN                 0x1C58

#define M_SDXL                0x02
#define M_SDXR                0x20

#define M_LEN                 0x1C5C

#define M_AR0                 0x1C60
#define M_AR1                 0x1C64
#define M_AR2                 0x1C68
#define M_AR3                 0x1C6C
#define M_AR4                 0x1C70
#define M_AR5                 0x1C74
#define M_AR6                 0x1C78

#define M_CXBNDRY             0x1C80
#define M_FXBNDRY             0x1C84
#define M_YDSTLEN             0x1C88
#define M_PITCH               0x1C8C

#define M_YDST                0x1C90
#define M_YDSTORG             0x1C94
#define M_YTOP                0x1C98
#define M_YBOT                0x1C9C

#define M_CXLEFT              0x1CA0
#define M_CXRIGHT             0x1CA4
#define M_FXLEFT              0x1CA8
#define M_FXRIGHT             0x1CAC

#define M_XDST                0x1CB0

#define M_EXEC                0x0100

#define M_FIFOSTATUS          0x1E10
#define M_STATUS              0x1E14
#define M_ICLEAR              0x1E18
#define M_IEN                 0x1E1C

#define M_VCOUNT              0x1E20

#define M_RESET               0x1E40

#define M_OPMODE              0x1E54

#define M_DMA_GENERAL         0x00
#define M_DMA_BLIT            0x04
#define M_DMA_VECTOR          0x40

#define M_DWG_LINE_OPEN       0x00
#define M_DWG_AUTOLINE_OPEN   0x01
#define M_DWG_LINE_CLOSE      0x02
#define M_DWG_AUTOLINE_CLOSE  0x03

#define M_DWG_TRAP            0x04
#define M_DWG_TEXTURE_TRAP    0x05

#define M_DWG_BITBLT          0x08
#define M_DWG_FBITBLT         0x0C
#define M_DWG_ILOAD           0x09
#define M_DWG_ILOAD_SCALE     0x0D
#define M_DWG_ILOAD_FILTER    0x0F
#define M_DWG_IDUMP           0x0A

#define M_DWG_LINEAR          0x0080
#define M_DWG_SOLID           0x0800
#define M_DWG_ARZERO          0x1000
#define M_DWG_SGNZERO         0x2000
#define M_DWG_SHIFTZERO       0x4000

#define M_DWG_BPLAN           0x02000000
#define M_DWG_BFCOL           0x04000000
#define M_DWG_BMONOWF         0x08000000

#define M_DWG_PATTERN         0x20000000
#define M_DWG_TRANSC          0x40000000

#define M_CRTC_INDEX          0x1FD4
#define M_CRTC_DATA           0x1FD5

#define M_CRTC_EXT_INDEX      0x1FDE
#define M_CRTC_EXT_DATA       0x1FDF



/* Mystique-only registers: */

#define M_PALWTADD            0x3C00
#define M_X_DATAREG           0x3C0A

#define M_CURPOS              0x3C0C

#define M_XCURADDL            0x04
#define M_XCURADDH            0x05 

#define M_XCURCTRL            0x06

#define M_XCURCOL0RED         0x08
#define M_XCURCOL0GREEN       0x09
#define M_XCURCOL0BLUE        0x0A
#define M_XCURCOL1RED         0x0C
#define M_XCURCOL1GREEN       0x0D
#define M_XCURCOL1BLUE        0x0E
#define M_XCURCOL2RED         0x10
#define M_XCURCOL2GREEN       0x11
#define M_XCURCOL2BLUE        0x12

#define M_CURCTRL_OFF         0
#define M_CURCTRL_3COLOR      1
#define M_CURCTRL_XGA         2
#define M_CURCTRL_XWINDOWS    3



/* helpers for accessing the Matrox registers */
#define mga_select()          hwptr_select(hwptr.IOMemMaps[0])
#define mga_inb(addr)         hwptr_nspeekb(hwptr.IOMemMaps[0], addr)
#define mga_inw(addr)         hwptr_nspeekw(hwptr.IOMemMaps[0], addr)
#define mga_inl(addr)         hwptr_nspeekl(hwptr.IOMemMaps[0], addr)
#define mga_outb(addr, val)   hwptr_nspokeb(hwptr.IOMemMaps[0], addr, val)
#define mga_outw(addr, val)   hwptr_nspokew(hwptr.IOMemMaps[0], addr, val)
#define mga_outl(addr, val)   hwptr_nspokel(hwptr.IOMemMaps[0], addr, val)



/* mga_xdata:
 *  Writes to one of the Mystique cursor control registers.
 */
#define mga_xdata(reg, val)                        \
{                                                  \
   mga_outb(M_PALWTADD, reg);                      \
   mga_outb(M_X_DATAREG, val);                     \
}



/* mga_fifo:
 *  Waits until there are at least <n> free slots in the FIFO buffer.
 */
#define mga_fifo(n)                                \
{                                                  \
   do {                                            \
   } while (mga_inb(M_FIFOSTATUS) < (n));          \
}



/* bswap:
 *  Toggles the endianess of a 32 bit integer.
 */
unsigned long bswap(unsigned long n)
{
   unsigned long a = n&0xFF;
   unsigned long b = (n>>8)&0xFF;
   unsigned long c = (n>>16)&0xFF;
   unsigned long d = (n>>24)&0xFF;

   return (a<<24) | (b<<16) | (c<<8) | d;
}



/* mga_wait_retrace:
 *  Waits for the next vertical sync period.
 */
void mga_wait_retrace(AF_DRIVER *af)
{
   int t1 = 0;
   int t2 = 0;

   do {
      t1 = t2;
      t2 = mga_inl(M_VCOUNT);
   } while (t2 >= t1);
}



/* mode_callback:
 *  Callback for the get_vesa_info() function to add a new resolution to
 *  the table of available modes.
 */
void mode_callback(int vesa_num, int linear, int w, int h, int bpp, int bytes_per_scanline, int redsize, int redpos, int greensize, int greenpos, int bluesize, int bluepos, int rsvdsize, int rsvdpos)
{
   if (num_modes >= MAX_MODES)
      return;

   if ((bpp != 8) && (bpp != 15) && (bpp != 16) && (bpp != 32))
      return;

   mode_list[num_modes].vesa_num = vesa_num;
   mode_list[num_modes].linear = linear;
   mode_list[num_modes].w = w;
   mode_list[num_modes].h = h;
   mode_list[num_modes].bpp = bpp;
   mode_list[num_modes].bytes_per_scanline = bytes_per_scanline;
   mode_list[num_modes].redsize = redsize;
   mode_list[num_modes].redpos = redpos;
   mode_list[num_modes].greensize = greensize;
   mode_list[num_modes].greenpos = greenpos;
   mode_list[num_modes].bluesize = bluesize;
   mode_list[num_modes].bluepos = bluepos;
   mode_list[num_modes].rsvdsize = rsvdsize;
   mode_list[num_modes].rsvdpos = rsvdpos;

   available_modes[num_modes] = num_modes+1;
   available_modes[num_modes+1] = -1;

   num_modes++;
}



/* SetupDriver:
 *  The first thing ever to be called after our code has been relocated.
 *  This is in charge of filling in the driver header with all the required
 *  information and function pointers. We do not yet have access to the
 *  video memory, so we can't talk directly to the card.
 */
int SetupDriver(AF_DRIVER *af)
{
   unsigned long pci_base[2];
   int vram_size;
   int bus_id;
   char *name;
   int i;

   /* find PCI device */
   matrox_id = 0;
   bus_id = 0;

   for (i=0; matrox_id_list[i]; i++) {
      if (FindPCIDevice(matrox_id_list[i], MATROX_VENDOR_ID, 0, &bus_id)) {
	 matrox_id = matrox_id_list[i];
	 break;
      }
   }

   if (!matrox_id)
      return -1;

   /* read hardware configuration data */
   matrox_rev = PCIReadLong(bus_id, 8) & 0xFF;

   for (i=0; i<2; i++)
      pci_base[i] = PCIReadLong(bus_id, 16+i*4);

   /* fetch mode list from the VESA driver */
   if (get_vesa_info(&vram_size, NULL, mode_callback) != 0)
      return -1;

   af->AvailableModes = available_modes;

   af->TotalMemory = vram_size/1024;

   af->Attributes = (afHaveMultiBuffer | 
		     afHaveVirtualScroll | 
		     afHaveBankedBuffer | 
		     afHaveLinearBuffer |
		     afHaveAccel2D);

   if (!(CFG_FEATURES & fafLinear))
      af->Attributes &= ~afHaveLinearBuffer;

   if (!(CFG_FEATURES & fafBanked))
      af->Attributes &= ~afHaveBankedBuffer;

   if (HAS_HW_CURSOR)
      af->Attributes |= afHaveHWCursor;

   af->BankSize = 64;
   af->BankedBasePtr = 0xA0000;

   af->IOPortsTable = ports_table;

   /* work out the linear framebuffer and MMIO addresses */
   if (((matrox_id == MATROX_MYST_ID) && (matrox_rev >= 3)) ||
       (matrox_id == MATROX_MII_PCI_ID) ||
       (matrox_id == MATROX_MII_AGP_ID)) {
      if (pci_base[0])
	 af->LinearBasePtr = pci_base[0] & 0xFF800000;

      if (pci_base[1])
	 af->IOMemoryBase[0] = pci_base[1] & 0xFFFFC000;
   }
   else {
      if (pci_base[0])
	 af->IOMemoryBase[0] = pci_base[0] & 0xFFFFC000;

      if (pci_base[1])
	 af->LinearBasePtr = pci_base[1] & 0xFF800000;
   }

   if ((!af->LinearBasePtr) || (!af->IOMemoryBase[0]))
      return -1;

   af->LinearSize = vram_size/1024;
   af->IOMemoryLen[0] = 0x4000;

   /* set up the driver name */
   switch (matrox_id) {

      case MATROX_MILL_ID:
	 name = "Millenium";
	 break;

      case MATROX_MYST_ID:
	 name = "Mystique";
	 break;

      case MATROX_MII_PCI_ID:
	 name = "Millenium II PCI";
	 break;

      case MATROX_MII_AGP_ID:
	 name = "Millenium II AGP";
	 break;

      default:
	 name = "Unknown Matrox";
	 break;
   } 

   i = 0;
   while (af->OemVendorName[i])
      i++;

   af->OemVendorName[i++] = ',';
   af->OemVendorName[i++] = ' ';

   while (*name)
      af->OemVendorName[i++] = *(name++);

   af->OemVendorName[i] = 0;

   /* set up driver functions */
   af->SetBank32 = SetBank32;
   af->SetBank32Len = (long)SetBank32End - (long)SetBank32;

   af->SupplementalExt = ExtStub;
   af->GetVideoModeInfo = GetVideoModeInfo;
   af->SetVideoMode = SetVideoMode;
   af->RestoreTextMode = RestoreTextMode;
   af->GetClosestPixelClock = GetClosestPixelClock;
   af->SaveRestoreState = SaveRestoreState;
   af->SetDisplayStart = SetDisplayStart;
   af->SetActiveBuffer = SetActiveBuffer;
   af->SetVisibleBuffer = SetVisibleBuffer;
   af->GetDisplayStartStatus = GetDisplayStartStatus;
   af->SetPaletteData = SetPaletteData;
   af->SetBank = SetBank;
   af->WaitTillIdle = WaitTillIdle;
   af->SetMix = SetMix;
   af->Set8x8MonoPattern = Set8x8MonoPattern;
   af->DrawScan = DrawScan;
   af->DrawPattScan = DrawPattScan;
   af->DrawRect = DrawRect;
   af->DrawPattRect = DrawPattRect;
   af->DrawLine = DrawLine;
   af->DrawTrap = DrawTrap;
   af->PutMonoImage = PutMonoImage;
   af->BitBlt = BitBlt;
   af->BitBltSys = BitBltSys;

   fixup_feature_list(af, CFG_FEATURES);

   return 0;
}



/* InitDriver:
 *  The second thing to be called during the init process, after the 
 *  application has mapped all the memory and I/O resources we need.
 *  This is in charge of finding the card, returning 0 on success or
 *  -1 to abort.
 */
int InitDriver(AF_DRIVER *af)
{
   /* initialise farptr video memory access */
   hwptr_init(hwptr.IOMemMaps[0], af->IOMemMaps[0]);
   hwptr_init(hwptr.IOMemMaps[1], af->IOMemMaps[1]);
   hwptr_init(hwptr.IOMemMaps[2], af->IOMemMaps[2]);
   hwptr_init(hwptr.IOMemMaps[3], af->IOMemMaps[3]);
   hwptr_init(hwptr.BankedMem, af->BankedMem);
   hwptr_init(hwptr.LinearMem, af->LinearMem);

   return 0;
}



/* FreeBEX:
 *  Returns an interface structure for the requested FreeBE/AF extension.
 */
void *FreeBEX(AF_DRIVER *af, unsigned long id)
{
   switch (id) {

   #ifndef NO_HWPTR

      case FAFEXT_HWPTR:
	 /* allow farptr access to video memory */
	 return &hwptr;

   #endif 

      case FAFEXT_CONFIG:
	 /* allow the install program to configure our driver */
	 return config_data;

      default:
	 return NULL;
   }
}



/* ExtStub:
 *  Vendor-specific extension hook: we don't provide any.
 */
int ExtStub()
{
   return 0;
}



/* GetVideoModeInfo:
 *  Retrieves information about this video mode, returning zero on success
 *  or -1 if the mode is invalid.
 */
long GetVideoModeInfo(AF_DRIVER *af, short mode, AF_MODE_INFO *modeInfo)
{
   VIDEO_MODE *info;
   int i;

   if ((mode <= 0) || (mode > num_modes))
      return -1;

   info = &mode_list[mode-1];

   /* clear the structure to zero */
   for (i=0; i<(int)sizeof(AF_MODE_INFO); i++)
      ((char *)modeInfo)[i] = 0;

   /* copy data across from our stored list of mode attributes */
   modeInfo->Attributes = (afHaveMultiBuffer | 
			   afHaveVirtualScroll | 
			   afHaveBankedBuffer | 
			   afHaveAccel2D);

   if (info->linear) {
      modeInfo->Attributes |= afHaveLinearBuffer;

      if (HAS_HW_CURSOR)
	 modeInfo->Attributes |= afHaveHWCursor;
   }

   if (!(CFG_FEATURES & fafLinear))
      modeInfo->Attributes &= ~afHaveLinearBuffer;

   if (!(CFG_FEATURES & fafBanked))
      modeInfo->Attributes &= ~afHaveBankedBuffer;

   modeInfo->XResolution = info->w;
   modeInfo->YResolution = info->h;
   modeInfo->BitsPerPixel = info->bpp;

   /* available pages of video memory */
   modeInfo->MaxBuffers = (af->TotalMemory*1024 - RESERVED_VRAM(info->bpp)) / 
			  (info->bytes_per_scanline * info->h);

   /* maximum virtual scanline length in both bytes and pixels */
   modeInfo->MaxBytesPerScanLine = 2048*BYTES_PER_PIXEL(info->bpp);
   modeInfo->MaxScanLineWidth = 2048;

   /* for banked video modes, fill in these variables: */
   modeInfo->BytesPerScanLine = info->bytes_per_scanline;
   modeInfo->BnkMaxBuffers = modeInfo->MaxBuffers;
   modeInfo->RedMaskSize = info->redsize;
   modeInfo->RedFieldPosition = info->redpos;
   modeInfo->GreenMaskSize = info->greensize;
   modeInfo->GreenFieldPosition = info->greenpos;
   modeInfo->BlueMaskSize = info->bluesize;
   modeInfo->BlueFieldPosition = info->bluepos;
   modeInfo->RsvdMaskSize = info->rsvdsize;
   modeInfo->RsvdFieldPosition = info->rsvdpos;

   /* for linear video modes, fill in these variables: */
   modeInfo->LinBytesPerScanLine = info->bytes_per_scanline;
   modeInfo->LinMaxBuffers = modeInfo->MaxBuffers;
   modeInfo->LinRedMaskSize = info->redsize;
   modeInfo->LinRedFieldPosition = info->redpos;
   modeInfo->LinGreenMaskSize = info->greensize;
   modeInfo->LinGreenFieldPosition = info->greenpos;
   modeInfo->LinBlueMaskSize = info->bluesize;
   modeInfo->LinBlueFieldPosition = info->bluepos;
   modeInfo->LinRsvdMaskSize = info->rsvdsize;
   modeInfo->LinRsvdFieldPosition = info->rsvdpos;

   /* I'm not sure exactly what these should be: Allegro doesn't use them */
   modeInfo->MaxPixelClock = 135000000;
   modeInfo->VideoCapabilities = 0;
   modeInfo->VideoMinXScale = 0;
   modeInfo->VideoMinYScale = 0;
   modeInfo->VideoMaxXScale = 0;
   modeInfo->VideoMaxYScale = 0;

   return 0;
}



/* SetVideoMode:
 *  Sets the specified video mode, returning zero on success.
 *
 *  Possible flag bits that may be or'ed with the mode number:
 *
 *    0x8000 = don't clear video memory
 *    0x4000 = enable linear framebuffer
 *    0x2000 = enable multi buffering
 *    0x1000 = enable virtual scrolling
 *    0x0800 = use refresh rate control
 *    0x0400 = use hardware stereo
 */
long SetVideoMode(AF_DRIVER *af, short mode, long virtualX, long virtualY, long *bytesPerLine, int numBuffers, AF_CRTCInfo *crtc)
{
   static int millenium_strides[] = 
   {
      640, 768, 800, 960, 1024, 1152, 1280, 1600, 1920, 2048, 0
   };

   /* for some reason, 800 pixel wide screens seem to upset the drawing
    * engine on my Mystique. I fixed this by leaving them out of this table,
    * so the virtual width will be rounded up to 832: this seems to correct
    * the problem, but I've no idea why! According to the specs, 800 should
    * be a valid pitch.
    */
   static int mystique_strides[] =
   {
      512, 640, 768, 832, 960, 1024, 1152, 1280, 1600, 1664, 1920, 2048, 0
   };

   int linear = ((mode & 0x4000) != 0);
   int noclear = ((mode & 0x8000) != 0);
   long available_vram;
   long used_vram;
   int *strides;
   VIDEO_MODE *info;
   RM_REGS r;
   int i;

   /* reject anything with hardware stereo */
   if (mode & 0x400)
      return -1;

   /* reject linear/banked modes if the install program has disabled them */
   if (linear) {
      if (!(CFG_FEATURES & fafLinear))
	 return -1;
   }
   else {
      if (!(CFG_FEATURES & fafBanked))
	 return -1;
   }

   /* mask off the other flag bits */
   mode &= 0x3FF;

   if ((mode <= 0) || (mode > num_modes))
      return -1;

   info = &mode_list[mode-1];

   /* reject the linear flag if the mode doesn't support it */
   if ((linear) && (!info->linear))
      return -1;

   /* call VESA to set the mode */
   r.x.ax = 0x4F02;
   r.x.bx = info->vesa_num;
   if (linear)
      r.x.bx |= 0x4000;
   if (noclear)
      r.x.bx |= 0x8000;
   rm_int(0x10, &r);
   if (r.h.ah)
      return -1;

   /* round the virtual width up to a suitable value */
   *bytesPerLine = MAX(info->bytes_per_scanline, virtualX*BYTES_PER_PIXEL(info->bpp));

   if (matrox_id == MATROX_MYST_ID)
      strides = mystique_strides;
   else
      strides = millenium_strides;

   for (i=0; strides[i]; i++) {
      if (*bytesPerLine <= strides[i]*BYTES_PER_PIXEL(info->bpp)) {
	 *bytesPerLine = strides[i]*BYTES_PER_PIXEL(info->bpp);
	 break;
      }
   }

   /* wider than this would overflow the 7k window used by BitBltSys() */
   if (*bytesPerLine > 4096)
      return -1;

   /* adjust the virtual screen width */
   if (matrox_id != MATROX_MILL_ID) {
      write_vga_register(0x3D4, 0x13, (*bytesPerLine/16)&0xFF);
      alter_vga_register(0x3DE, 0, 0x30, ((*bytesPerLine/16)>>4)&0x30);
   }
   else {
      write_vga_register(0x3D4, 0x13, (*bytesPerLine/8)&0xFF);
      alter_vga_register(0x3DE, 0, 0x30, ((*bytesPerLine/8)>>4)&0x30);
   }

   /* store info about the current mode */
   af_bpp = info->bpp;
   af_width_bytes = *bytesPerLine;
   af_width_pixels = *bytesPerLine/BYTES_PER_PIXEL(af_bpp);
   af_height = MAX(info->h, virtualY);
   af_visible_page = 0;
   af_active_page = 0;
   af_scroll_x = 0;
   af_scroll_y = 0;
   af_y = 0;

   /* return framebuffer dimensions to the application */
   af->BufferEndX = af_width_pixels-1;
   af->BufferEndY = af_height-1;
   af->OriginOffset = 0;

   used_vram = af_width_bytes * af_height * numBuffers;
   available_vram = af->TotalMemory*1024 - RESERVED_VRAM(af_bpp);

   if (used_vram > available_vram)
      return -1;

   if (available_vram-used_vram >= af_width_bytes) {
      af->OffscreenOffset = used_vram;
      af->OffscreenStartY = af_height*numBuffers;
      af->OffscreenEndY = available_vram/af_width_bytes-1;
   }
   else {
      af->OffscreenOffset = 0;
      af->OffscreenStartY = 0;
      af->OffscreenEndY = 0;
   }

   af_fore_mix = AF_REPLACE_MIX;
   af_back_mix = AF_FORE_MIX;

   af_color_pattern = 0;

   af_operation = OP_NONE;

   /* set up the accelerator engine */
   mga_select();

   mga_fifo(8);
   mga_outl(M_PITCH, af_width_pixels);
   mga_outl(M_YDSTORG, 0);
   mga_outl(M_PLNWT, 0xFFFFFFFF);
   mga_outl(M_OPMODE, M_DMA_BLIT);
   mga_outl(M_CXBNDRY, 0xFFFF0000);
   mga_outl(M_YTOP, 0x00000000);
   mga_outl(M_YBOT, 0x007FFFFF);

   switch (af_bpp) {

      case 8:
	 mga_outl(M_MACCESS, 0);
	 break;

      case 15:
	 mga_outl(M_MACCESS, 0xC0000001);
	 break;

      case 16:
	 mga_outl(M_MACCESS, 0x40000001);
	 break;

      case 32:
	 mga_outl(M_MACCESS, 2);
	 break;
   }

   /* only enable colored patterns and hardware cursors in linear modes */
   if (linear) {
      af->Set8x8ColorPattern = Set8x8ColorPattern;
      af->Use8x8ColorPattern = Use8x8ColorPattern;
      af->DrawColorPattScan = DrawColorPattScan;
      af->DrawColorPattRect = DrawColorPattRect;

      if (HAS_HW_CURSOR) {
	 af->SetCursor = SetCursor;
	 af->SetCursorPos = SetCursorPos;
	 af->SetCursorColor = SetCursorColor;
	 af->ShowCursor = ShowCursor;

	 cur_doublemode = (info->h < 400);
	 cur_color = -1;
      }
      else {
	 af->SetCursor = NULL;
	 af->SetCursorPos = NULL;
	 af->SetCursorColor = NULL;
	 af->ShowCursor = NULL;
      }
   }
   else {
      af->Set8x8ColorPattern = NULL;
      af->Use8x8ColorPattern = NULL;
      af->DrawColorPattScan = NULL;
      af->DrawColorPattRect = NULL;

      af->SetCursor = NULL;
      af->SetCursorPos = NULL;
      af->SetCursorColor = NULL;
      af->ShowCursor = NULL;
   }

   /* masked blitting is only possible on the Mystique */
   if (matrox_id != MATROX_MILL_ID) {
      af->SrcTransBlt = SrcTransBlt;

      /* in truecolor modes, this is faster without using the hardware! */
      if (af_bpp == 8)
	 af->SrcTransBltSys = SrcTransBltSys;
      else
	 af->SrcTransBltSys = NULL;
   }

   /* deal with odd alignment/interleaving rules for color pattern data */
   i = (af->TotalMemory*1024 - HW_CURSOR_VRAM) / BYTES_PER_PIXEL(af_bpp);

   if (af_bpp == 8) { 
      pat_off_pixels[0] = i-512;
      pat_off_pixels[1] = i-512+8;
      pat_off_pixels[2] = i-512+16;
      pat_off_pixels[3] = i-512+24;
      pat_off_pixels[4] = i-256;
      pat_off_pixels[5] = i-256+8;
      pat_off_pixels[6] = i-256+16;
      pat_off_pixels[7] = i-256+24;
   }
   else {
      pat_off_pixels[0] = i-768;
      pat_off_pixels[1] = i-768+8;
      pat_off_pixels[2] = i-768+16;
      pat_off_pixels[3] = i-512;
      pat_off_pixels[4] = i-512+8;
      pat_off_pixels[5] = i-512+16;
      pat_off_pixels[6] = i-256;
      pat_off_pixels[7] = i-256+8;
   }

   for (i=0; i<8; i++)
      pat_off_bytes[i] = pat_off_pixels[i] * BYTES_PER_PIXEL(af_bpp);

   fixup_feature_list(af, CFG_FEATURES);

   return 0;
}



/* RestoreTextMode:
 *  Returns to text mode, shutting down the accelerator hardware.
 */
void RestoreTextMode(AF_DRIVER *af)
{
   RM_REGS r;

   r.x.ax = 3;
   rm_int(0x10, &r);
}



/* GetClosestPixelClock:
 *  I don't have a clue what this should return: it is used for the
 *  refresh rate control.
 */
long GetClosestPixelClock(AF_DRIVER *af, short mode, unsigned long pixelClock)
{
   /* ??? */
   return 135000000;
}



/* SaveRestoreState:
 *  Stores the current driver status: not presently implemented.
 */
void SaveRestoreState(AF_DRIVER *af, int subfunc, void *saveBuf)
{
   /* not implemented (not used by Allegro) */
}



/* SetDisplayStart:
 *  Hardware scrolling function.
 */
void SetDisplayStart(AF_DRIVER *af, long x, long y, long waitVRT)
{
   long addr;

   mga_select();

   if (waitVRT >= 0) {

      if ((waitVRT) && (matrox_id == MATROX_MYST_ID))
	 mga_wait_retrace(af);

      addr = (((y + af_visible_page*af_height) * af_width_bytes) + 
	      (x*BYTES_PER_PIXEL(af_bpp)));

      if (matrox_id != MATROX_MILL_ID)
	 addr /= 8;
      else
	 addr /= 4;

      write_vga_register(0x3D4, 0x0D, (addr)&0xFF);
      write_vga_register(0x3D4, 0x0C, (addr>>8)&0xFF);
      alter_vga_register(0x3DE, 0, 0x0F, (addr>>16)&0x0F);

      if ((waitVRT) && (matrox_id != MATROX_MYST_ID))
	 mga_wait_retrace(af);
   }

   af_scroll_x = x;
   af_scroll_y = y;
}



/* SetActiveBuffer:
 *  Sets which buffer is being drawn onto, for use in multi buffering
 *  systems (not used by Allegro).
 */
void SetActiveBuffer(AF_DRIVER *af, long index)
{
   if (af->OffscreenOffset) {
      af->OffscreenStartY += af_active_page*af_height;
      af->OffscreenEndY += af_active_page*af_height;
   }

   af_active_page = index;
   af_y = index*af_height;

   af_operation = OP_NONE;

   af->OriginOffset = af_width_bytes*af_height*index;

   if (af->OffscreenOffset) {
      af->OffscreenStartY -= af_active_page*af_height;
      af->OffscreenEndY -= af_active_page*af_height;
   }
}



/* SetVisibleBuffer:
 *  Sets which buffer is displayed on the screen, for use in multi buffering
 *  systems (not used by Allegro).
 */
void SetVisibleBuffer(AF_DRIVER *af, long index, long waitVRT)
{
   af_visible_page = index;

   SetDisplayStart(af, af_scroll_x, af_scroll_y, waitVRT);
}



/* GetDisplayStartStatus:
 *  Status poll for triple buffering. Not possible on the majority of
 *  present cards: this function is just a placeholder.
 */
int GetDisplayStartStatus(AF_DRIVER *af)
{
   return 1;
}



/* SetPaletteData:
 *  Palette setting routine.
 */
void SetPaletteData(AF_DRIVER *af, AF_PALETTE *pal, long num, long index, long waitVRT)
{
   int i;

   mga_select();

   if (waitVRT)
      mga_wait_retrace(af);

   for (i=0; i<num; i++) {
      outportb(0x3C8, index+i);
      outportb(0x3C9, pal[i].red/4);
      outportb(0x3C9, pal[i].green/4);
      outportb(0x3C9, pal[i].blue/4);

      af_palette[index+i] = pal[i];
   }

   if ((af_bpp == 8) && (cur_color >= 0) && 
       (((cur_color >= index) && (cur_color < index+num)) || (index == 0)))
      SetCursorColor(af, cur_color, 0, 0);
}



/* SetBank32:
 *  Relocatable bank switch function. This is called with a bank number in
 *  %edx.
 */

asm ("

   .globl _SetBank32, _SetBank32End

      .align 4
   _SetBank32:
      pushl %eax
      pushl %edx
      movb %dl, %ah
      movb $4, %al
      movw $0x03DE, %dx
      outw %ax, %dx
      popl %edx
      popl %eax
      ret

   _SetBank32End:

");



/* SetBank:
 *  C-callable bank switch function. This version simply chains to the
 *  relocatable SetBank32() above.
 */
void SetBank(AF_DRIVER *af, long bank)
{
   asm (
      " call _SetBank32 "
   :
   : "d" (bank)
   );
}



/* SetCursor:
 *  Sets the hardware cursor shape.
 */
void SetCursor(AF_DRIVER *af, AF_CURSOR *cursor)
{
   int addr = af->TotalMemory*1024 - HW_CURSOR_VRAM;
   int p = addr;
   int i;

   hwptr_select(hwptr.LinearMem);

   if (cur_doublemode) {
      for (i=0; i<32; i++) {
	 hwptr_nspokel(hwptr.LinearMem, p, 0);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, bswap(cursor->xorMask[i]));
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, 0xFFFFFFFF);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, bswap(~cursor->andMask[i]));
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, 0);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, bswap(cursor->xorMask[i]));
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, 0xFFFFFFFF);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, bswap(~cursor->andMask[i]));
	 p += 4;
      }
   }
   else {
      for (i=0; i<32; i++) {
	 hwptr_nspokel(hwptr.LinearMem, p, 0);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, bswap(cursor->xorMask[i]));
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, 0xFFFFFFFF);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, bswap(~cursor->andMask[i]));
	 p += 4;
      }

      for (i=0; i<32; i++) {
	 hwptr_nspokel(hwptr.LinearMem, p, 0);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, 0);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, 0xFFFFFFFF);
	 p += 4;
	 hwptr_nspokel(hwptr.LinearMem, p, 0xFFFFFFFF);
	 p += 4;
      }
   }

   mga_select();

   mga_xdata(M_XCURADDL, (addr/1024)&0xFF);
   mga_xdata(M_XCURADDH, (addr/1024)>>8);

   cur_hot_x = cursor->hotx;
   cur_hot_y = cursor->hoty;
}



/* SetCursorPos:
 *  Sets the hardware cursor position.
 */
void SetCursorPos(AF_DRIVER *af, long x, long y)
{
   x -= cur_hot_x;
   y -= cur_hot_y;

   mga_select();

   if (cur_doublemode)
      y *= 2;

   mga_outl(M_CURPOS, ((y+64)<<16) | (x+64));
}



/* SetCursorColor:
 *  Sets the hardware cursor color.
 */
void SetCursorColor(AF_DRIVER *af, unsigned char red, unsigned char green, unsigned char blue)
{
   int r2, g2, b2;

   mga_select();

   if (af_bpp == 8) {
      cur_color = red;

      red = af_palette[cur_color].red;
      green = af_palette[cur_color].green;
      blue = af_palette[cur_color].blue;

      r2 = af_palette[0].red;
      g2 = af_palette[0].green;
      b2 = af_palette[0].blue;
   }
   else {
      cur_color = -1;

      r2 = ~red;
      g2 = ~green;
      b2 = ~blue;
   }

   mga_xdata(M_XCURCOL0RED,   r2);
   mga_xdata(M_XCURCOL0GREEN, b2);
   mga_xdata(M_XCURCOL0BLUE,  b2);
   mga_xdata(M_XCURCOL1RED,   red);
   mga_xdata(M_XCURCOL1GREEN, green);
   mga_xdata(M_XCURCOL1BLUE,  blue);
}



/* ShowCursor:
 *  Turns the hardware cursor on or off.
 */
void ShowCursor(AF_DRIVER *af, long visible)
{
   mga_select();

   mga_xdata(M_XCURCTRL, (visible) ? M_CURCTRL_XGA : M_CURCTRL_OFF);
}



/* WaitTillIdle:
 *  Delay until the hardware controller has finished drawing.
 */
void WaitTillIdle(AF_DRIVER *af)
{
   FAF_HWPTR oldptr;
   int tries = 2;

   hwptr_unselect(oldptr);

   mga_select();

   while (tries--) {

      do {
      } while (!(mga_inl(M_FIFOSTATUS) & 0x200));

      do {
      } while (mga_inl(M_STATUS) & 0x10000);

      mga_outb(M_CRTC_INDEX, 0); 
   }

   hwptr_select(oldptr);
}



/* mga_mix:
 *  Returns drawing engine control flags for the specified VBE/AF mix mode.
 */
long mga_mix(int mix)
{
   switch (mix) {

      case AF_REPLACE_MIX: 
	 return 0x000C0040;

      case AF_AND_MIX: 
	 return 0x00080010;

      case AF_OR_MIX: 
	 return 0x000E0010;

      case AF_XOR_MIX: 
	 return 0x00060010;

      default: 
	 return 0x000A0010; 
   }
}



/* mga_mix_noblk:
 *  Returns drawing engine control flags for the specified VBE/AF mix mode.
 */
long mga_mix_noblk(int mix)
{
   switch (mix) {

      case AF_REPLACE_MIX: 
	 return 0x000C0000;

      case AF_AND_MIX: 
	 return 0x00080010;

      case AF_OR_MIX: 
	 return 0x000E0010;

      case AF_XOR_MIX: 
	 return 0x00060010;

      default: 
	 return 0x000A0010; 
   }
}



/* SetMix:
 *  Specifies the pixel mix mode to be used for hardware drawing functions.
 */
void SetMix(AF_DRIVER *af, long foreMix, long backMix)
{
   af_fore_mix = foreMix;
   af_back_mix = backMix;

   af_operation = OP_NONE;
}



/* Set8x8MonoPattern:
 *  Downloads a monochrome (packed bit) pattern, for use by the 
 *  DrawPattScan() and DrawPattRect() functions. This is always sized
 *  8x8, and aligned with the top left corner of video memory: if other
 *  alignments are desired, the pattern will be prerotated before it 
 *  is passed to this routine.
 */
void Set8x8MonoPattern(AF_DRIVER *af, unsigned char *pattern)
{
   unsigned long d1, d2;

   d1 = pattern[0] | (pattern[1]<<8) | (pattern[2]<<16) | (pattern[3]<<24);
   d2 = pattern[4] | (pattern[5]<<8) | (pattern[6]<<16) | (pattern[7]<<24);

   mga_select();

   mga_fifo(2);
   mga_outl(M_PAT0, d1);
   mga_outl(M_PAT1, d2);
}



/* Set8x8ColorPattern:
 *  Downloads a color pattern, for use by the DrawColorPattScan() and
 *  DrawColorPattRect() functions. This is always sized 8x8, and aligned
 *  with the top left corner of video memory: if other alignments are
 *  desired, the pattern will be prerotated before it is passed to this
 *  routine. The color values are presented in the native format for
 *  the current video mode, but padded to 32 bits (so the pattern is
 *  always an 8x8 array of longs).
 */
void Set8x8ColorPattern(AF_DRIVER *af, int index, unsigned long *pattern)
{
   int p = pat_off_bytes[index];
   int x, y;

   hwptr_select(hwptr.LinearMem);

   switch (af_bpp) {

      case 8:
	 for (y=0; y<8; y++)
	    for (x=0; x<8; x++)
	       hwptr_nspokeb(hwptr.LinearMem, p+(y*32+x), pattern[y*8+x]);
	 break;

      case 15:
      case 16:
	 for (y=0; y<8; y++)
	    for (x=0; x<8; x++)
	       hwptr_nspokew(hwptr.LinearMem, p+(y*32+x)*2, pattern[y*8+x]);
	 break;

      case 32:
	 for (y=0; y<8; y++)
	    for (x=0; x<8; x++)
	       hwptr_nspokel(hwptr.LinearMem, p+(y*32+x)*4, pattern[y*8+x]);
	 break;
   }
}



/* Use8x8ColorPattern:
 *  Selects one of the patterns previously downloaded by Set8x8ColorPattern().
 */
void Use8x8ColorPattern(AF_DRIVER *af, int index)
{
   af_color_pattern = index;
}



/* DrawScan:
 *  Fills a scanline in the current foreground mix mode. Draws up to but
 *  not including the second x coordinate. If the second coord is less
 *  than the first, they are swapped. If they are equal, nothing is drawn.
 */
void DrawScan(AF_DRIVER *af, long color, long y, long x1, long x2)
{
   if (x1 < x2)
      DrawRect(af, color, x1, y, x2-x1, 1);
   else if (x2 < x1)
      DrawRect(af, color, x2, y, x1-x2, 1);
}



/* DrawPattScan:
 *  Fills a scanline using the current mono pattern. Set pattern bits are
 *  drawn using the specified foreground color and the foreground mix
 *  mode, and clear bits use the background color and background mix mode.
 */
void DrawPattScan(AF_DRIVER *af, long foreColor, long backColor, long y, long x1, long x2)
{
   if (x1 < x2)
      DrawPattRect(af, foreColor, backColor, x1, y, x2-x1, 1);
   else if (x2 < x1)
      DrawPattRect(af, foreColor, backColor, x2, y, x1-x2, 1);
}



/* DrawColorPattScan:
 *  Fills a scanline using the current color pattern and mix mode.
 */
void DrawColorPattScan(AF_DRIVER *af, long y, long x1, long x2)
{
   if (x1 < x2)
      DrawColorPattRect(af, x1, y, x2-x1, 1);
   else if (x2 < x1)
      DrawColorPattRect(af, x2, y, x1-x2, 1);
}



/* DrawRect:
 *  Fills a rectangle in the current foreground mix mode.
 */
void DrawRect(AF_DRIVER *af, unsigned long color, long left, long top, long width, long height)
{
   long cmd;

   mga_select();

   /* set engine state */
   if (af_operation != OP_DRAWRECT) {

      cmd = M_DWG_TRAP | M_DWG_SOLID | M_DWG_ARZERO | 
	    M_DWG_SGNZERO | M_DWG_SHIFTZERO;

      if ((matrox_id == MATROX_MII_PCI_ID) || 
	  (matrox_id == MATROX_MII_AGP_ID))
	 cmd |= M_DWG_TRANSC;

      mga_fifo(1);
      mga_outl(M_DWGCTL, cmd | mga_mix(af_fore_mix));

      af_operation = OP_DRAWRECT;
   }

   /* make the color a dword */
   if (af_bpp == 8) {
      color |= color<<8;
      color |= color<<16;
   }
   else if (af_bpp < 32) {
      color |= color<<16;
   }

   /* draw the rectangle */
   mga_fifo(3);
   mga_outl(M_FCOL, color);
   mga_outl(M_FXBNDRY, ((left+width)<<16) | left);
   mga_outl(M_YDSTLEN | M_EXEC, ((top+af_y)<<16) | height);
}



/* DrawPattRect:
 *  Fills a rectangle using the current mono pattern. Set pattern bits are
 *  drawn using the specified foreground color and the foreground mix
 *  mode, and clear bits use the background color and background mix mode.
 */
void DrawPattRect(AF_DRIVER *af, unsigned long foreColor, unsigned long backColor, long left, long top, long width, long height)
{
   long cmd;

   mga_select();

   /* set engine state */
   if (af_operation != OP_DRAWPATTRECT) {

      cmd = M_DWG_TRAP | M_DWG_ARZERO | M_DWG_SGNZERO | M_DWG_SHIFTZERO;

      if (af_back_mix == AF_NOP_MIX)
	 cmd |= mga_mix(af_fore_mix) | M_DWG_TRANSC;
      else
	 cmd |= mga_mix_noblk(af_fore_mix);

      mga_fifo(1);
      mga_outl(M_DWGCTL, cmd);

      af_operation = OP_DRAWPATTRECT;
   }

   /* make the color a dword */
   if (af_bpp == 8) {
      foreColor |= foreColor<<8;
      foreColor |= foreColor<<16;
   }
   else if (af_bpp < 32) {
      foreColor |= foreColor<<16;
   }

   /* sort out the background color */
   if (af_back_mix == AF_NOP_MIX) {
      backColor = 0;
   }
   else {
      if (af_bpp == 8) {
	 backColor |= backColor<<8;
	 backColor |= backColor<<16;
      }
      else if (af_bpp < 32) {
	 backColor |= backColor<<16;
      }
   }

   /* draw the rectangle */
   mga_fifo(4);
   mga_outl(M_FCOL, foreColor);
   mga_outl(M_BCOL, backColor);
   mga_outl(M_FXBNDRY, ((left+width)<<16) | left);
   mga_outl(M_YDSTLEN | M_EXEC, ((top+af_y)<<16) | height);
}



/* DrawColorPattRect:
 *  Fills a rectangle using the current color pattern and mix mode.
 */
void DrawColorPattRect(AF_DRIVER *af, long left, long top, long width, long height)
{
   int addr, addr2, offs;

   mga_select();

   /* set engine state */
   if (af_operation != OP_DRAWCOLORPATTRECT) {
      mga_fifo(1);
      mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SGNZERO | 
			 M_DWG_SHIFTZERO | M_DWG_BFCOL | 
			 M_DWG_PATTERN | mga_mix_noblk(af_fore_mix));

      af_operation = OP_DRAWCOLORPATTRECT;
   }

   /* calculate pattern address */
   addr = pat_off_pixels[af_color_pattern] + (left&7) + ((top+af_y)&7)*32;
   offs = (af_bpp == 8) ? 2 : ((af_bpp == 32) ? 6 : 4);
   addr2 = ((addr+offs)&7) | (addr&0xFFFFFFF8);

   /* draw the rectangle */
   mga_fifo(5);
   mga_outl(M_AR0, addr2);
   mga_outl(M_AR3, addr);
   mga_outl(M_AR5, 32);
   mga_outl(M_FXBNDRY, ((left+width-1)<<16) | left);
   mga_outl(M_YDSTLEN | M_EXEC, ((top+af_y)<<16) | height);
}



/* DrawLine:
 *  Draws a line, using the current foreground mix mode.
 */
void DrawLine(AF_DRIVER *af, unsigned long color, fixed x1, fixed y1, fixed x2, fixed y2)
{
   mga_select();

   /* set engine state */
   if (af_operation != OP_DRAWLINE) {
      mga_fifo(1);
      mga_outl(M_DWGCTL, M_DWG_AUTOLINE_CLOSE | M_DWG_SOLID | 
			 M_DWG_SHIFTZERO | mga_mix_noblk(af_fore_mix));

      af_operation = OP_DRAWLINE;
   }

   /* make the color a dword */
   if (af_bpp == 8) {
      color |= color<<8;
      color |= color<<16;
   }
   else if (af_bpp < 32) {
      color |= color<<16;
   }

   /* round coordinates from fixed point to integer */
   x1 = (x1+0x8000) >> 16;
   y1 = (y1+0x8000) >> 16;
   x2 = (x2+0x8000) >> 16;
   y2 = (y2+0x8000) >> 16;

   /* draw the line */
   mga_fifo(3);
   mga_outl(M_FCOL, color);
   mga_outl(M_XYSTRT, ((y1+af_y)<<16) | x1);
   mga_outl(M_XYEND | M_EXEC, ((y2+af_y)<<16) | x2);
}



/* DrawSlowTrap:
 *  Special case trapezoid filler, for handling cases where the two edges
 *  cross. I'm not sure if this is allowed to happen, but I'd rather support
 *  it just in case :-)
 */
static void DrawSlowTrap(AF_DRIVER *af, AF_TRAP *trap)
{
   int ix1, ix2;
   long cmd;

   /* set engine state */
   if (af_operation != OP_DRAWSLOWTRAP) {

      cmd = M_DWG_TRAP | M_DWG_SOLID | M_DWG_ARZERO | 
	    M_DWG_SGNZERO | M_DWG_SHIFTZERO;

      if ((matrox_id == MATROX_MII_PCI_ID) || 
	  (matrox_id == MATROX_MII_AGP_ID))
	 cmd |= M_DWG_TRANSC;

      mga_fifo(1);
      mga_outl(M_DWGCTL, cmd | mga_mix(af_fore_mix));

      af_operation = OP_DRAWSLOWTRAP;
   }

   /* scan-convert the trapezoid */
   while (trap->count--) {
      ix1 = (trap->x1+0x8000) >> 16;
      ix2 = (trap->x2+0x8000) >> 16;

      if (ix2 < ix1) {
	 int tmp = ix1;
	 ix1 = ix2;
	 ix2 = tmp;
      }

      if (ix1 < ix2) {
	 mga_fifo(2);
	 mga_outl(M_FXBNDRY, (ix2<<16) | ix1);
	 mga_outl(M_YDSTLEN | M_EXEC, ((trap->y+af_y)<<16) | 1);
      }

      trap->x1 += trap->slope1;
      trap->x2 += trap->slope2;
      trap->y++;
   }
}



/* DrawTrap:
 *  Draws a filled trapezoid, using the current foreground mix mode.
 */
void DrawTrap(AF_DRIVER *af, unsigned long color, AF_TRAP *trap)
{
   fixed startx1, startx2;
   fixed endx1, endx2;
   fixed tmp;
   int dx1, dx2;
   int sgn = 0;

   mga_select();

   /* make the color a dword */
   if (af_bpp == 8) {
      color |= color<<8;
      color |= color<<16;
   }
   else if (af_bpp < 32) {
      color |= color<<16;
   }

   mga_fifo(1);
   mga_outl(M_FCOL, color);

   /* read the edge coordinates */
   startx1 = trap->x1;
   startx2 = trap->x2;

   endx1 = trap->x1+trap->slope1*trap->count;
   endx2 = trap->x2+trap->slope2*trap->count;

   if (startx1 > startx2) {
      /* special case for when the edges cross */
      if (endx1 < endx2) {
	 DrawSlowTrap(af, trap);
	 return;
      }

      /* make sure the left edge comes first */ 
      tmp = startx1;
      startx1 = startx2;
      startx2 = tmp;

      tmp = endx1;
      endx1 = endx2;
      endx2 = tmp;

      trap->x1 = endx2;
      trap->x2 = endx1;
   }
   else {
      trap->x1 = endx1;
      trap->x2 = endx2;
   }

   /* calculate deltas */
   dx1 = (endx1-startx1) >> 16;
   dx2 = (endx2-startx2) >> 16;

   startx1 = (startx1+0x8000) >> 16;
   startx2 = (startx2+0x8000) >> 16;

   if (dx1 < 0)
      sgn |= M_SDXL;

   if (dx2 < 0)
      sgn |= M_SDXR;

   /* set engine state */
   if (af_operation != OP_DRAWTRAP) {
      mga_fifo(1);
      mga_outl(M_DWGCTL, M_DWG_TRAP | M_DWG_SOLID | 
			 M_DWG_SHIFTZERO | mga_mix(af_fore_mix));

      af_operation = OP_DRAWTRAP;
   }

   /* draw the trapezoid */
   mga_fifo(9);
   mga_outl(M_AR0, trap->count);
   mga_outl(M_AR1, (dx1 < 0) ? dx1+trap->count-1 : -dx1);
   mga_outl(M_AR2, -ABS(dx1));
   mga_outl(M_AR4, (dx2 < 0) ? dx2+trap->count-1 : -dx2);
   mga_outl(M_AR5, -ABS(dx2));
   mga_outl(M_AR6, trap->count);
   mga_outl(M_SGN, sgn);
   mga_outl(M_FXBNDRY, (startx2<<16) | startx1);
   mga_outl(M_YDSTLEN | M_EXEC, ((trap->y+af_y)<<16) | trap->count);

   trap->y += trap->count;
   trap->count = 0;
}



/* PutMonoImage:
 *  Expands a monochrome bitmap from system memory onto the screen.
 */
void PutMonoImage(AF_DRIVER *af, long foreColor, long backColor, long dstX, long dstY, long byteWidth, long srcX, long srcY, long width, long height, unsigned char *image)
{
   unsigned long val;
   int i, n, x, y;
   long cmd;
   int op;

   mga_select();

   /* is the source data in linear format? */
   if ((srcX > 0) || (width != byteWidth*8))
      op = OP_PUTMONOIMAGE_SLOW;
   else
      op = OP_PUTMONOIMAGE_LINEAR;

   /* set engine state */
   if (af_operation != op) {

      cmd = M_DWG_ILOAD | M_DWG_SGNZERO | M_DWG_SHIFTZERO | M_DWG_BMONOWF;

      if (op == OP_PUTMONOIMAGE_LINEAR)
	 cmd |= M_DWG_LINEAR;

      if (af_back_mix == AF_NOP_MIX)
	 cmd |= mga_mix(af_fore_mix) | M_DWG_TRANSC;
      else
	 cmd |= mga_mix_noblk(af_fore_mix);

      mga_fifo(1);
      mga_outl(M_DWGCTL, cmd);

      af_operation = op;
   }

   /* make the color a dword */
   if (af_bpp == 8) {
      foreColor |= foreColor<<8;
      foreColor |= foreColor<<16;
   }
   else if (af_bpp < 32) {
      foreColor |= foreColor<<16;
   }

   /* sort out the background color */
   if (af_back_mix == AF_NOP_MIX) {
      backColor = 0;
   }
   else {
      if (af_bpp == 8) {
	 backColor |= backColor<<8;
	 backColor |= backColor<<16;
      }
      else if (af_bpp < 32) {
	 backColor |= backColor<<16;
      }
   }

   /* skip leading image data */
   image += srcY*byteWidth;

   /* start the hardware engine */
   mga_fifo(6);
   mga_outl(M_FCOL, foreColor);
   mga_outl(M_BCOL, backColor);
   mga_outl(M_AR0, (op == OP_PUTMONOIMAGE_LINEAR) ? width*height-1 : width-1);
   mga_outl(M_AR3, 0);
   mga_outl(M_FXBNDRY, ((dstX+width-1)<<16) | dstX);
   mga_outl(M_YDSTLEN | M_EXEC, ((dstY+af_y)<<16) | height);

   if (op == OP_PUTMONOIMAGE_LINEAR) { 
      /* block copy to the psuedo-dma window */
      n = (width*height+31)/32;

      while (n > 0) {
	 i = MIN(n, 1792);
	 n -= i;

	 #ifdef NO_HWPTR

	    asm (
	       " rep ; movsl "
	    :
	    : "c" (i),
	      "S" (image),
	      "D" (af->IOMemMaps[0])

	    : "%ecx", "%esi", "%edi"
	    );

	 #else

	    asm (
	       " movw %%es, %%dx ; "
	       " movw %%ax, %%es ; "
	       " rep ; movsl ; "
	       " movw %%dx, %%es "
	    :
	    : "c" (i),
	      "S" (image),
	      "D" (hwptr.IOMemMaps[0].offset),
	      "a" (hwptr.IOMemMaps[0].sel)

	    : "%ecx", "%edx", "%esi", "%edi"
	    );

	 #endif
      }
   }
   else {
      /* special munging is required when the source isn't linear */
      for (y=0; y<height; y++) {
	 val = 0;
	 i = 0;

	 for (x=0; x<width; x++) {
	    val <<= 1;
	    if (image[(srcX+x)/8] & (0x80>>((srcX+x)&7)))
	       val |= 1;

	    if (i == 31) {
	       mga_outl(0, bswap(val));
	       val = 0;
	       i = 0;
	    }
	    else
	       i++;
	 }

	 if (i)
	    mga_outl(0, bswap(val<<(32-i)));

	 image += byteWidth;
      }
   }
}



/* BitBlt:
 *  Blits from one part of video memory to another, using the specified
 *  mix operation. This must correctly handle the case where the two
 *  regions overlap.
 */
void BitBlt(AF_DRIVER *af, long left, long top, long width, long height, long dstLeft, long dstTop, long op)
{
   int start, end;
   int operation;

   mga_select();

   /* which direction to copy? */
   if ((left+width > dstLeft) && (top+height > dstTop) &&
       (dstLeft+width > left) && (dstTop+height > top) &&
       ((dstTop > top) || ((dstTop == top) && (dstLeft > left))))
      operation = OP_BITBLT_BACKWARD;
   else
      operation = OP_BITBLT_FORWARD;

   /* set engine state */
   if (af_operation != operation) {
      if (operation == OP_BITBLT_FORWARD) {
	 mga_fifo(2);
	 mga_outl(M_AR5, af_width_pixels);
	 mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO |
			    M_DWG_SGNZERO | M_DWG_BFCOL | 
			    mga_mix_noblk(op));
      } 
      else {
	 mga_fifo(3);
	 mga_outl(M_SGN, 5);
	 mga_outl(M_AR5, -af_width_pixels);
	 mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | 
			    M_DWG_BFCOL | mga_mix_noblk(op));
      }

      af_operation = operation;
   }

   /* adjust parameters */
   if (operation == OP_BITBLT_BACKWARD) {
      width--;
      end = (top+af_y+height-1)*af_width_pixels + left;
      start = end+width;
      dstTop += height-1;
   }
   else {
      width--;
      start = (top+af_y)*af_width_pixels + left;
      end = start+width; 
   }

   /* do the blit */
   mga_fifo(4);
   mga_outl(M_AR0, end);
   mga_outl(M_AR3, start);
   mga_outl(M_FXBNDRY, ((dstLeft+width)<<16) | dstLeft);
   mga_outl(M_YDSTLEN | M_EXEC, ((dstTop+af_y)<<16) | height);
}



/* BitBltSys:
 *  Copies from system memory to the screen.
 */
void BitBltSys(AF_DRIVER *af, void *srcAddr, long srcPitch, long srcLeft, long srcTop, long width, long height, long dstLeft, long dstTop, long op)
{
   int i, n;
   void *addr;

   mga_select();

   /* set engine state */
   if (af_operation != OP_BITBLTSYS) {
      mga_fifo(2);
      mga_outl(M_AR5, 0);
      mga_outl(M_DWGCTL, M_DWG_ILOAD | M_DWG_SHIFTZERO | 
			 M_DWG_SGNZERO | M_DWG_BFCOL | 
			 mga_mix_noblk(op));

      af_operation = OP_BITBLTSYS;
   }

   /* adjust source bitmap pointers */
   switch (af_bpp) {

      case 8:
	 addr = srcAddr + srcLeft + srcTop*srcPitch;
	 n = (width+3)/4;
	 break;

      case 15:
      case 16:
	 addr = srcAddr + srcLeft*2 + srcTop*srcPitch;
	 n = (width+1)/2;
	 break;

      case 32:
	 addr = srcAddr + srcLeft*4 + srcTop*srcPitch;
	 n = width;
	 break;

      default:
	 return;
   }

   /* wake up the hardware engine */
   mga_fifo(4);
   mga_outl(M_AR0, width-1);
   mga_outl(M_AR3, 0);
   mga_outl(M_FXBNDRY, ((dstLeft+width-1)<<16) | dstLeft);
   mga_outl(M_YDSTLEN | M_EXEC, ((dstTop+af_y)<<16) | height);

   /* copy data to the psuedo-dma window */
   for (i=0; i<height; i++) {

      #ifdef NO_HWPTR

	 asm (
	    " rep ; movsl "
	 :
	 : "c" (n),
	   "S" (addr),
	   "D" (af->IOMemMaps[0])

	 : "%ecx", "%esi", "%edi"
	 );

      #else

	 asm (
	    " movw %%es, %%dx ; "
	    " movw %%ax, %%es ; "
	    " rep ; movsl ; "
	    " movw %%dx, %%es "
	 :
	 : "c" (n),
	   "S" (addr),
	   "D" (hwptr.IOMemMaps[0].offset),
	   "a" (hwptr.IOMemMaps[0].sel)

	 : "%ecx", "%edx", "%esi", "%edi"
	 );

      #endif

      addr += srcPitch;
   }
}



/* SrcTransBlt:
 *  Blits from one part of video memory to another, using the specified
 *  mix operation and skipping any source pixels which match the specified
 *  transparent color. Results are undefined if the two regions overlap.
 */
void SrcTransBlt(AF_DRIVER *af, long left, long top, long width, long height, long dstLeft, long dstTop, long op, unsigned long transparent)
{
   int start, end;

   mga_select();

   /* set engine state */
   if (af_operation != OP_SRCTRANSBLT) {

      if (af_bpp == 8) {
	 transparent |= transparent<<8;
	 transparent |= transparent<<16;
      }
      else if (af_bpp < 32) {
	 transparent |= transparent<<16;
      }

      mga_fifo(4);
      mga_outl(M_FCOL, transparent);
      mga_outl(M_BCOL, 0xFFFFFFFF);
      mga_outl(M_AR5, af_width_pixels);
      mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | 
			 M_DWG_SGNZERO | M_DWG_BFCOL | 
			 M_DWG_TRANSC | mga_mix_noblk(op));

      af_operation = OP_SRCTRANSBLT;
   }

   /* adjust parameters */
   width--;
   start = (top+af_y)*af_width_pixels + left;
   end = start+width; 

   /* do the blit */
   mga_fifo(4);
   mga_outl(M_AR0, end);
   mga_outl(M_AR3, start);
   mga_outl(M_FXBNDRY, ((dstLeft+width)<<16) | dstLeft);
   mga_outl(M_YDSTLEN | M_EXEC, ((dstTop+af_y)<<16) | height);
}



/* SrcTransBltSys:
 *  Copies from system memory to the screen, skipping any source pixels that
 *  match the specified transparent color.
 */
void SrcTransBltSys(AF_DRIVER *af, void *srcAddr, long srcPitch, long srcLeft, long srcTop, long width, long height, long dstLeft, long dstTop, long op, unsigned long transparent)
{
   int i, n;
   void *addr;

   mga_select();

   /* set engine state */
   if (af_operation != OP_SRCTRANSBLTSYS) {

      if (af_bpp == 8) {
	 transparent |= transparent<<8;
	 transparent |= transparent<<16;
      }
      else if (af_bpp < 32) {
	 transparent |= transparent<<16;
      }

      mga_fifo(4);
      mga_outl(M_FCOL, transparent);
      mga_outl(M_BCOL, 0xFFFFFFFF);
      mga_outl(M_AR5, 0);
      mga_outl(M_DWGCTL, M_DWG_ILOAD | M_DWG_SHIFTZERO | 
			 M_DWG_SGNZERO | M_DWG_BFCOL | 
			 M_DWG_TRANSC | mga_mix_noblk(op));

      af_operation = OP_SRCTRANSBLTSYS;
   }

   /* adjust source bitmap pointers */
   switch (af_bpp) {

      case 8:
	 addr = srcAddr + srcLeft + srcTop*srcPitch;
	 n = (width+3)/4;
	 break;

      case 15:
      case 16:
	 addr = srcAddr + srcLeft*2 + srcTop*srcPitch;
	 n = (width+1)/2;
	 break;

      case 32:
	 addr = srcAddr + srcLeft*4 + srcTop*srcPitch;
	 n = width;
	 break;

      default:
	 return;
   }

   /* wake up the hardware engine */
   mga_fifo(4);
   mga_outl(M_AR0, width-1);
   mga_outl(M_AR3, 0);
   mga_outl(M_FXBNDRY, ((dstLeft+width-1)<<16) | dstLeft);
   mga_outl(M_YDSTLEN | M_EXEC, ((dstTop+af_y)<<16) | height);

   /* copy data to the psuedo-dma window */
   for (i=0; i<height; i++) {

      #ifdef NO_HWPTR

	 asm (
	    " rep ; movsl "
	 :
	 : "c" (n),
	   "S" (addr),
	   "D" (af->IOMemMaps[0])

	 : "%ecx", "%esi", "%edi"
	 );

      #else

	 asm (
	    " movw %%es, %%dx ; "
	    " movw %%ax, %%es ; "
	    " rep ; movsl ; "
	    " movw %%dx, %%es "
	 :
	 : "c" (n),
	   "S" (addr),
	   "D" (hwptr.IOMemMaps[0].offset),
	   "a" (hwptr.IOMemMaps[0].sel)

	 : "%ecx", "%edx", "%esi", "%edi"
	 );

      #endif

      addr += srcPitch;
   }
}

