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

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

Biobuf stdout;

static int iflag, lflag, pflag, rflag;

static char *dbname = "/lib/vgadb";
static char monitordb[128];

static void
dump(Vga* vga)
{
       Ctlr *ctlr;
       Attr *attr;
       int i;

       if(vga->mode)
               dbdumpmode(vga->mode);

       for(attr = vga->attr; attr; attr = attr->next)
               Bprint(&stdout, "vga->attr: %s=%s\n", attr->attr, attr->val);

       for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
               if(ctlr->dump == 0)
                       continue;

               trace("%s->dump\n", ctlr->name);
               if(ctlr->flag && ctlr->flag != Fsnarf){
                       printitem(ctlr->name, "flag");
                       printflag(ctlr->flag);
                       Bprint(&stdout, "\n");
               }
               (*ctlr->dump)(vga, ctlr);
               ctlr->flag |= Fdump;
       }

       for(i=0; i < nelem(vga->edid); i++){
               if(vga->edid[i])
                       printedid(vga->edid[i]);
       }

       Bprint(&stdout, "\n");
}

void
resyncinit(Vga* vga, Ctlr* ctlr, ulong on, ulong off)
{
       Ctlr *link;

       trace("%s->resyncinit on 0x%8.8luX off 0x%8.8luX\n",
               ctlr->name, on, off);

       for(link = vga->link; link; link = link->link){
               link->flag |= on;
               link->flag &= ~off;
               if(link == ctlr)
                       continue;

               if(link->init == 0 || (link->flag & Finit) == 0)
                       continue;
               link->flag &= ~Finit;
               trace("%s->init 0x%8.8luX\n", link->name, link->flag);
               (*link->init)(vga, link);
       }
}

void
sequencer(Vga* vga, int on)
{
       static uchar seq01;
       static int state = 1;
       char *s;

       if(on)
               s = "on";
       else
               s = "off";
       trace("sequencer->enter %s\n", s);
       if(on){
               if(vga)
                       seq01 = vga->sequencer[0x01];
               if(state == 0){
                       seq01 |= 0x01;
                       vgaxo(Seqx, 0x01, seq01);
                       vgaxo(Seqx, 0x00, 0x03);
               }
       }
       else{
               vgaxo(Seqx, 0x00, 0x01);
               seq01 = vgaxi(Seqx, 0x01);
               vgaxo(Seqx, 0x01, seq01|0x20);
       }
       state = on;
       trace("sequencer->leave %s\n", s);
}

static void
linear(Vga* vga)
{
       char buf[256];
       char *p;

       /*
        * Set up for linear addressing: try to allocate the
        * kernel memory map then read the base-address back.
        * vga->linear is a compatibility hack.
        */
       if(vga->linear == 0){
               vga->ctlr->flag &= ~Ulinear;
               return;
       }
       if(vga->ctlr->flag & Ulinear){
               /*
                * If there's already an aperture don't bother trying
                * to set up a new one.
                */
               vgactlr("addr", buf);
               if(strtoul(buf, 0, 0)==0 && (buf[0]!='p' || buf[1]!=' ' || strtoul(buf+2, 0, 0)==0)){
                       sprint(buf, "0x%lux 0x%lux", vga->apz ? vga->apz : vga->vmz, vga->vma);
                       vgactlw("linear", buf);
                       vgactlr("addr", buf);
               }
               trace("linear->addr %s\n", buf);
               /*
                * old: addr 0x12345678
                * new: addr p 0x12345678 v 0x82345678 size 0x123
                */
               if(buf[0]=='p' && buf[1]==' '){
                       vga->vmb = strtoul(buf+2, 0, 0);
                       p = strstr(buf, "size");
                       if(p)
                               vga->apz = strtoul(p+4, 0, 0);
               }else
                       vga->vmb = strtoul(buf, 0, 0);
       }
       else
               vgactlw("linear", "0");
}

