/*
 * linux/drivers/video/vmware/vmwarefb.c -- VMware SVGA Framebuffer Driver
 *
 * Copyright (c) 2002  Denis Oliver Kropp <dok@directfb.org>
 *
 *
 * Card specific code is based on XFree86's VMware driver.
 * Framebuffer framework code is based on code of neofb.
 *
 * This file is subject to the terms and conditions of the GNU General
 * Public License.  See the file COPYING in the main directory of this
 * archive for more details.
 *
 *
 * 0.6.0
 *  - added accelerated clear and move for console acceleration (dok)
 *  - removed VGA code that isn't needed at all in SVGA mode (dok)
 *  - disabling acceleration is only possible after power off via "noaccel" (dok)
 *  - added a workaround to fix console after running X (dok)
 *
 * 0.5.2
 *  - take care of fb offset, fixes non-standard resolutions (dok)
 *
 * 0.5.1
 *  - fixed undeclared integer when built as a module (dok)
 *
 * 0.5.0
 *  - initial version (dok)
 *
 *
 * TODO
 * - support for changing bits per pixel (using 8bit emulation capability)
 * - panning (yet unsupported by WMware)
 * - disabling accel after bootup (requires yet unreleased VMware above 3.1)
 *
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/pci.h>
#include <linux/init.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#include <video/fbcon.h>
#include <video/fbcon-cfb8.h>
#include <video/fbcon-cfb16.h>
#include <video/fbcon-cfb24.h>
#include <video/fbcon-cfb32.h>

#include "vmwarefb.h"

#include "svga_reg.h"
#include "guest_os.h"

#define VMWAREFB_VERSION "0.6.0"

/* --------------------------------------------------------------------- */

static int disabled = 0;
static int noaccel = 0;

MODULE_AUTHOR("(c) 2002  Denis Oliver Kropp <dok@directfb.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FBDev driver for VMware Virtual SVGA Card");
MODULE_PARM(disabled, "i");
MODULE_PARM_DESC(disabled, "Disable this driver's initialization.");
MODULE_PARM(noaccel, "i");
MODULE_PARM_DESC(noaccel, "Disable console acceleration.");

static struct fb_var_screeninfo __devinitdata vmwarefb_var640x480x8 = {
  accel_flags:          0,
  xres:                 640,
  yres:                 480,
  xres_virtual:         640,
  yres_virtual:         480,
  bits_per_pixel:       8,
  pixclock:             39722,
  left_margin:          48,
  right_margin:         16,
  upper_margin:         33,
  lower_margin:         10,
  hsync_len:            96,
  vsync_len:            2,
  sync:                 0,
  vmode:                FB_VMODE_NONINTERLACED
};

static struct fb_var_screeninfo __devinitdata vmwarefb_var800x600x8 = {
  accel_flags:          0,
  xres:                 800,
  yres:                 600,
  xres_virtual:         800,
  yres_virtual:         600,
  bits_per_pixel:       8,
  pixclock:             25000,
  left_margin:  	88,
  right_margin: 	40,
  upper_margin: 	23,
  lower_margin: 	1,
  hsync_len:            128,
  vsync_len:            4,
  sync:                 FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
  vmode:                FB_VMODE_NONINTERLACED
};

static struct fb_var_screeninfo __devinitdata vmwarefb_var1024x768x8 = {
  accel_flags:          0,
  xres:                 1024,
  yres:                 768,
  xres_virtual:         1024,
  yres_virtual:         768,
  bits_per_pixel:       8,
  pixclock:             15385,
  left_margin:          160,
  right_margin:         24,
  upper_margin:         29,
  lower_margin:         3,
  hsync_len:            136,
  vsync_len:            6,
  sync:                 FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
  vmode:                FB_VMODE_NONINTERLACED
};

static struct fb_var_screeninfo *vmwarefb_var = &vmwarefb_var800x600x8;

/* ------------------------ VMware specific functions ------------------------ */

static volatile u32
vmwareReadReg (const struct vmwarefb_info *info, int index)
{
  outl (index, info->indexReg);

  return inl (info->valueReg);
}

static void
vmwareWriteReg (const struct vmwarefb_info *info, int index, u32 value)
{
  outl (index, info->indexReg);
  outl (value, info->valueReg);
}

static void
vmwareWriteWordToFIFO (const struct vmwarefb_info *info, u32 value)
{
  u32* vmwareFIFO = info->vmwareFIFO;

  if (!vmwareFIFO)
    return;

  /* Need to sync? */
  if ((vmwareFIFO[SVGA_FIFO_NEXT_CMD] + sizeof(u32) == vmwareFIFO[SVGA_FIFO_STOP])
      || (vmwareFIFO[SVGA_FIFO_NEXT_CMD] == vmwareFIFO[SVGA_FIFO_MAX] - sizeof(u32) &&
          vmwareFIFO[SVGA_FIFO_STOP] == vmwareFIFO[SVGA_FIFO_MIN]))
    {
      vmwareWriteReg(info, SVGA_REG_SYNC, 1);
      while (vmwareReadReg(info, SVGA_REG_BUSY)) ;
    }

  vmwareFIFO[vmwareFIFO[SVGA_FIFO_NEXT_CMD] / sizeof(u32)] = value;
  vmwareFIFO[SVGA_FIFO_NEXT_CMD] += sizeof(u32);

  if (vmwareFIFO[SVGA_FIFO_NEXT_CMD] == vmwareFIFO[SVGA_FIFO_MAX])
    vmwareFIFO[SVGA_FIFO_NEXT_CMD] = vmwareFIFO[SVGA_FIFO_MIN];
}

static void
vmwareWaitForFB (struct vmwarefb_info *info)
{
  if (info->vmwareFIFOMarkSet)
    {
      vmwareWriteReg (info, SVGA_REG_SYNC, 1);

      while (vmwareReadReg(info, SVGA_REG_BUSY)) ;

      info->vmwareFIFOMarkSet = 0;
    }
}

