#include <u.h>
#include <libc.h>
#include <bio.h>

#include "pci.h"
#include "vga.h"

enum {
       VMWARE1         = 0x0710,       /* PCI DID */
       VMWARE2         = 0x0405,
};

enum {
       Rid = 0,
       Renable,
       Rwidth,
       Rheight,
       Rmaxwidth,
       Rmaxheight,
       Rdepth,
       Rbpp,
       Rpseudocolor,
       Rrmask,
       Rgmask,
       Rbmask,
       Rbpl,
       Rfbstart,
       Rfboffset,
       Rfbmaxsize,
       Rfbsize,
       Rcap,
       Rmemstart,
       Rmemsize,
       Rconfigdone,
       Rsync,
       Rbusy,
       Rguestid,
       Rcursorid,
       Rcursorx,
       Rcursory,
       Rcursoron,
       Rhostbpp,
       Nreg,

       Rpalette = 1024,
};

typedef struct Vmware   Vmware;
struct Vmware {
       ulong   ra;
       ulong   rd;

       ulong   r[Nreg];

       char    chan[32];
       int     depth;
       int     ver;
};

static char*
rname[Nreg] = {
       "ID",
       "Enable",
       "Width",
       "Height",
       "MaxWidth",
       "MaxHeight",
       "Depth",
       "Bpp",
       "PseudoColor",
       "RedMask",
       "GreenMask",
       "BlueMask",
       "Bpl",
       "FbStart",
       "FbOffset",
       "FbMaxSize",
       "FbSize",
       "Cap",
       "MemStart",
       "MemSize",
       "ConfigDone",
       "Sync",
       "Busy",
       "GuestID",
       "CursorID",
       "CursorX",
       "CursorY",
       "CursorOn",
       "HostBpp",
};

static ulong
vmrd(Vmware *vm, int i)
{
       outportl(vm->ra, i);
       return inportl(vm->rd);
}

static void
vmwr(Vmware *vm, int i, ulong v)
{
       outportl(vm->ra, i);
       outportl(vm->rd, v);
}

static uint
bits(ulong a)
{
       int b;

       for(b=0; a; a>>=1)
               if(a&1)
                       b++;
       return b;
}

static void
snarf(Vga* vga, Ctlr* ctlr)
{
       int extra, i;
       Pcidev *p;
       Vmware *vm;

       p = vga->pci;
       if(p == nil)
               error("%s: vga->pci not set\n", ctlr->name);
       vm = alloc(sizeof(Vmware));
       switch(p->did){
       case VMWARE1:   /* VMware video chipset #1 */
               vm->ver = 1;
               vm->ra = 0x4560;
               vm->rd = 0x4560+4;
               break;

       case VMWARE2:   /* VMware video chipset #2 */
               vm->ver = 2;
               vm->ra = p->mem[0].bar&~3;
               vm->rd = vm->ra+1;
               break;

       default:
               error("%s: unrecognized DID %.4ux\n", ctlr->name, p->did);
       }
       vgactlpci(p);

       for(i=0; i<Nreg; i++)
               vm->r[i] = vmrd(vm, i);

//vmwr(vm, Renable, 0);
       /*
        * Figure out color channel.  Delay errors until init,
        * which is after the register dump.
        */
       vm->depth = vm->r[Rbpp];
       extra = vm->r[Rbpp] - vm->r[Rdepth];
       if(vm->r[Rrmask] > vm->r[Rgmask] && vm->r[Rgmask] > vm->r[Rbmask]){
               if(extra)
                       sprint(vm->chan, "x%d", extra);
               else
                       vm->chan[0] = '\0';
               sprint(vm->chan+strlen(vm->chan), "r%dg%db%d", bits(vm->r[Rrmask]),
                       bits(vm->r[Rgmask]), bits(vm->r[Rbmask]));
       }else if(vm->r[Rbmask] > vm->r[Rgmask] && vm->r[Rgmask] > vm->r[Rrmask]){
               sprint(vm->chan, "b%dg%dr%d", bits(vm->r[Rbmask]),
                       bits(vm->r[Rgmask]), bits(vm->r[Rrmask]));
               if(extra)
                       sprint(vm->chan+strlen(vm->chan), "x%d", extra);
       }else
               sprint(vm->chan, "unknown");

       /* Record the frame buffer start, size */
       // vga->vmb = vm->r[Rfstart];
       vga->vmb = p->mem[1].bar & ~0xF;
       vga->vmb += vm->r[Rfboffset];
       vga->apz = vm->r[Rfbmaxsize];

       vga->private = vm;
       ctlr->flag |= Fsnarf;
}