static Mode*
dbedidmode(Vga *vga, char *type, char *size)
{
       char buf[32], *p;
       int i, x, y, z;
       Modelist *l;
       Mode *m;

       z = 32;
       x = y = 0;
       snprint(buf, sizeof(buf), "%s", size);
       if((p = strchr(buf, 'x')) != nil){
               *p++ = 0;
               x = atoi(buf);
               y = atoi(p);
               if((p = strchr(p, 'x')) != nil){
                       *p++ = 0;
                       z = atoi(p);
               }
       }

       for(i=0; i<nelem(vga->edid); i++){
               if(vga->edid[i] == nil)
                       continue;
               for(l = vga->edid[i]->modelist; l != nil; l = l->next)
                       if(strcmp(l->name, type) == 0)
                               goto found;
       }
       for(i=0; i<nelem(vga->edid); i++){
               if(vga->edid[i] == nil)
                       continue;
               for(l = vga->edid[i]->modelist; l != nil; l = l->next)
                       if((x == 0 || l->x == x) && (y == 0 || l->y == y))
                               goto found;
       }
       return nil;

found:
       m = alloc(sizeof(Mode));
       *m = *((Mode*)l);
       m->z = z;
       x = m->x;
       y = m->y;
       snprint(m->type, sizeof(m->type), "%s", type);
       snprint(m->size, sizeof(m->size), "%dx%dx%d", x, y, z);
       return m;
}

char*
chanstr[32+1] = {
[1]     "k1",
[2]     "k2",
[4]     "k4",
[8]     "m8",
[16]    "r5g6b5",
[24]    "r8g8b8",
[32]    "x8r8g8b8",
};

static void
usage(void)
{
       fprint(2, "usage: aux/vga [ -BcdilpvV ] [ -b bios-id ] [ -m monitor ] [ -x db ] [ mode [ virtualsize ] ]\n");
       exits("usage");
}