static void
vmwareSendSVGACmdUpdate (const struct vmwarefb_info *info,
                         int x, int y, int width, int height)
{
  vmwareWriteWordToFIFO (info, SVGA_CMD_UPDATE);
  vmwareWriteWordToFIFO (info, x);
  vmwareWriteWordToFIFO (info, y);
  vmwareWriteWordToFIFO (info, width);
  vmwareWriteWordToFIFO (info, height);
}

static u32
vmwareCalculateWeight (u32 mask)
{
  u32 weight;

  for (weight = 0; mask; mask >>= 1)
    {
      if (mask & 1)
        weight++;
    }

  return weight;
}

/*
 *-----------------------------------------------------------------------------
 *
 * VMXGetVMwareSvgaId --
 *
 *    Retrieve the SVGA_ID of the VMware SVGA adapter.
 *    This function should hide any backward compatibility mess.
 *
 * Results:
 *    The SVGA_ID_* of the present VMware adapter.
 *
 * Side effects:
 *    ins/outs
 *
 *-----------------------------------------------------------------------------
 */

static u32
VMXGetVMwareSvgaId (const struct vmwarefb_info *info)
{
   u32 vmware_svga_id;

   /* Any version with any SVGA_ID_* support will initialize SVGA_REG_ID
    * to SVGA_ID_0 to support versions of this driver with SVGA_ID_0.
    *
    * Versions of SVGA_ID_0 ignore writes to the SVGA_REG_ID register.
    *
    * Versions of SVGA_ID_1 will allow us to overwrite the content
    * of the SVGA_REG_ID register only with the values SVGA_ID_0 or SVGA_ID_1.
    *
    * Versions of SVGA_ID_2 will allow us to overwrite the content
    * of the SVGA_REG_ID register only with the values SVGA_ID_0 or SVGA_ID_1
    * or SVGA_ID_2.
    */

   vmwareWriteReg(info, SVGA_REG_ID, SVGA_ID_2);
   vmware_svga_id = vmwareReadReg(info, SVGA_REG_ID);
   if (vmware_svga_id == SVGA_ID_2) {
      return SVGA_ID_2;
   }

   vmwareWriteReg(info, SVGA_REG_ID, SVGA_ID_1);
   vmware_svga_id = vmwareReadReg(info, SVGA_REG_ID);
   if (vmware_svga_id == SVGA_ID_1) {
      return SVGA_ID_1;
   }

   if (vmware_svga_id == SVGA_ID_0) {
      return SVGA_ID_0;
   }

   /* No supported VMware SVGA devices found */
   return SVGA_ID_INVALID;
}

static void
VMWAREInitFIFO (struct vmwarefb_info *info)
{
  u32* vmwareFIFO = info->vmwareFIFO;

  if (vmwareFIFO)
    return;

  vmwareFIFO = (u32*) info->mmio.vbase;

  vmwareFIFO[SVGA_FIFO_MIN]      = 4 * sizeof(u32);
  vmwareFIFO[SVGA_FIFO_MAX]      = info->mmio.len;
  vmwareFIFO[SVGA_FIFO_NEXT_CMD] = 4 * sizeof(u32);
  vmwareFIFO[SVGA_FIFO_STOP]     = 4 * sizeof(u32);

  info->vmwareFIFO = vmwareFIFO;
  info->vmwareFIFOMarkSet = 0;

  vmwareWriteReg (info, SVGA_REG_CONFIG_DONE, 1);
}

static void
VMWAREStopFIFO (struct vmwarefb_info *info)
{
  u32* vmwareFIFO = info->vmwareFIFO;

  if (!vmwareFIFO)
    return;

  vmwareWaitForFB (info);

  /* doesn't work in current version of VMware */
  vmwareWriteReg (info, SVGA_REG_CONFIG_DONE, 0);

  info->vmwareFIFO = NULL;
}

/* -------------------------- Hardware Acceleration -------------------------- */

static void
vmware_rectfill (struct vmwarefb_info *info,
                 int x, int y, int width, int height, u32 color)
{
  vmwareWriteWordToFIFO (info, SVGA_CMD_RECT_FILL);
  vmwareWriteWordToFIFO (info, color);
  vmwareWriteWordToFIFO (info, x);
  vmwareWriteWordToFIFO (info, y);
  vmwareWriteWordToFIFO (info, width);
  vmwareWriteWordToFIFO (info, height);

  info->vmwareFIFOMarkSet = 1;

  vmwareSendSVGACmdUpdate (info, x, y, width, height);
}

static void
vmware_accel_setup (struct display *p)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)p->fb_info;

  info->dispsw.setup (p);
}

static void
vmware_accel_bmove (struct display *p,
                    int sy, int sx, int dy, int dx,
                    int height, int width)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)p->fb_info;
  unsigned int fh, fw;

  fw      = fontwidth(p);
  sx     *= fw;
  dx     *= fw;
  width  *= fw;

  fh      = fontheight(p);
  sy     *= fh;
  dy     *= fh;
  height *= fh;

  vmwareWriteWordToFIFO (info, SVGA_CMD_RECT_COPY);
  vmwareWriteWordToFIFO (info, sx);
  vmwareWriteWordToFIFO (info, sy);
  vmwareWriteWordToFIFO (info, dx);
  vmwareWriteWordToFIFO (info, dy);
  vmwareWriteWordToFIFO (info, width);
  vmwareWriteWordToFIFO (info, height);

  info->vmwareFIFOMarkSet = 1;

  vmwareSendSVGACmdUpdate (info, dx, dy, width, height);
}