static void
options(Vga*, Ctlr* ctlr)
{
       ctlr->flag |= Hlinear|Henhanced|Foptions;
}


static void
clock(Vga*, Ctlr*)
{
       /* BEST CLOCK ROUTINE EVER! */
}

static void
init(Vga* vga, Ctlr* ctlr)
{
       Vmware *vm;

       vm = vga->private;

       vmwr(vm, Rid, (0x900000<<8)|2);
       if(vmrd(vm, Rid)&0xFF != 2)
               error("old vmware svga version %lud; need version 2\n",
                       vmrd(vm,Rid)&0xFF);

       ctlr->flag |= Ulinear;
       if(strcmp(vm->chan, "unknown") == 0)
               error("couldn't translate color masks into channel\n");

       /* Always use the screen depth, and clip the screen size */
       vga->mode->z = vm->r[Rbpp];
       if(vga->mode->x > vm->r[Rmaxwidth])
               vga->mode->x = vm->r[Rmaxwidth];
       if(vga->mode->y > vm->r[Rmaxheight])
               vga->mode->y = vm->r[Rmaxheight];

       vm->r[Rwidth] = vga->mode->x;
       vm->r[Rheight] = vga->mode->y;

       /* Figure out the channel string */
       strcpy(vga->mode->chan, vm->chan);

       /* Record the bytes per line */
       ctlr->flag |= Finit;
}

static void
load(Vga* vga, Ctlr *ctlr)
{
       char buf[64];
       int x;
       Vmware *vm;

       vm = vga->private;
       vmwr(vm, Rwidth, vm->r[Rwidth]);
       vmwr(vm, Rheight, vm->r[Rheight]);
       vmwr(vm, Renable, 1);
       vmwr(vm, Rguestid, 0x5010);     /* OS type is "Other" */

       x = vmrd(vm, Rbpl)/(vm->depth/8);
       if(x != vga->mode->x){
               vga->virtx = x;
               sprint(buf, "%ludx%ludx%d %s", vga->virtx, vga->virty,
                       vga->mode->z, vga->mode->chan);
               vgactlw("size", buf);
       }
       ctlr->flag |= Fload;
}

static void
dump(Vga* vga, Ctlr* ctlr)
{
       int i;
       Vmware *vm;

       vm = vga->private;

       for(i=0; i<Nreg; i++){
               printitem(ctlr->name, rname[i]);
               Bprint(&stdout, " %.8lux\n", vm->r[i]);
       }

       printitem(ctlr->name, "chan");
       Bprint(&stdout, " %s\n", vm->chan);
       printitem(ctlr->name, "depth");
       Bprint(&stdout, " %d\n", vm->depth);
       printitem(ctlr->name, "linear");

}

Ctlr vmware = {
       "vmware",                       /* name */
       snarf,                          /* snarf */
       options,                        /* options */
       init,                           /* init */
       load,                           /* load */
       dump,                           /* dump */
};

Ctlr vmwarehwgc = {
       "vmwarehwgc",                   /* name */
       0,                              /* snarf */
       0,                              /* options */
       0,                              /* init */
       0,                              /* load */
       0,                              /* dump */
};