void
main(int argc, char** argv)
{
       char *bios, buf[256], sizeb[256], *p, *vsize, *psize;
       char *type, *vtype;
       int virtual, len;
       Ctlr *ctlr;
       Vga *vga;

       fmtinstall('H', encodefmt);
       Binit(&stdout, 1, OWRITE);

       bios = getenv("vgactlr");
       if((type = getenv("monitor")) == 0)
               type = "vga";
       psize = vsize = "640x480x8";

       ARGBEGIN{
       default:
               usage();
               break;
       case 'b':
               bios = EARGF(usage());
               break;
       case 'B':
               dumpbios(0x10000);
               exits(0);
       case 'c':
               cflag = 1;
               break;
       case 'd':
               dflag = 1;
               break;
       case 'i':
               iflag = 1;
               break;
       case 'l':
               lflag = 1;
               break;
       case 'm':
               type = EARGF(usage());
               break;
       case 'p':
               pflag = 1;
               break;
       case 'r':
               /*
                * rflag > 1 means "leave me alone, I know what I'm doing."
                */
               rflag++;
               break;
       case 'v':
               vflag = 1;
               break;
       case 'V':
               vflag = 1;
               Vflag = 1;
               break;
       case 'x':
               dbname = EARGF(usage());
               break;
       }ARGEND

       virtual = 0;
       switch(argc){
       default:
               usage();
               break;
       case 1:
               vsize = psize = argv[0];
               break;
       case 2:
               psize = argv[0];
               vsize = argv[1];
               virtual = 1;
               break;
       case 0:
               break;
       }

       if(lflag && strcmp(vsize, "text") == 0){
               vesatextmode();
               vgactlw("textmode", "");
               exits(0);
       }

       vga = alloc(sizeof(Vga));
       if(bios){
               if((vga->offset = strtol(bios, &p, 0)) == 0 || *p++ != '=')
                       error("main: bad BIOS string format - %s\n", bios);
               len = strlen(p);
               vga->bios = alloc(len+1);
               strncpy(vga->bios, p, len);
               trace("main->BIOS %s\n", bios);
       }

       /*
        * Try to identify the VGA card and grab
        * registers.  Print them out if requested.
        * If monitor=vesa or our vga controller can't be found
        * in vgadb, try vesa modes; failing that, try vga.
        */
       if(strcmp(type, "vesa") == 0 || dbctlr(dbname, vga) == 0 ||
           vga->ctlr == 0)
               if(dbvesa(vga) == 0 || vga->ctlr == 0){
                       Bprint(&stdout, "%s: controller not in %s, not vesa\n",
                               argv0, dbname);
                       dumpbios(256);
                       type = "vga";
                       vsize = psize = "640x480x1";
                       virtual = 0;
                       vga->ctlr = &generic;
                       vga->link = &generic;
               }

       trace("main->snarf\n");
       for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
               if(ctlr->snarf == 0)
                       continue;
               trace("%s->snarf\n", ctlr->name);
               (*ctlr->snarf)(vga, ctlr);
       }

       if(pflag)
               dump(vga);

       for(ctlr = vga->link; ctlr; ctlr = ctlr->link)
               if(ctlr->flag & Ferror)
                       error("%r\n");

       if(iflag || lflag){
               if(getenv(type))
                       snprint(monitordb, sizeof monitordb, "/env/%s", type);
               else
                       strecpy(monitordb, monitordb+sizeof monitordb, dbname);

               if(vga->vesa){
                       strcpy(monitordb, "vesa bios");
                       vga->mode = dbvesamode(vga, psize);
               }else {
                       vga->mode = dbmode(monitordb, type, psize);
                       if(vga->mode == nil)
                               vga->mode = dbedidmode(vga, type, psize);
               }
               if(vga->mode == nil)
                       error("main: %s@%s not in %s\n", type, psize, monitordb);

               /*
                * because vga programs shb/ehb (Crt02, Crt03) the same as
                * shs/ehs (Crt04, Crt05), only shb/ehb is specified in vgadb
                * meaning the horizontal sync pulse start and end position.
                */
               if(vga->mode->shs == 0)
                       vga->mode->shs = vga->mode->shb;
               if(vga->mode->ehs == 0)
                       vga->mode->ehs = vga->mode->ehb;

               if(virtual){
                       if((p = strchr(vsize, 'x')) == nil)
                               error("bad virtual size %s\n", vsize);
                       vga->virtx = atoi(vsize);
                       vga->virty = atoi(p+1);
                       if(vga->virtx < vga->mode->x || vga->virty < vga->mode->y)
                               error("virtual size smaller than physical size\n");
                       vga->panning = 1;
               }
               else{
                       vga->virtx = vga->mode->x;
                       vga->virty = vga->mode->y;
                       vga->panning = 0;
               }

               trace("vmf %d vmdf %d vf1 %lud vbw %lud\n",
                       vga->mode->frequency, vga->mode->deffrequency,
                       vga->f[1], vga->mode->videobw);
               if(vga->mode->frequency == 0 && vga->mode->videobw != 0 && vga->f[1] != 0){
                       /*
                        * boost clock as much as possible subject
                        * to video and memory bandwidth constraints
                        */
                       ulong bytes, freq, membw;
                       double rr;

                       freq = vga->mode->videobw;
                       if(freq > vga->f[1])
                               freq = vga->f[1];

                       rr = (double)freq/(vga->mode->ht*vga->mode->vt);
                       if(rr > 85.0)           /* >85Hz is ridiculous */
                               rr = 85.0;

                       bytes = (vga->mode->x*vga->mode->y*vga->mode->z)/8;
                       membw = rr*bytes;
                       if(vga->membw != 0 && membw > vga->membw){
                               membw = vga->membw;
                               rr = (double)membw/bytes;
                       }

                       freq = rr*(vga->mode->ht*vga->mode->vt);
                       vga->mode->frequency = freq;

                       trace("using frequency %lud rr %.2f membw %lud\n",
                               freq, rr, membw);
               }
               else if(vga->mode->frequency == 0)
                       vga->mode->frequency = vga->mode->deffrequency;

               for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
                       if(ctlr->options == 0)
                               continue;
                       trace("%s->options\n", ctlr->name);
                       (*ctlr->options)(vga, ctlr);
               }

               /*
                * skip init for vesa - vesa will do the registers for us
                */
               if(!vga->vesa)
               for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
                       if(ctlr->init == 0)
                               continue;
                       trace("%s->init\n", ctlr->name);
                       (*ctlr->init)(vga, ctlr);
               }

               if(strcmp(vga->mode->chan, "") == 0){
                       if(vga->mode->z < nelem(chanstr) && chanstr[vga->mode->z])
                               strcpy(vga->mode->chan, chanstr[vga->mode->z]);
                       else
                               error("%s: unknown channel type to use for depth %d\n", vga->ctlr->name, vga->mode->z);
               }

               if(iflag || pflag)
                       dump(vga);

               if(lflag){
                       trace("main->load\n");
                       if(vga->vmz && (vga->virtx*vga->virty*vga->mode->z)/8 > vga->vmz)
                               error("%s: not enough video memory - %lud\n",
                                       vga->ctlr->name, vga->vmz);

                       if(vga->ctlr->type)
                               vtype = vga->ctlr->type;
                       else if(p = strchr(vga->ctlr->name, '-')){
                               strncpy(buf, vga->ctlr->name, p - vga->ctlr->name);
                               buf[p - vga->ctlr->name] = 0;
                               vtype = buf;
                       }
                       else
                               vtype = vga->ctlr->name;

                       /*
                        * VESA must be set up before linear.
                        * Set type to vesa for linear.
                        */
                       if(vga->vesa){
                               vesa.load(vga, vga->vesa);
                               if(vga->vesa->flag&Ferror)
                                       error("vesa load error\n");
                               vgactlw("type", vesa.name);
                       } else
                               vgactlw("type", vtype);

                       /*
                        * The new draw device needs linear mode set
                        * before size.
                        */
                       linear(vga);

                       /*
                        * Linear is over so switch to other driver for
                        * acceleration.
                        */
                       if(vga->vesa && strcmp(vesa.name, vtype))
                               vgactlw("type", vtype);

                       sprint(buf, "%ludx%ludx%d %s",
                               vga->virtx, vga->virty,
                               vga->mode->z, vga->mode->chan);
                       if(rflag){
                               vgactlr("size", sizeb);
                               if(rflag < 2 && strcmp(buf, sizeb) != 0)
                                       error("bad refresh: %s != %s\n",
                                               buf, sizeb);
                       }
                       else
                               vgactlw("size", buf);

                       /*
                        * No fiddling with registers if VESA set
                        * things up already.  Sorry.
                        */
                       if(!vga->vesa){
                               /*
                                * Turn off the display during the load.
                                */
                               sequencer(vga, 0);

                               for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
                                       if(ctlr->load == 0 || ctlr == &vesa)
                                               continue;
                                       trace("%s->load\n", ctlr->name);
                                       (*ctlr->load)(vga, ctlr);
                               }

                               sequencer(vga, 1);
                       }

                       vgactlw("drawinit", "");

                       if(vga->hwgc == 0 || cflag)
                               vgactlw("hwgc", "soft");
                       else
                               vgactlw("hwgc", vga->hwgc->name);

                       if(vga->virtx != vga->mode->x || vga->virty != vga->mode->y){
                               sprint(buf, "%dx%d", vga->mode->x, vga->mode->y);
                               vgactlw("actualsize", buf);
                               if(vga->panning)
                                       vgactlw("panning", "on");
                       }

                       if(pflag)
                               dump(vga);
               }
       }

       trace("main->exits\n");
       exits(0);
}