static void
vmware_accel_clear (struct vc_data *conp, struct display *p,
                    int y, int x, int height, int width)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)p->fb_info;
  unsigned int fw, fh;
  u32 bgx = attr_bgcol_ec(p, conp);

  fw      = fontwidth(p);
  x      *= fw;
  width  *= fw;

  fh      = fontheight(p);
  y      *= fh;
  height *= fh;

  vmware_rectfill (info, x, y, width, height, bgx);
}

static void
vmware_accel_putc (struct vc_data *conp, struct display *p,
                   int c, int y, int x)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)p->fb_info;
  unsigned int fw, fh;

  fw = fontwidth(p);
  fh = fontheight(p);

  vmwareWaitForFB (info);

  info->dispsw.putc (conp, p, c, y, x);

  vmwareSendSVGACmdUpdate (info, x * fw, y * fh, fw, fh);
}

static void
vmware_accel_putcs (struct vc_data *conp, struct display *p,
                    const unsigned short *s, int count, int y, int x)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)p->fb_info;
  unsigned int fw, fh;

  fw = fontwidth(p);
  fh = fontheight(p);

  vmwareWaitForFB (info);

  info->dispsw.putcs (conp, p, s, count, y, x);

  vmwareSendSVGACmdUpdate (info, x * fw, y * fh, fw * count, fh);
}

static void
vmware_accel_revc (struct display *p, int x, int y)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)p->fb_info;
  unsigned int fw, fh;

  fw = fontwidth(p);
  fh = fontheight(p);
	
  vmwareWaitForFB (info);

  info->dispsw.revc (p, x, y);

  vmwareSendSVGACmdUpdate (info, x * fw, y * fh, fw, fh);
}

static void
vmware_accel_clear_margins (struct vc_data *conp, struct display *p,
                            int bottom_only)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)p->fb_info;

  unsigned int right_start  = conp->vc_cols * fontwidth(p);
  unsigned int bottom_start = conp->vc_rows * fontheight(p);
  unsigned int right_width, bottom_height;

  u32 bgx = attr_bgcol_ec (p, conp);

  if (!bottom_only && (right_width = p->var.xres - right_start))
    vmware_rectfill (info, right_start, 0,
                     right_width, p->var.yres_virtual, bgx);

  if ((bottom_height = p->var.yres - bottom_start))
    vmware_rectfill (info, 0, p->var.yoffset + bottom_start,
                     right_start, bottom_height, bgx);
}

static struct display_switch fbcon_vmware_accel = {
  setup:		vmware_accel_setup,
  bmove:		vmware_accel_bmove,
  clear:		vmware_accel_clear,
  putc:			vmware_accel_putc,
  putcs:		vmware_accel_putcs,
  revc:			vmware_accel_revc,
  clear_margins:	vmware_accel_clear_margins,
  fontwidthmask:	FONTWIDTH(8)|FONTWIDTH(16)
};

/* --------------------------------------------------------------------- */

/*
 *    Set a single color register. Return != 0 for invalid regno.
 */
static int
vmware_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
                 u_int transp, struct fb_info *fb)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)fb;

  if (regno >= NR_PALETTE)
    return -EINVAL;

  info->palette[regno].red    = red;
  info->palette[regno].green  = green;
  info->palette[regno].blue   = blue;
  info->palette[regno].transp = transp;

  switch (fb->var.bits_per_pixel)
    {
#ifdef FBCON_HAS_CFB8
    case 8:
      vmwareWriteReg (info, SVGA_PALETTE_BASE + regno + 0, red >> 8);
      vmwareWriteReg (info, SVGA_PALETTE_BASE + regno + 1, green >> 8);
      vmwareWriteReg (info, SVGA_PALETTE_BASE + regno + 2, blue >> 8);
      break;
#endif

#ifdef FBCON_HAS_CFB16
    case 16:
      if (regno < 16)
	((u16 *)fb->pseudo_palette)[regno] = ((red   & 0xf800)      ) |
	                                     ((green & 0xfc00) >>  5) |
                                             ((blue  & 0xf800) >> 11);
      break;
#endif

#ifdef FBCON_HAS_CFB24
    case 24:
      if (regno < 16)
	((u32 *)fb->pseudo_palette)[regno] = ((red   & 0xff00) << 8) |
                                             ((green & 0xff00)     ) |
	                                     ((blue  & 0xff00) >> 8);
      break;
#endif

#ifdef FBCON_HAS_CFB32
    case 32:
      if (regno < 16)
	((u32 *)fb->pseudo_palette)[regno] = ((transp & 0xff00) << 16) |
	                                     ((red    & 0xff00) <<  8) |
                                             ((green  & 0xff00)      ) |
	                                     ((blue   & 0xff00) >>  8);
      break;
#endif

    default:
      return 1;
    }

  return 0;
}

static void
vmwarefb_set_par (struct vmwarefb_info       *info,
                  const struct vmwarefb_par  *par)
{
  DBG("vmwarefb_set_par");

  vmwareWaitForFB (info);

  /* Write SVGA mode registers */
  vmwareWriteReg (info, SVGA_REG_WIDTH, par->svga_reg_width);
  vmwareWriteReg (info, SVGA_REG_HEIGHT, par->svga_reg_height);
  vmwareWriteReg (info, SVGA_REG_ENABLE, 1);
  vmwareWriteReg (info, SVGA_REG_CURSOR_ON, 0);
  vmwareWriteReg (info, SVGA_REG_GUEST_ID, GUEST_OS_LINUX);
}

static void
vmwarefb_update_start (struct vmwarefb_info *info, struct fb_var_screeninfo *var)
{
}

/*
 * Set the Colormap
 */
static int
vmwarefb_set_cmap(struct fb_cmap *cmap, int kspc, int con, struct fb_info *fb)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)fb;
  struct display* disp = (con < 0) ? fb->disp : (fb_display + con);
  struct fb_cmap *dcmap = &disp->cmap;
  int err = 0;

  /* no colormap allocated? */
  if (!dcmap->len)
    {
      int size;

      if (fb->var.bits_per_pixel == 8)
	size = NR_PALETTE;
      else
	size = 32;

      err = fb_alloc_cmap (dcmap, size, 0);
    }

  /*
   * we should be able to remove this test once fbcon has been
   * "improved" --rmk
   */
  if (!err && con == info->currcon)
    {
      err = fb_set_cmap (cmap, kspc, vmware_setcolreg, fb);
      dcmap = &fb->cmap;
    }

  if (!err)
    fb_copy_cmap (cmap, dcmap, kspc ? 0 : 1);

  return err;
}

static int
vmwarefb_decode_var (struct fb_var_screeninfo        *var,
                     const struct vmwarefb_info      *info,
                     struct vmwarefb_par             *par)
{
  int memlen, vramlen;
  unsigned int pixclock = var->pixclock;

  DBG("vmwarefb_decode_var");

  if (!pixclock)
    pixclock = 10000;	/* 10ns = 100MHz */

  pixclock = 1000000000 / pixclock;

  if (pixclock < 1)
    pixclock = 1;

  if (pixclock > info->maxClock)
    return -EINVAL;

  /* No double scan or interlaced support */
  if (var->vmode & (FB_VMODE_DOUBLE | FB_VMODE_INTERLACED))
    {
      printk (KERN_INFO "vmwarefb: Double scan and interlaced "
              "modes are not supported\n");
      return -EINVAL;
    }

  /* Is the mode larger than the maximum screen resolution? */
  if (var->xres > info->maxWidth || var->yres > info->maxHeight)
    {
      printk (KERN_INFO "vmwarefb: Mode (%dx%d) larger than the maximum "
              "screen resolution (%dx%d)\n", var->xres, var->yres,
              info->maxWidth, info->maxHeight);
      return -EINVAL;
    }

  /* The right bits per pixel? (TODO: Add switching support) */
  if (var->bits_per_pixel != info->hostBitsPerPixel)
    {
      printk (KERN_INFO "vmwarefb: %d bits per pixel not supported, "
              "only %d bits per pixel available for now\n",
	      var->bits_per_pixel, info->hostBitsPerPixel);
      return -EINVAL;
    }

  /* (TODO: Add virtual resolution support if possible) */
  if (var->xres_virtual != var->xres ||
      var->yres_virtual != var->yres)
    {
      printk (KERN_INFO "vmwarefb: Virtual resolutions are not supported yet\n");
      return -EINVAL;
    }

  switch (var->bits_per_pixel)
    {
#ifdef FBCON_HAS_CFB8
    case 8:
      break;
#endif

#ifdef FBCON_HAS_CFB16
    case 16:
      break;
#endif

#ifdef FBCON_HAS_CFB24
    case 24:
      break;
#endif

#ifdef FBCON_HAS_CFB32
    case 32:
      break;
#endif

    default:
      return -EINVAL;
    }

  vramlen = info->video.len;

  if (var->yres_virtual < var->yres)
    var->yres_virtual = var->yres;
  if (var->xres_virtual < var->xres)
    var->xres_virtual = var->xres;

  memlen = var->xres_virtual * var->bits_per_pixel * var->yres_virtual / 8;
  if (memlen > vramlen)
    {
      var->yres_virtual = vramlen * 8 / (var->xres_virtual * var->bits_per_pixel);
      memlen = var->xres_virtual * var->bits_per_pixel * var->yres_virtual / 8;
    }

  /* we must round yres/xres down, we already rounded y/xres_virtual up
     if it was possible. We should return -EINVAL, but I disagree */
  if (var->yres_virtual < var->yres)
    var->yres = var->yres_virtual;
  if (var->xres_virtual < var->xres)
    var->xres = var->xres_virtual;
  if (var->xoffset + var->xres > var->xres_virtual)
    var->xoffset = var->xres_virtual - var->xres;
  if (var->yoffset + var->yres > var->yres_virtual)
    var->yoffset = var->yres_virtual - var->yres;

  /*
   * SVGA mode registers
   */
  par->svga_reg_width  = var->xres;
  par->svga_reg_height = var->yres;

  return 0;
}

static int
vmwarefb_set_var (struct fb_var_screeninfo *var, int con,
                  struct fb_info *fb)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)fb;
  struct display *display;
  struct display_switch *dispsw;
  struct vmwarefb_par par;
  int err, chgvar = 0;

  DBG("vmwarefb_set_var");

  err = vmwarefb_decode_var (var, info, &par);
  if (err)
    return err;

  if (var->activate & FB_ACTIVATE_TEST)
    return 0;

  if (con < 0)
    {
      display = fb->disp;
      chgvar = 0;
    }
  else
    {
      display = fb_display + con;

      if (fb->var.xres != var->xres)
	chgvar = 1;
      if (fb->var.yres != var->yres)
	chgvar = 1;
      if (fb->var.xres_virtual != var->xres_virtual)
	chgvar = 1;
      if (fb->var.yres_virtual != var->yres_virtual)
	chgvar = 1;
      if (fb->var.bits_per_pixel != var->bits_per_pixel)
	chgvar = 1;
      if (fb->var.accel_flags != var->accel_flags)
	chgvar = 1;

      /* cannot disable acceleration in current VMware version */
      if (fb->var.accel_flags && !var->accel_flags)
        {
          printk (KERN_INFO "vmwarefb: Acceleration cannot be "
                  "disabled once it it's initialized\n");
          return -EINVAL;
        }
    }

  if ((info->capabilities & (SVGA_CAP_RECT_FILL | SVGA_CAP_RECT_COPY)) !=
      (SVGA_CAP_RECT_FILL | SVGA_CAP_RECT_COPY))
    var->accel_flags &= ~FB_ACCELF_TEXT;

  if (con == info->currcon)
    {
#if 0 /* work around X server, always reinitialize FIFO and mode */
      if (chgvar || con < 0)
        vmwarefb_set_par (info, &par);
#else
      VMWAREStopFIFO (info);
      vmwarefb_set_par (info, &par);
#endif

      vmwarefb_update_start (info, var);

      if (con < 0)
        fb_copy_cmap (fb_default_cmap (var->bits_per_pixel > 8 ? 16 : NR_PALETTE),
                      &fb->cmap, 0);

      fb_set_cmap (&fb->cmap, 1, vmware_setcolreg, fb);
    }

  var->transp.offset   = 0;
  var->transp.length   = 0;
  var->red.length      = vmwareCalculateWeight(vmwareReadReg(info, SVGA_REG_RED_MASK));
  var->green.length    = vmwareCalculateWeight(vmwareReadReg(info, SVGA_REG_GREEN_MASK));
  var->blue.length     = vmwareCalculateWeight(vmwareReadReg(info, SVGA_REG_BLUE_MASK));
  var->red.offset      = var->green.length + var->green.offset;
  var->green.offset    = var->blue.length;
  var->blue.offset     = 0;

  fb->fix.line_length  = vmwareReadReg(info, SVGA_REG_BYTES_PER_LINE);

  var->red.msb_right   = 0;
  var->green.msb_right = 0;
  var->blue.msb_right  = 0;
                                               
  switch (var->bits_per_pixel)
    {
#ifdef FBCON_HAS_CFB8
    case 8:
      var->red.length      = 8;
      var->green.length    = 8;
      var->blue.length     = 8;
      var->red.offset      = 0;
      var->green.offset    = 0;
      var->blue.offset     = 0;

      fb->fix.visual       = FB_VISUAL_PSEUDOCOLOR;
      dispsw               = &fbcon_cfb8;
      display->dispsw_data = NULL;
      break;
#endif

#ifdef FBCON_HAS_CFB16
    case 16:
      fb->fix.visual       = FB_VISUAL_TRUECOLOR;
      dispsw               = &fbcon_cfb16;
      display->dispsw_data = fb->pseudo_palette;
      break;
#endif

#ifdef FBCON_HAS_CFB24
    case 24:
      fb->fix.visual       = FB_VISUAL_TRUECOLOR;
      dispsw               = &fbcon_cfb24;
      display->dispsw_data = fb->pseudo_palette;
      break;
#endif

#ifdef FBCON_HAS_CFB32
    case 32:
      fb->fix.visual       = FB_VISUAL_TRUECOLOR;
      dispsw               = &fbcon_cfb32;
      display->dispsw_data = fb->pseudo_palette;
      break;
#endif

    default:
      printk (KERN_WARNING "vmwarefb: no support for %dbpp\n", var->bits_per_pixel);
      dispsw            = &fbcon_dummy;
      var->accel_flags &= ~FB_ACCELF_TEXT;
      break;
    }

  /* Copy default console text drawing functions */
  memcpy (&info->dispsw, dispsw, sizeof(struct display_switch));

  /* Using acceleration? */
  if (var->accel_flags & FB_ACCELF_TEXT)
    {
      VMWAREInitFIFO (info);

      vmwareSendSVGACmdUpdate (info, 0, 0, var->xres, var->yres);

      /* move content */
      if (chgvar)
        display->scrollmode = 0;

      /* use accelerated functions */
      dispsw = &fbcon_vmware_accel;
    }
  else
    {
      VMWAREStopFIFO (info);

      /* redraw content */
      display->scrollmode = SCROLL_YREDRAW;
      info->dispsw.bmove  = fbcon_redraw_bmove;

      /* use (modified) default functions */
      dispsw = &info->dispsw;
    }

  /* Setup display information */
  display->dispsw         = dispsw;
  display->screen_base    = fb->screen_base + vmwareReadReg(info, SVGA_REG_FB_OFFSET);
  display->next_line      =
  display->line_length    = fb->fix.line_length;
  display->visual         = fb->fix.visual;
  display->type	          = fb->fix.type;
  display->type_aux       = fb->fix.type_aux;
  display->ypanstep       = fb->fix.ypanstep;
  display->ywrapstep      = fb->fix.ywrapstep;
  display->can_soft_blank = 1;
  display->inverse        = 0;

  /* Update var info */
  fb->var = *var;
  fb->var.activate &= ~FB_ACTIVATE_ALL;

  /*
   * Update the old var.  The fbcon drivers still use this.
   * Once they are using cfb->fb.var, this can be dropped.
   *					--rmk
   */
  display->var = fb->var;

  /*
   * If we are setting all the virtual consoles, also set the
   * defaults used to create new consoles.
   */
  if (var->activate & FB_ACTIVATE_ALL)
    fb->disp->var = fb->var;

  if (chgvar && fb && fb->changevar)
    fb->changevar (con);

  return 0;
}

/*
 *    Pan or Wrap the Display
 */
static int
vmwarefb_pan_display (struct fb_var_screeninfo *var, int con,
                      struct fb_info *fb)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)fb;
  u_int y_bottom;

  y_bottom = var->yoffset;

  if (!(var->vmode & FB_VMODE_YWRAP))
    y_bottom += var->yres;

  if (var->xoffset > (var->xres_virtual - var->xres))
    return -EINVAL;
  if (y_bottom > fb->var.yres_virtual)
    return -EINVAL;

  vmwarefb_update_start (info, var);

  fb->var.xoffset = var->xoffset;
  fb->var.yoffset = var->yoffset;

  if (var->vmode & FB_VMODE_YWRAP)
    fb->var.vmode |= FB_VMODE_YWRAP;
  else
    fb->var.vmode &= ~FB_VMODE_YWRAP;

  return 0;
}


/*
 *    Update the `var' structure (called by fbcon.c)
 *
 *    This call looks only at yoffset and the FB_VMODE_YWRAP flag in `var'.
 *    Since it's called by a kernel driver, no range checking is done.
 */
static int
vmwarefb_updatevar (int con, struct fb_info *fb)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)fb;

  vmwarefb_update_start (info, &fb_display[con].var);

  return 0;
}

static int
vmwarefb_switch (int con, struct fb_info *fb)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)fb;
  struct display *disp;
  struct fb_cmap *cmap;

  if (info->currcon >= 0)
    {
      disp = fb_display + info->currcon;

      /*
       * Save the old colormap and video mode.
       */
      disp->var = fb->var;
      if (disp->cmap.len)
	fb_copy_cmap(&fb->cmap, &disp->cmap, 0);
    }

  info->currcon = con;
  disp = fb_display + con;

  /*
   * Install the new colormap and change the video mode.  By default,
   * fbcon sets all the colormaps and video modes to the default
   * values at bootup.
   *
   * Really, we want to set the colourmap size depending on the
   * depth of the new video mode.  For now, we leave it at its
   * default 256 entry.
   */
  if (disp->cmap.len)
    cmap = &disp->cmap;
  else
    cmap = fb_default_cmap(disp->var.bits_per_pixel > 8 ? 16 : NR_PALETTE);

  fb_copy_cmap(cmap, &fb->cmap, 0);

  disp->var.activate = FB_ACTIVATE_NOW;
  vmwarefb_set_var(&disp->var, con, fb);

  return 0;
}

/*
 *    (Un)Blank the display.
 */
static void
vmwarefb_blank (int blank, struct fb_info *fb)
{
  //  struct vmwarefb_info *info = (struct vmwarefb_info *)fb;

  /*
   *  Blank the screen if blank_mode != 0, else unblank. If
   *  blank == NULL then the caller blanks by setting the CLUT
   *  (Color Look Up Table) to all black. Return 0 if blanking
   *  succeeded, != 0 if un-/blanking failed due to e.g. a
   *  video mode which doesn't support it. Implements VESA
   *  suspend and powerdown modes on hardware that supports
   *  disabling hsync/vsync:
   *    blank_mode == 2: suspend vsync
   *    blank_mode == 3: suspend hsync
   *    blank_mode == 4: powerdown
   *
   *  wms...Enable VESA DMPS compatible powerdown mode
   *  run "setterm -powersave powerdown" to take advantage
   */
     
  switch (blank)
    {
    case 4:	/* powerdown - both sync lines down */
      break;	
    case 3:	/* hsync off */
      break;	
    case 2:	/* vsync off */
      break;	
    case 1:	/* just software blanking of screen */
      break;
    default: /* case 0, or anything else: unblank */
      break;
    }
}

/*
 * Get the currently displayed virtual consoles colormap.
 */
static int
gen_get_cmap (struct fb_cmap *cmap, int kspc, int con, struct fb_info *fb)
{
  fb_copy_cmap (&fb->cmap, cmap, kspc ? 0 : 2);
  return 0;
}

/*
 * Get the currently displayed virtual consoles fixed part of the display.
 */
static int
gen_get_fix (struct fb_fix_screeninfo *fix, int con, struct fb_info *fb)
{
  *fix = fb->fix;
  return 0;
}

/*
 * Get the current user defined part of the display.
 */
static int
gen_get_var (struct fb_var_screeninfo *var, int con, struct fb_info *fb)
{
  *var = fb->var;
  return 0;
}

static struct fb_ops vmwarefb_ops = {
  owner:          THIS_MODULE,
  fb_set_var:     vmwarefb_set_var,
  fb_set_cmap:    vmwarefb_set_cmap,
  fb_pan_display: vmwarefb_pan_display,
  fb_get_fix:     gen_get_fix,
  fb_get_var:     gen_get_var,
  fb_get_cmap:    gen_get_cmap,
};

/* --------------------------------------------------------------------- */

static int __devinit
vmware_map_mmio (struct vmwarefb_info *info)
{
  DBG("vmware_map_mmio");

  info->mmio.pbase = vmwareReadReg (info, SVGA_REG_MEM_START);
  info->mmio.len   = vmwareReadReg (info, SVGA_REG_MEM_SIZE) & ~3;

  if (!request_mem_region (info->mmio.pbase, info->mmio.len, "memory mapped I/O"))
    {
      printk (KERN_ERR "vmwarefb: memory mapped IO at 0x%08x in use\n",
              info->mmio.pbase);
      return -EBUSY;
    }

  info->mmio.vbase = ioremap (info->mmio.pbase, info->mmio.len);
  if (!info->mmio.vbase)
    {
      printk (KERN_ERR "vmwarefb: unable to map memory mapped IO at 0x%08x\n",
              info->mmio.pbase);
      release_mem_region (info->mmio.pbase, info->mmio.len);
      return -ENOMEM;
    }
  else
    printk (KERN_INFO "vmwarefb: mapped io at 0x%08x -> %p\n",
            info->mmio.pbase, info->mmio.vbase);

  info->fb.fix.mmio_start = info->mmio.pbase;
  info->fb.fix.mmio_len   = info->mmio.len;

  return 0;
}

static void __devexit
vmware_unmap_mmio (struct vmwarefb_info *info)
{
  DBG("vmware_unmap_mmio");

  VMWAREStopFIFO (info);

  if (info->mmio.vbase)
    {
      iounmap (info->mmio.vbase);
      info->mmio.vbase = NULL;

      release_mem_region (info->mmio.pbase, info->mmio.len);
    }
}

static int __devinit
vmware_map_video (struct vmwarefb_info *info)
{
  DBG("vmware_map_video");

  info->video.pbase = vmwareReadReg (info, SVGA_REG_FB_START);
  info->video.len   = vmwareReadReg (info, SVGA_REG_FB_MAX_SIZE);

  if (!request_mem_region (info->video.pbase, info->video.len, "frame buffer"))
    {
      printk (KERN_ERR "vmwarefb: frame buffer at 0x%08x in use\n",
              info->video.pbase);
      return -EBUSY;
    }

  info->video.vbase = ioremap (info->video.pbase, info->video.len);
  if (!info->video.vbase)
    {
      printk (KERN_ERR "vmwarefb: unable to map screen memory at 0x%08x\n",
              info->video.pbase);
      release_mem_region (info->video.pbase, info->video.len);
      return -ENOMEM;
    }
  else
    printk (KERN_INFO "vmwarefb: mapped framebuffer at 0x%08x -> %p\n",
            info->video.pbase, info->video.vbase);

  info->fb.fix.smem_start = info->video.pbase;
  info->fb.fix.smem_len   = info->video.len;
  info->fb.screen_base    = info->video.vbase;

  return 0;
}

static void __devexit
vmware_unmap_video (struct vmwarefb_info *info)
{
  DBG("vmware_unmap_video");

  if (info->video.vbase)
    {
      iounmap (info->video.vbase);
      info->video.vbase = NULL;
      info->fb.screen_base = NULL;

      release_mem_region (info->video.pbase, info->video.len);
    }
}

static int __devinit
vmware_init_hw (struct vmwarefb_info *info)
{
  u32 id;

  DBG("vmware_init_hw");

  /* Figure out SVGA ports */
  switch (info->accel)
    {
    case FB_ACCEL_VMWARE_SVGA:
      info->indexReg = SVGA_LEGACY_BASE_PORT + SVGA_INDEX_PORT * sizeof(u32);
      info->valueReg = SVGA_LEGACY_BASE_PORT + SVGA_VALUE_PORT * sizeof(u32);
      break;

    case FB_ACCEL_VMWARE_SVGA2:
      info->indexReg = pci_resource_start (info->pcidev, 0) + SVGA_INDEX_PORT;
      info->valueReg = pci_resource_start (info->pcidev, 0) + SVGA_VALUE_PORT;
      break;
    }

  /* Check for supported SVGA id */
  id = VMXGetVMwareSvgaId (info);
  if (id == SVGA_ID_0 || id == SVGA_ID_INVALID)
    {
      printk (KERN_ERR "vmwarefb: No supported "
              "VMware SVGA found (read ID 0x%08x).\n", id);
      return -ENOTSUPP;
    }

  /* Maximum dot clock in kHz */
  info->maxClock = 400000;

  /* Maximum screen resolution */
  info->maxWidth  = vmwareReadReg (info, SVGA_REG_MAX_WIDTH);
  info->maxHeight = vmwareReadReg (info, SVGA_REG_MAX_HEIGHT);

  /* Bits per pixel in host format */
  info->hostBitsPerPixel = vmwareReadReg (info, SVGA_REG_HOST_BITS_PER_PIXEL);

  /* Capabilities like 8bit emulation */
  info->capabilities = vmwareReadReg (info, SVGA_REG_CAPABILITIES);

  printk (KERN_INFO
          "vmwarefb: Maximum screen resolution is %dx%d, host bpp is %d\n",
	  info->maxWidth, info->maxHeight, info->hostBitsPerPixel);

  return 0;
}


static struct vmwarefb_info * __devinit
vmware_alloc_fb_info (struct pci_dev             *dev,
                      const struct pci_device_id *id)
{
  struct vmwarefb_info *info;

  info = kmalloc (sizeof(struct vmwarefb_info) + sizeof(struct display) +
		  sizeof(u32) * 16, GFP_KERNEL);

  if (!info)
    return NULL;

  memset (info, 0, sizeof(struct vmwarefb_info) + sizeof(struct display));

  info->currcon = -1;
  info->pcidev  = dev;
  info->accel   = id->driver_data;

  switch (info->accel)
    {
    case FB_ACCEL_VMWARE_SVGA:
      sprintf (info->fb.fix.id, "VMware SVGA");
      break;

    case FB_ACCEL_VMWARE_SVGA2:
      sprintf (info->fb.fix.id, "VMware SVGA2");
      break;
    }

  info->fb.fix.type	   = FB_TYPE_PACKED_PIXELS;
  info->fb.fix.type_aux	   = 0;
  info->fb.fix.xpanstep	   = 0;
  info->fb.fix.ypanstep	   = 0; /* No virtual resolution support yet */
  info->fb.fix.ywrapstep   = 0;
  info->fb.fix.accel       = id->driver_data;

  info->fb.var.nonstd      = 0;
  info->fb.var.activate    = FB_ACTIVATE_NOW;
  info->fb.var.height      = -1;
  info->fb.var.width       = -1;
  info->fb.var.accel_flags = 0;

  strcpy (info->fb.modename, info->fb.fix.id);

  info->fb.fbops          = &vmwarefb_ops;
  info->fb.changevar      = NULL;
  info->fb.switch_con     = vmwarefb_switch;
  info->fb.updatevar      = vmwarefb_updatevar;
  info->fb.blank          = vmwarefb_blank;
  info->fb.flags          = FBINFO_FLAG_DEFAULT;
  info->fb.disp           = (struct display *)(info + 1);
  info->fb.pseudo_palette = (void *)(info->fb.disp + 1);

  fb_alloc_cmap (&info->fb.cmap, NR_PALETTE, 0);

  return info;
}

static void __devexit
vmware_free_fb_info (struct vmwarefb_info *info)
{
  if (info)
    {
      /*
       * Free the colourmap
       */
      fb_alloc_cmap (&info->fb.cmap, 0, 0);

      kfree (info);
    }
}

/* --------------------------------------------------------------------- */

static int __devinit
vmwarefb_probe (struct pci_dev* dev, const struct pci_device_id* id)
{
  struct vmwarefb_info *info;
  u_int h_sync, v_sync;
  int err;

  DBG("vmwarefb_probe");

  err = pci_enable_device (dev);
  if (err)
    return err;

  err = -ENOMEM;
  info = vmware_alloc_fb_info (dev, id);
  if (!info)
    goto failed;

  err = vmware_init_hw (info);
  if (err)
    goto failed;

  err = vmware_map_mmio (info);
  if (err)
    goto failed;

  err = vmware_map_video (info);
  if (err)
    goto failed;

  /* Use bits per pixel of host */
  vmwarefb_var->bits_per_pixel = info->hostBitsPerPixel;

  /* Use acceleration? */
  if (!noaccel)
    vmwarefb_var->accel_flags = FB_ACCELF_TEXT;

  /* Set initial mode */
  vmwarefb_set_var (vmwarefb_var, -1, &info->fb);

  /*
   * Calculate the hsync and vsync frequencies.  Note that
   * we split the 1e12 constant up so that we can preserve
   * the precision and fit the results into 32-bit registers.
   *  (1953125000 * 512 = 1e12)
   */
  h_sync = 1953125000 / info->fb.var.pixclock;
  h_sync = h_sync * 512 / (info->fb.var.xres + info->fb.var.left_margin +
			   info->fb.var.right_margin + info->fb.var.hsync_len);
  v_sync = h_sync / (info->fb.var.yres + info->fb.var.upper_margin +
		     info->fb.var.lower_margin + info->fb.var.vsync_len);

  printk(KERN_INFO "vmwarefb v" VMWAREFB_VERSION ": "
         "%dkB VRAM, using %dx%d, %d.%03dkHz, %dHz\n",
	 info->fb.fix.smem_len >> 10,
	 info->fb.var.xres, info->fb.var.yres,
	 h_sync / 1000, h_sync % 1000, v_sync);


  err = register_framebuffer (&info->fb);
  if (err < 0)
    goto failed;

  printk (KERN_INFO "fb%d: %s frame buffer device\n",
	  GET_FB_IDX(info->fb.node), info->fb.modename);

  /*
   * Our driver data
   */
  dev->driver_data = info;

  return 0;

failed:
  vmware_unmap_video (info);
  vmware_unmap_mmio (info);
  vmware_free_fb_info (info);

  return err;
}

static void __devexit
vmwarefb_remove (struct pci_dev *dev)
{
  struct vmwarefb_info *info = (struct vmwarefb_info *)dev->driver_data;

  DBG("vmwarefb_remove");

  if (info)
    {
      /*
       * If unregister_framebuffer fails, then
       * we will be leaving hooks that could cause
       * oopsen laying around.
       */
      if (unregister_framebuffer (&info->fb))
	printk (KERN_WARNING "vmwarefb: danger danger!  Oopsen imminent!\n");

      vmware_unmap_video (info);
      vmware_unmap_mmio (info);
      vmware_free_fb_info (info);

      /*
       * Ensure that the driver data is no longer
       * valid.
       */
      dev->driver_data = NULL;
    }
}

static struct pci_device_id vmwarefb_devices[] __devinitdata = {
  {PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_SVGA,
   PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_VMWARE_SVGA},

  {PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_SVGA2,
   PCI_ANY_ID, PCI_ANY_ID, 0, 0, FB_ACCEL_VMWARE_SVGA2},

  {0, 0, 0, 0, 0, 0, 0}
};

MODULE_DEVICE_TABLE(pci, vmwarefb_devices);

static struct pci_driver vmwarefb_driver = {
  name:      "vmwarefb",
  id_table:  vmwarefb_devices,
  probe:     vmwarefb_probe,
  remove:    vmwarefb_remove
};

/* **************************** init-time only **************************** */

static void __init
vmware_init (void)
{
  DBG("vmware_init");
  pci_register_driver (&vmwarefb_driver);
}

/* **************************** exit-time only **************************** */

static void __exit
vmware_done (void)
{
  DBG("vmware_done");
  pci_unregister_driver (&vmwarefb_driver);
}


/* ************************* init in-kernel code ************************** */

#ifndef MODULE

static int initialized = 0;

int __init
vmwarefb_setup (char *options)
{
  char *this_opt;

  DBG("vmwarefb_setup");

  if (!options || !*options)
    return 0;

  for (this_opt=strtok(options,","); this_opt; this_opt=strtok(NULL,","))
    {
      if (!*this_opt) continue;

      if (!strncmp(this_opt, "disabled", 8))
	disabled = 1;
      if (!strncmp(this_opt, "noaccel", 8))
	noaccel = 1;
      if (!strncmp(this_opt, "640x480", 7))
	vmwarefb_var = &vmwarefb_var640x480x8;
      if (!strncmp(this_opt, "800x600", 7))
	vmwarefb_var = &vmwarefb_var800x600x8;
      if (!strncmp(this_opt, "1024x768", 8))
	vmwarefb_var = &vmwarefb_var1024x768x8;
    }

  return 0;
}

int __init
vmwarefb_init(void)
{
  DBG("vmwarefb_init");

  if (disabled)
    return -ENXIO;

  if (!initialized)
    {
      initialized = 1;
      vmware_init();
    }

  /* never return failure, user can hotplug card later... */
  return 0;
}

#else

/* *************************** init module code **************************** */

int __init
init_module(void)
{
  DBG("init_module");

  if (disabled)
    return -ENXIO;

  vmware_init();

  /* never return failure; user can hotplug card later... */
  return 0;
}

#endif	/* MODULE */

module_exit(vmware_done);
