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

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

typedef struct Reg Reg;
typedef struct Dpll Dpll;
typedef struct Hdmi Hdmi;
typedef struct Dp Dp;
typedef struct Fdi Fdi;
typedef struct Pfit Pfit;
typedef struct Curs Curs;
typedef struct Plane Plane;
typedef struct Trans Trans;
typedef struct Pipe Pipe;
typedef struct Igfx Igfx;

enum {
       MHz = 1000000,
};

enum {
       TypeG45,
       TypeIVB,                /* Ivy Bridge */
       TypeSNB,                /* Sandy Bridge (unfinished) */
       TypeHSW,                /* Haswell */
};

enum {
       PortVGA = 0,            /* adpa */
       PortLCD = 1,            /* lvds */
       PortDPA = 2,
       PortDPB = 3,
       PortDPC = 4,
       PortDPD = 5,
       PortDPE = 6,
};

struct Reg {
       u32int  a;              /* address or 0 when invalid */
       u32int  v;              /* value */
};

struct Dpll {
       Reg     ctrl;           /* DPLLx_CTRL */
       Reg     fp0;            /* FPx0 */
       Reg     fp1;            /* FPx1 */
};

struct Trans {
       Reg     dm[2];          /* pipe/trans DATAM */
       Reg     dn[2];          /* pipe/trans DATAN */
       Reg     lm[2];          /* pipe/trans LINKM */
       Reg     ln[2];          /* pipe/trans LINKN */

       Reg     ht;             /* pipe/trans HTOTAL_x */
       Reg     hb;             /* pipe/trans HBLANK_x */
       Reg     hs;             /* pipe/trans HSYNC_x */
       Reg     vt;             /* pipe/trans VTOTAL_x */
       Reg     vb;             /* pipe/trans VBLANK_x */
       Reg     vs;             /* pipe/trans VSYNC_x */
       Reg     vss;            /* pipe/trans VSYNCSHIFT_x */

       Reg     conf;           /* pipe/trans CONF_x */
       Reg     chicken;        /* workarround register */

       Reg     dpctl;          /* TRANS_DP_CTL_x */

       Dpll    *dpll;          /* this transcoders dpll */
       Reg     clksel;         /* HSW: PIPE_CLK_SEL_x: transcoder clock select */
};

struct Hdmi {
       Reg     ctl;
       Reg     bufctl[4];
};

struct Dp {
       /* HSW */
       int     hdmi;
       Reg     stat;
       Reg     bufctl;         /* DDI_BUF_CTL_x */
       Reg     buftrans[20];   /* DDI_BUF_TRANS */

       Reg     ctl;            /* HSW: DP_TP_CTL_x */
       Reg     auxctl;
       Reg     auxdat[5];

       uchar   dpcd[256];
};

struct Fdi {
       Trans;

       Reg     txctl;          /* FDI_TX_CTL */

       Reg     rxctl;          /* FDI_RX_CTL */
       Reg     rxmisc;         /* FDI_RX_MISC */
       Reg     rxiir;          /* FDI_RX_IIR */
       Reg     rximr;          /* FDI_RX_IMR */
       Reg     rxtu[2];        /* FDI_RX_TUSIZE */
};

struct Pfit {
       Reg     ctrl;
       Reg     winpos;
       Reg     winsize;
       Reg     pwrgate;
};

struct Plane {
       Reg     cntr;           /* DSPxCNTR */
       Reg     linoff;         /* DSPxLINOFF */
       Reg     stride;         /* DSPxSTRIDE */
       Reg     surf;           /* DSPxSURF */
       Reg     tileoff;        /* DSPxTILEOFF */
       Reg     leftsurf;       /* HSW: PRI_LEFT_SURF_x */

       Reg     pos;
       Reg     size;
};

struct Curs {
       Reg     cntr;
       Reg     base;
       Reg     pos;
};

struct Pipe {
       Trans;                  /* cpu transcoder */

       Reg     src;            /* PIPExSRC */

       Fdi     fdi[1];         /* fdi/dp transcoder */

       Plane   dsp[1];         /* display plane */
       Curs    cur[1];         /* hardware cursor */

       Pfit    *pfit;          /* selected panel fitter */
};

struct Igfx {
       Ctlr    *ctlr;
       Pcidev  *pci;

       u32int  pio;
       u32int  *mmio;

       int     type;
       int     cdclk;          /* core display clock in mhz */

       int     npipe;
       Pipe    pipe[4];

       Dpll    dpll[4];
       Pfit    pfit[3];

       /* HSW */
       int     isult;

       Reg     dpllsel[5];     /* DPLL_SEL (IVB), PORT_CLK_SEL_DDIx (HSW) */

       /* IVB */
       Reg     drefctl;        /* DREF_CTL */
       Reg     rawclkfreq;     /* RAWCLK_FREQ */
       Reg     ssc4params;     /* SSC4_PARAMS */

       Dp      dp[5];
       Hdmi    hdmi[4];

       Reg     ppcontrol;
       Reg     ppstatus;

       /* G45 */
       Reg     gmbus[6];       /* GMBUSx */

       Reg     sdvoc;
       Reg     sdvob;

       /* common */
       Reg     adpa;
       Reg     lvds;

       Reg     vgacntrl;
};

static u32int
rr(Igfx *igfx, u32int a)
{
       if(a == 0)
               return 0;
       assert((a & 3) == 0);
       if(igfx->mmio != nil)
               return igfx->mmio[a/4];
       outportl(igfx->pio, a);
       return inportl(igfx->pio + 4);
}
static void
wr(Igfx *igfx, u32int a, u32int v)
{
       if(a == 0)      /* invalid */
               return;
       assert((a & 3) == 0);
       if(igfx->mmio != nil){
               igfx->mmio[a/4] = v;
               return;
       }
       outportl(igfx->pio, a);
       outportl(igfx->pio + 4, v);
}
static void
csr(Igfx *igfx, u32int reg, u32int clr, u32int set)
{
       wr(igfx, reg, (rr(igfx, reg) & ~clr) | set);
}

static void
loadreg(Igfx *igfx, Reg r)
{
       wr(igfx, r.a, r.v);
}

static Reg
snarfreg(Igfx *igfx, u32int a)
{
       Reg r;

       r.a = a;
       r.v = rr(igfx, a);
       return r;
}

static void
snarftrans(Igfx *igfx, Trans *t, u32int o)
{
       /* pipe timing */
       t->ht   = snarfreg(igfx, o + 0x00000);
       t->hb   = snarfreg(igfx, o + 0x00004);
       t->hs   = snarfreg(igfx, o + 0x00008);
       t->vt   = snarfreg(igfx, o + 0x0000C);
       t->vb   = snarfreg(igfx, o + 0x00010);
       t->vs   = snarfreg(igfx, o + 0x00014);
       t->vss  = snarfreg(igfx, o + 0x00028);

       t->conf = snarfreg(igfx, o + 0x10008);

       switch(igfx->type){
       case TypeG45:
               if(t == &igfx->pipe[0]){                        /* PIPEA */
                       t->dm[0] = snarfreg(igfx, 0x70050);     /* GMCHDataM */
                       t->dn[0] = snarfreg(igfx, 0x70054);     /* GMCHDataN */
                       t->lm[0] = snarfreg(igfx, 0x70060);     /* DPLinkM */
                       t->ln[0] = snarfreg(igfx, 0x70064);     /* DPLinkN */
               }
               break;
       case TypeIVB:
       case TypeSNB:
               t->dm[0] = snarfreg(igfx, o + 0x30);
               t->dn[0] = snarfreg(igfx, o + 0x34);
               t->dm[1] = snarfreg(igfx, o + 0x38);
               t->dn[1] = snarfreg(igfx, o + 0x3c);
               t->lm[0] = snarfreg(igfx, o + 0x40);
               t->ln[0] = snarfreg(igfx, o + 0x44);
               t->lm[1] = snarfreg(igfx, o + 0x48);
               t->ln[1] = snarfreg(igfx, o + 0x4c);
               break;
       case TypeHSW:
               t->dm[0] = snarfreg(igfx, o + 0x30);
               t->dn[0] = snarfreg(igfx, o + 0x34);
               t->lm[0] = snarfreg(igfx, o + 0x40);
               t->ln[0] = snarfreg(igfx, o + 0x44);
               if(t == &igfx->pipe[3]){                        /* eDP pipe */
                       t->dm[1] = snarfreg(igfx, o + 0x38);
                       t->dn[1] = snarfreg(igfx, o + 0x3C);
                       t->lm[1] = snarfreg(igfx, o + 0x48);
                       t->ln[1] = snarfreg(igfx, o + 0x4C);
               }
               break;
       }
}

static void
snarfpipe(Igfx *igfx, int x)
{
       u32int o;
       Pipe *p;

       p = &igfx->pipe[x];

       o = x == 3 ? 0x6F000 : 0x60000 + x*0x1000;
       snarftrans(igfx, p, o);

       if(igfx->type != TypeHSW || x != 3)
               p->src = snarfreg(igfx, o + 0x0001C);

       if(igfx->type == TypeHSW) {
               p->dpctl = snarfreg(igfx, o + 0x400);   /* PIPE_DDI_FUNC_CTL_x */
               p->dpll = &igfx->dpll[0];
               if(x == 3){
                       x = 0;  /* use plane/cursor a */
                       p->src = snarfreg(igfx, 0x6001C);
               }else
                       p->clksel = snarfreg(igfx, 0x46140 + x*4);
       } else if(igfx->type == TypeIVB || igfx->type == TypeSNB) {
               p->fdi->txctl = snarfreg(igfx, o + 0x100);

               o = 0xE0000 | x*0x1000;
               snarftrans(igfx, p->fdi, o);

               p->fdi->dpctl = snarfreg(igfx, o + 0x300);

               p->fdi->rxctl = snarfreg(igfx, o + 0x1000c);
               p->fdi->rxmisc = snarfreg(igfx, o + 0x10010);

               p->fdi->rxiir = snarfreg(igfx, o + 0x10014);
               p->fdi->rximr = snarfreg(igfx, o + 0x10018);

               p->fdi->rxtu[0] = snarfreg(igfx, o + 0x10030);
               p->fdi->rxtu[1] = snarfreg(igfx, o + 0x10038);

               p->fdi->chicken = snarfreg(igfx, o + 0x10064);

               p->fdi->dpll = &igfx->dpll[(igfx->dpllsel[0].v>>(x*4)) & 1];
               p->dpll = nil;
       } else {
               p->dpll = &igfx->dpll[x & 1];
       }

       /* display plane */
       p->dsp->cntr            = snarfreg(igfx, 0x70180 + x*0x1000);
       p->dsp->linoff          = snarfreg(igfx, 0x70184 + x*0x1000);
       p->dsp->stride          = snarfreg(igfx, 0x70188 + x*0x1000);
       p->dsp->tileoff         = snarfreg(igfx, 0x701A4 + x*0x1000);
       p->dsp->surf            = snarfreg(igfx, 0x7019C + x*0x1000);
       if(igfx->type == TypeHSW)
               p->dsp->leftsurf = snarfreg(igfx, 0x701B0 + x*0x1000);

       /* cursor plane */
       switch(igfx->type){
       case TypeIVB:
       case TypeHSW:
               p->cur->cntr    = snarfreg(igfx, 0x70080 + x*0x1000);
               p->cur->base    = snarfreg(igfx, 0x70084 + x*0x1000);
               p->cur->pos     = snarfreg(igfx, 0x70088 + x*0x1000);
               break;
       case TypeG45:
               p->dsp->pos     = snarfreg(igfx, 0x7018C + x*0x1000);
               p->dsp->size    = snarfreg(igfx, 0x70190 + x*0x1000);
               /* wet floor */
       case TypeSNB:
               p->cur->cntr    = snarfreg(igfx, 0x70080 + x*0x40);
               p->cur->base    = snarfreg(igfx, 0x70084 + x*0x40);
               p->cur->pos     = snarfreg(igfx, 0x70088 + x*0x40);
               break;
       }
}

static int
devtype(Igfx *igfx)
{
       if(igfx->pci->vid != 0x8086)
               return -1;
       switch(igfx->pci->did){
       case 0x0a16:    /* HD 4400 - 4th Gen Core (ULT) */
               igfx->isult = 1;
               /* wet floor */
       case 0x0412:    /* HD 4600 - 4th Gen Core */
               return TypeHSW;
       case 0x0166:    /* 3rd Gen Core - ThinkPad X230 */
       case 0x0152:    /* 2nd/3rd Gen Core - Core-i3 */
               return TypeIVB;
       case 0x0046:    /* Thinkpad T510 */
       case 0x0102:    /* Dell Optiplex 790 */
       case 0x0126:    /* Thinkpad X220 */
               return TypeSNB;
       case 0x27a2:    /* GM945/82940GML - ThinkPad X60 Tablet */
       case 0x29a2:    /* 82P965/G965 HECI desktop */
       case 0x2a02:    /* GM965/GL960/X3100 - ThinkPad X61 Tablet */
       case 0x2a42:    /* 4 Series Mobile - ThinkPad X200 */
       case 0x2592:    /* 915GM */
               return TypeG45;
       }
       return -1;
}

static Edid* snarfgmedid(Igfx*, int port, int addr);
static Edid* snarfdpedid(Igfx*, Dp *dp, int addr);

static int enabledp(Igfx*, Dp*);

static void
snarf(Vga* vga, Ctlr* ctlr)
{
       Igfx *igfx;
       int x, y;

       igfx = vga->private;
       if(igfx == nil) {
               igfx = alloc(sizeof(Igfx));
               igfx->ctlr = ctlr;
               igfx->pci = vga->pci;
               if(igfx->pci == nil){
                       error("%s: no pci device\n", ctlr->name);
                       return;
               }
               igfx->type = devtype(igfx);
               if(igfx->type < 0){
                       error("%s: unrecognized device\n", ctlr->name);
                       return;
               }
               vgactlpci(igfx->pci);
               if(1){
                       vgactlw("type", ctlr->name);
                       igfx->mmio = segattach(0, "igfxmmio", 0, igfx->pci->mem[0].size);
                       if(igfx->mmio == (u32int*)-1)
                               error("%s: attaching mmio: %r\n", ctlr->name);
               } else {
                       if((igfx->pci->mem[4].bar & 1) == 0)
                               error("%s: no pio bar\n", ctlr->name);
                       igfx->pio = igfx->pci->mem[4].bar & ~1;
               }
               vga->private = igfx;
       }

       switch(igfx->type){
       case TypeG45:
               igfx->npipe = 2;        /* A,B */
               igfx->cdclk = 200;      /* MHz */

               igfx->dpll[0].ctrl      = snarfreg(igfx, 0x06014);
               igfx->dpll[0].fp0       = snarfreg(igfx, 0x06040);
               igfx->dpll[0].fp1       = snarfreg(igfx, 0x06044);
               igfx->dpll[1].ctrl      = snarfreg(igfx, 0x06018);
               igfx->dpll[1].fp0       = snarfreg(igfx, 0x06048);
               igfx->dpll[1].fp1       = snarfreg(igfx, 0x0604c);

               igfx->adpa              = snarfreg(igfx, 0x061100);
               igfx->lvds              = snarfreg(igfx, 0x061180);
               igfx->sdvob             = snarfreg(igfx, 0x061140);
               igfx->sdvoc             = snarfreg(igfx, 0x061160);

               for(x=0; x<5; x++)
                       igfx->gmbus[x]  = snarfreg(igfx, 0x5100 + x*4);
               igfx->gmbus[x]  = snarfreg(igfx, 0x5120);

               igfx->pfit[0].ctrl      = snarfreg(igfx, 0x061230);
               y = (igfx->pfit[0].ctrl.v >> 29) & 3;
               if(igfx->pipe[y].pfit == nil)
                       igfx->pipe[y].pfit = &igfx->pfit[0];

               igfx->ppstatus          = snarfreg(igfx, 0x61200);
               igfx->ppcontrol         = snarfreg(igfx, 0x61204);

               igfx->vgacntrl          = snarfreg(igfx, 0x071400);
               break;

       case TypeSNB:
               igfx->npipe = 2;        /* A,B */
               igfx->cdclk = 400;      /* MHz */
               goto IVBcommon;

       case TypeIVB:
               igfx->npipe = 3;        /* A,B,C */
               igfx->cdclk = 400;      /* MHz */
               goto IVBcommon;

       IVBcommon:
               igfx->dpll[0].ctrl      = snarfreg(igfx, 0xC6014);
               igfx->dpll[0].fp0       = snarfreg(igfx, 0xC6040);
               igfx->dpll[0].fp1       = snarfreg(igfx, 0xC6044);
               igfx->dpll[1].ctrl      = snarfreg(igfx, 0xC6018);
               igfx->dpll[1].fp0       = snarfreg(igfx, 0xC6048);
               igfx->dpll[1].fp1       = snarfreg(igfx, 0xC604c);

               igfx->dpllsel[0]        = snarfreg(igfx, 0xC7000);

               igfx->drefctl           = snarfreg(igfx, 0xC6200);
               igfx->ssc4params        = snarfreg(igfx, 0xC6210);

               igfx->dp[0].ctl         = snarfreg(igfx, 0x64000);
               for(x=1; x<4; x++)
                       igfx->dp[x].ctl = snarfreg(igfx, 0xE4000 + 0x100*x);

               igfx->hdmi[1].ctl       = snarfreg(igfx, 0x0E1140);     /* HDMI_CTL_B */
               igfx->hdmi[1].bufctl[0] = snarfreg(igfx, 0x0FC810);     /* HTMI_BUF_CTL_0 */
               igfx->hdmi[1].bufctl[1] = snarfreg(igfx, 0x0FC81C);     /* HTMI_BUF_CTL_1 */
               igfx->hdmi[1].bufctl[2] = snarfreg(igfx, 0x0FC828);     /* HTMI_BUF_CTL_2 */
               igfx->hdmi[1].bufctl[3] = snarfreg(igfx, 0x0FC834);     /* HTMI_BUF_CTL_3 */

               igfx->hdmi[2].ctl       = snarfreg(igfx, 0x0E1150);     /* HDMI_CTL_C */
               igfx->hdmi[2].bufctl[0] = snarfreg(igfx, 0x0FCC00);     /* HTMI_BUF_CTL_4 */
               igfx->hdmi[2].bufctl[1] = snarfreg(igfx, 0x0FCC0C);     /* HTMI_BUF_CTL_5 */
               igfx->hdmi[2].bufctl[2] = snarfreg(igfx, 0x0FCC18);     /* HTMI_BUF_CTL_6 */
               igfx->hdmi[2].bufctl[3] = snarfreg(igfx, 0x0FCC24);     /* HTMI_BUF_CTL_7 */

               igfx->hdmi[3].ctl       = snarfreg(igfx, 0x0E1160);     /* HDMI_CTL_D */
               igfx->hdmi[3].bufctl[0] = snarfreg(igfx, 0x0FD000);     /* HTMI_BUF_CTL_8 */
               igfx->hdmi[3].bufctl[1] = snarfreg(igfx, 0x0FD00C);     /* HTMI_BUF_CTL_9 */
               igfx->hdmi[3].bufctl[2] = snarfreg(igfx, 0x0FD018);     /* HTMI_BUF_CTL_10 */
               igfx->hdmi[3].bufctl[3] = snarfreg(igfx, 0x0FD024);     /* HTMI_BUF_CTL_11 */

               igfx->lvds              = snarfreg(igfx, 0x0E1180);     /* LVDS_CTL */
               goto PCHcommon;

       case TypeHSW:
               igfx->npipe = 4;        /* A,B,C,eDP */
               igfx->cdclk = igfx->isult ? 450 : 540;  /* MHz */

               igfx->dpll[0].ctrl      = snarfreg(igfx, 0x130040);     /* LCPLL_CTL */
               igfx->dpll[1].ctrl      = snarfreg(igfx, 0x46040);      /* WRPLL_CTL1 */
               igfx->dpll[2].ctrl      = snarfreg(igfx, 0x46060);      /* WRPLL_CTL2 */
               igfx->dpll[3].ctrl      = snarfreg(igfx, 0x46020);      /* SPLL_CTL */

               for(x=0; x<nelem(igfx->dp); x++){
                       if(x == 3 && igfx->isult)       /* no DDI D */
                               continue;
                       igfx->dp[x].bufctl      = snarfreg(igfx, 0x64000 + 0x100*x);
                       igfx->dp[x].ctl         = snarfreg(igfx, 0x64040 + 0x100*x);
                       if(x > 0)
                               igfx->dp[x].stat        = snarfreg(igfx, 0x64044 + 0x100*x);
                       for(y=0; y<nelem(igfx->dp[x].buftrans); y++)
                               igfx->dp[x].buftrans[y] = snarfreg(igfx, 0x64E00 + 0x60*x + 4*y);
                       igfx->dpllsel[x]        = snarfreg(igfx, 0x46100 + 4*x);
               }

               goto PCHcommon;

       PCHcommon:
               igfx->rawclkfreq        = snarfreg(igfx, 0xC6204);

               /* cpu displayport A */
               igfx->dp[0].auxctl      = snarfreg(igfx, 0x64010);
               igfx->dp[0].auxdat[0]   = snarfreg(igfx, 0x64014);
               igfx->dp[0].auxdat[1]   = snarfreg(igfx, 0x64018);
               igfx->dp[0].auxdat[2]   = snarfreg(igfx, 0x6401C);
               igfx->dp[0].auxdat[3]   = snarfreg(igfx, 0x64020);
               igfx->dp[0].auxdat[4]   = snarfreg(igfx, 0x64024);

               /* pch displayport B,C,D */
               for(x=1; x<4; x++){
                       igfx->dp[x].auxctl      = snarfreg(igfx, 0xE4010 + 0x100*x);
                       igfx->dp[x].auxdat[0]   = snarfreg(igfx, 0xE4014 + 0x100*x);
                       igfx->dp[x].auxdat[1]   = snarfreg(igfx, 0xE4018 + 0x100*x);
                       igfx->dp[x].auxdat[2]   = snarfreg(igfx, 0xE401C + 0x100*x);
                       igfx->dp[x].auxdat[3]   = snarfreg(igfx, 0xE4020 + 0x100*x);
                       igfx->dp[x].auxdat[4]   = snarfreg(igfx, 0xE4024 + 0x100*x);
               }

               for(x=0; x<igfx->npipe; x++){
                       if(igfx->type == TypeHSW && x == 3){
                               igfx->pipe[x].pfit = &igfx->pfit[0];
                               continue;
                       }
                       igfx->pfit[x].pwrgate   = snarfreg(igfx, 0x68060 + 0x800*x);
                       igfx->pfit[x].winpos    = snarfreg(igfx, 0x68070 + 0x800*x);
                       igfx->pfit[x].winsize   = snarfreg(igfx, 0x68074 + 0x800*x);
                       igfx->pfit[x].ctrl      = snarfreg(igfx, 0x68080 + 0x800*x);

                       y = (igfx->pfit[x].ctrl.v >> 29) & 3;
                       if(igfx->pipe[y].pfit == nil)
                               igfx->pipe[y].pfit = &igfx->pfit[x];
               }

               igfx->ppstatus          = snarfreg(igfx, 0xC7200);
               igfx->ppcontrol         = snarfreg(igfx, 0xC7204);

               for(x=0; x<5; x++)
                       igfx->gmbus[x]  = snarfreg(igfx, 0xC5100 + x*4);
               igfx->gmbus[x]  = snarfreg(igfx, 0xC5120);

               igfx->adpa              = snarfreg(igfx, 0x0E1100);     /* DAC_CTL */
               igfx->vgacntrl          = snarfreg(igfx, 0x041000);
               break;
       }

       for(x=0; x<igfx->npipe; x++)
               snarfpipe(igfx, x);

       for(x=0; x<nelem(vga->edid); x++){
               Modelist *l;

               switch(x){
               case PortVGA:
                       vga->edid[x] = snarfgmedid(igfx, 2, 0x50);
                       break;
               case PortLCD:
                       vga->edid[x] = snarfgmedid(igfx, 3, 0x50);
                       if(vga->edid[x] == nil)
                               continue;
                       for(l = vga->edid[x]->modelist; l != nil; l = l->next)
                               l->attr = mkattr(l->attr, "lcd", "1");
                       break;
               case PortDPD:
                       if(igfx->type == TypeHSW && igfx->isult)
                               continue;
               case PortDPA:
               case PortDPB:
               case PortDPC:
                       vga->edid[x] = snarfdpedid(igfx, &igfx->dp[x-PortDPA], 0x50);
                       break;
               default:
                       continue;
               }

               if(vga->edid[x] == nil){
                       if(x < PortDPB)
                               continue;
                       vga->edid[x] = snarfgmedid(igfx, x + 1 & ~1 | x >> 1 & 1, 0x50);
                       if(vga->edid[x] == nil)
                               continue;
                       igfx->dp[x-PortDPA].hdmi = 1;
               }
               for(l = vga->edid[x]->modelist; l != nil; l = l->next)
                       l->attr = mkattr(l->attr, "display", "%d", x+1);
       }

       ctlr->flag |= Fsnarf;
}

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

enum {
       Lcpll = 2700,
       Lcpll2k = Lcpll * 2000,
};

static int
wrbudget(int freq)
{
       static int b[] = {
               25175000,0, 25200000,0, 27000000,0, 27027000,0,
               37762500,0, 37800000,0, 40500000,0, 40541000,0,
               54000000,0, 54054000,0, 59341000,0, 59400000,0,
               72000000,0, 74176000,0, 74250000,0, 81000000,0,
               81081000,0, 89012000,0, 89100000,0, 108000000,0,
               108108000,0, 111264000,0, 111375000,0, 148352000,0,
               148500000,0, 162000000,0, 162162000,0, 222525000,0,
               222750000,0, 296703000,0, 297000000,0, 233500000,1500,
               245250000,1500, 247750000,1500, 253250000,1500, 298000000,1500,
               169128000,2000, 169500000,2000, 179500000,2000, 202000000,2000,
               256250000,4000, 262500000,4000, 270000000,4000, 272500000,4000,
               273750000,4000, 280750000,4000, 281250000,4000, 286000000,4000,
               291750000,4000, 267250000,5000, 268500000,5000
       };
       int *i;

       for(i=b; i<b+nelem(b); i+=2)
               if(i[0] == freq)
                       return i[1];
       return 1000;
}

static void
genwrpll(int freq, int *n2, int *p, int *r2)
{
       int budget, N2, P, R2;
       vlong f2k, a, b, c, d, Δ, bestΔ;

       f2k = freq / 100;
       if(f2k == Lcpll2k){     /* bypass wrpll entirely and use lcpll */
               *n2 = 2;
               *p = 1;
               *r2 = 2;
               return;
       }
       budget = wrbudget(freq);
       *p = 0;
       for(R2=Lcpll*2/400+1; R2<=Lcpll*2/48; R2++)
       for(N2=2400*R2/Lcpll+1; N2<=4800*R2/Lcpll; N2++)
               for(P=2; P<=64; P+=2){
                       if(*p == 0){
                               *n2 = N2;
                               *p = P;
                               *r2 = R2;
                               continue;
                       }
                       Δ = f2k * P * R2;
                       Δ -= N2 * Lcpll2k;
                       if(Δ < 0)
                               Δ = -Δ;
                       bestΔ = f2k * *p * *r2;
                       bestΔ -= *n2 * Lcpll2k;
                       if(bestΔ < 0)
                               bestΔ = -bestΔ;
                       a = f2k * budget;
                       b = a;
                       a *= P * R2;
                       b *= *p * *r2;
                       c = Δ * MHz;
                       d = bestΔ * MHz;
                       if(a < c && b < d && *p * *r2 * Δ < P * R2 * bestΔ
                       || a >= c && b < d
                       || a >= c && b >= d && N2 * *r2 * *r2 > *n2 * R2 * R2){
                               *n2 = N2;
                               *p = P;
                               *r2 = R2;
                       }
               }
}

static int
genpll(int freq, int cref, int P2, int *m1, int *m2, int *n, int *p1)
{
       int M1, M2, M, N, P, P1;
       int best, error;
       vlong a;

       best = -1;
       for(N=3; N<=8; N++)
       for(M2=5; M2<=9; M2++)
//      for(M1=10; M1<=20; M1++){
       for(M1=12; M1<=22; M1++){
               M = 5*(M1+2) + (M2+2);
               if(M < 79 || M > 127)
//              if(M < 70 || M > 120)
                       continue;
               for(P1=1; P1<=8; P1++){
                       P = P1 * P2;
                       if(P < 5 || P > 98)
//                      if(P < 4 || P > 98)
                               continue;
                       a = cref;
                       a *= M;
                       a /= N+2;
                       a /= P;
                       if(a < 20*MHz || a > 400*MHz)
                               continue;
                       error = a;
                       error -= freq;
                       if(error < 0)
                               error = -error;
                       if(best < 0 || error < best){
                               best = error;
                               *m1 = M1;
                               *m2 = M2;
                               *n = N;
                               *p1 = P1;
                       }
               }
       }
       return best;
}

static int
getcref(Igfx *igfx, int x)
{
       Dpll *dpll;

       dpll = &igfx->dpll[x];
       if(igfx->type == TypeG45){
               if(((dpll->ctrl.v >> 13) & 3) == 3)
                       return 100*MHz;
               return 96*MHz;
       }
       return 120*MHz;
}

static int
initdpll(Igfx *igfx, int x, int freq, int port)
{
       int cref, m1, m2, n, n2, p1, p2, r2;
       Dpll *dpll;

       switch(igfx->type){
       case TypeG45:
               /* PLL Reference Input Select */
               dpll = igfx->pipe[x].dpll;
               dpll->ctrl.v &= ~(3<<13);
               dpll->ctrl.v |= (port == PortLCD ? 3 : 0) << 13;
               break;
       case TypeSNB:
       case TypeIVB:
               /* transcoder dpll enable */
               igfx->dpllsel[0].v |= 8<<(x*4);
               /* program rawclock to 125MHz */
               igfx->rawclkfreq.v = 125;

               igfx->drefctl.v &= ~(3<<13);
               igfx->drefctl.v &= ~(3<<11);
               igfx->drefctl.v &= ~(3<<9);
               igfx->drefctl.v &= ~(3<<7);
               igfx->drefctl.v &= ~3;

               if(port == PortLCD){
                       igfx->drefctl.v |= 2<<11;
                       igfx->drefctl.v |= 1;
               } else {
                       igfx->drefctl.v |= 2<<9;
               }

               /*
                * PLL Reference Input Select:
                * 000  DREFCLK         (default is 120 MHz) for DAC/HDMI/DVI/DP
                * 001  Super SSC       120MHz super-spread clock
                * 011  SSC             Spread spectrum input clock (120MHz default) for LVDS/DP
                */
               dpll = igfx->pipe[x].fdi->dpll;
               dpll->ctrl.v &= ~(7<<13);
               dpll->ctrl.v |= (port == PortLCD ? 3 : 0) << 13;
               break;
       case TypeHSW:
               /* select port clock to pipe */
               igfx->pipe[x].clksel.v = port+1-PortDPA<<29;

               if(igfx->dp[port-PortDPA].hdmi){
                       /* select port clock */
                       igfx->dpllsel[port-PortDPA].v = 1<<31;  /* WRPLL1 */
                       igfx->pipe[x].dpll = &igfx->dpll[1];

                       dpll = igfx->pipe[x].dpll;
                       /* enable pll */
                       dpll->ctrl.v = 1<<31;
                       /* LCPLL 2700 (non scc) reference */
                       dpll->ctrl.v |= 3<<28;

                       genwrpll(freq, &n2, &p1, &r2);
                       dpll->ctrl.v |= n2 << 16;
                       dpll->ctrl.v |= p1 << 8;
                       dpll->ctrl.v |= r2;
               }else{
                       /* select port clock */
                       igfx->dpllsel[port-PortDPA].v = 1<<29;  /* LCPLL 1350 */
                       /* keep preconfigured frequency settings, keep cdclk */
                       dpll = igfx->pipe[x].dpll;
                       dpll->ctrl.v &= ~(1<<31) & ~(1<<28) & ~(1<<26);
               }
               return 0;
       default:
               return -1;
       }
       cref = getcref(igfx, x);

       /* Dpll Mode select */
       dpll->ctrl.v &= ~(3<<26);
       dpll->ctrl.v |= (port == PortLCD ? 2 : 1)<<26;

       /* P2 Clock Divide */
       dpll->ctrl.v &= ~(3<<24);
       if(port == PortLCD){
               p2 = 14;
               if(freq > 112*MHz){
                       p2 >>= 1;
                       dpll->ctrl.v |= (1<<24);
               }
               if(genpll(freq, cref, p2, &m1, &m2, &n, &p1) < 0)
                       return -1;
       } else {
               /* generate 270MHz clock for displayport */
               if(port >= PortDPA)
                       freq = 270*MHz;

               p2 = 10;
               if(freq > 270*MHz){
                       p2 >>= 1;
                       dpll->ctrl.v |= (1<<24);
               }
               if(genpll(freq, cref, p2, &m1, &m2, &n, &p1) < 0)
                       return -1;
       }

       /* Dpll VCO Enable */
       dpll->ctrl.v |= (1<<31);

       /* Dpll Serial DVO High Speed IO clock Enable */
       if(port >= PortDPA)
               dpll->ctrl.v |= (1<<30);
       else
               dpll->ctrl.v &= ~(1<<30);

       /* VGA Mode Disable */
       if(igfx->type == TypeG45)
               dpll->ctrl.v |= (1<<28);

       dpll->fp0.v &= ~(0x3f<<16);
       dpll->fp0.v |= n << 16;
       dpll->fp0.v &= ~(0x3f<<8);
       dpll->fp0.v |= m1 << 8;
       dpll->fp0.v &= ~(0x3f<<0);
       dpll->fp0.v |= m2 << 0;

       /* FP0 P1 Post Divisor */
       dpll->ctrl.v &= ~0xFF0000;
       dpll->ctrl.v |=  0x010000<<(p1-1);

       /* FP1 P1 Post divisor */
       if(igfx->pci->did != 0x27a2 && igfx->pci->did != 0x2592){
               dpll->ctrl.v &= ~0xFF;
               dpll->ctrl.v |=  0x01<<(p1-1);
               dpll->fp1.v = dpll->fp0.v;
       }

       return 0;
}

static int
needlanes(int freq, int lsclk, int bpp)
{
       vlong v;
       int n;

       v = ((vlong)freq * bpp) / 8;
       for(n=1; n<4 && v>lsclk; n<<=1, v>>=1)
               ;
       return n;
}

static void
initdatalinkmn(Trans *t, int freq, int lsclk, int lanes, int tu, int bpp)
{
       u32int m, n;

       n = 0x800000;
       m = (n * (((uvlong)freq * bpp)/8)) / ((uvlong)lsclk * lanes);

       t->dm[0].v = (tu-1)<<25 | m;
       t->dn[0].v = n;

       n = 0x80000;
       m = ((uvlong)n * freq) / lsclk;

       t->lm[0].v = m;
       t->ln[0].v = n;

       t->dm[1].v = t->dm[0].v;
       t->dn[1].v = t->dn[0].v;
       t->lm[1].v = t->lm[0].v;
       t->ln[1].v = t->ln[0].v;
}

static void
inittrans(Trans *t, Mode *m)
{
       /* clear all but 27:28 frame start delay (initialized by bios) */
       t->conf.v &= 3<<27;

       /* tans/pipe enable */
       t->conf.v |= 1<<31;

       /* trans/pipe timing */
       t->ht.v = (m->ht - 1)<<16 | (m->x - 1);
       t->hs.v = (m->ehs - 1)<<16 | (m->shs - 1);
       t->vt.v = (m->vt - 1)<<16 | (m->y - 1);
       t->vs.v = (m->vre - 1)<<16 | (m->vrs - 1);

       t->hb.v = t->ht.v;
       t->vb.v = t->vt.v;

       t->vss.v = 0;
}

static void
initpipe(Igfx *igfx, Pipe *p, Mode *m, int bpc, int port)
{
       static uchar bpctab[4] = { 8, 10, 6, 12 };
       int i, tu, lanes;
       Fdi *fdi;

       /* source image size */
       p->src.v = (m->x - 1)<<16 | (m->y - 1);

       if(p->pfit != nil){
               /* panel fitter off */
               p->pfit->ctrl.v &= ~(1<<31);
               p->pfit->winpos.v = 0;
               p->pfit->winsize.v = 0;
       }

       /* enable and set monitor timings for cpu pipe */
       inittrans(p, m);

       /* default for displayport */
       lanes = needlanes(m->frequency, 270*MHz, 3*bpc);
       tu = 64;

       fdi = p->fdi;
       if(fdi->rxctl.a != 0){
               /* enable and set monitor timings for transcoder */
               inittrans(fdi, m);

               /* tx port width selection */
               fdi->txctl.v &= ~(7<<19);
               fdi->txctl.v |= (lanes-1)<<19;

               /* rx port width selection */
               fdi->rxctl.v &= ~(7<<19);
               fdi->rxctl.v |= (lanes-1)<<19;
               /* bits per color for transcoder */
               for(i=0; i<nelem(bpctab); i++){
                       if(bpctab[i] == bpc){
                               fdi->rxctl.v &= ~(7<<16);
                               fdi->rxctl.v |= i<<16;
                               fdi->dpctl.v &= ~(7<<9);
                               fdi->dpctl.v |= i<<9;
                               break;
                       }
               }

               /* enhanced framing on */
               fdi->rxctl.v |= (1<<6);
               fdi->txctl.v |= (1<<18);

               /* tusize 1 and 2 */
               fdi->rxtu[0].v = (tu-1)<<25;
               fdi->rxtu[1].v = (tu-1)<<25;
               initdatalinkmn(fdi, m->frequency, 270*MHz, lanes, tu, 3*bpc);
       }else if(igfx->type == TypeHSW){
               p->dpctl.v &= 0xf773733e;       /* mbz */
               /* transcoder enable */
               p->dpctl.v |= 1<<31;
               /* DDI select (ignored by eDP) */
               p->dpctl.v &= ~(7<<28);
               p->dpctl.v |= (port-PortDPA)<<28;
               /* displayport SST or hdmi mode */
               p->dpctl.v &= ~(7<<24);
               if(!igfx->dp[port-PortDPA].hdmi)
                       p->dpctl.v |= 2<<24;
               /* sync polarity */
               p->dpctl.v |= 3<<16;
               if(m->hsync == '-')
                       p->dpctl.v ^= 1<<16;
               if(m->vsync == '-')
                       p->dpctl.v ^= 1<<17;
               /* eDP input select: always on power well */
               p->dpctl.v &= ~(7<<12);
               /* dp port width */
               if(igfx->dp[port-PortDPA].hdmi)
                       lanes = 4;
               else{
                       p->dpctl.v &= ~(7<<1);
                       p->dpctl.v |= lanes-1 << 1;
               }
       }

       /* bits per color for cpu pipe */
       for(i=0; i<nelem(bpctab); i++){
               if(bpctab[i] == bpc){
                       if(igfx->type == TypeHSW){
                               p->dpctl.v &= ~(7<<20);
                               p->dpctl.v |= i<<20;
                       }else{
                               p->conf.v &= ~(7<<5);
                               p->conf.v |= i<<5;
                       }
                       break;
               }
       }
       initdatalinkmn(p, m->frequency, 270*MHz, lanes, tu, 3*bpc);
}

static void
init(Vga* vga, Ctlr* ctlr)
{
       int x, nl, port, bpc;
       char *val;
       Igfx *igfx;
       Pipe *p;
       Mode *m;
       Reg *r;

       m = vga->mode;
       if(m->z != 32)
               error("%s: unsupported color depth %d\n", ctlr->name, m->z);

       bpc = 8;        /* bits per color channel */

       igfx = vga->private;

       /* disable vga */
       igfx->vgacntrl.v |= (1<<31);

       /* disable all pipes and ports */
       igfx->ppcontrol.v &= 10;        /* 15:4 mbz; unset 5<<0 */
       igfx->lvds.v &= ~(1<<31);
       igfx->adpa.v &= ~(1<<31);
       if(igfx->type == TypeG45)
               igfx->adpa.v |= (3<<10);        /* Monitor DPMS: off */
       if(igfx->type == TypeHSW){
               for(x=1; x<nelem(igfx->dpll); x++)
                       igfx->dpll[x].ctrl.v &= ~(1<<31);
               for(x=0; x<nelem(igfx->dpllsel); x++)
                       igfx->dpllsel[x].v = 7<<29;
       }
       for(x=0; x<nelem(igfx->dp); x++){
               igfx->dp[x].ctl.v &= ~(1<<31);
               igfx->dp[x].bufctl.v &= ~(1<<31);
       }
       for(x=0; x<nelem(igfx->hdmi); x++)
               igfx->hdmi[x].ctl.v &= ~(1<<31);
       for(x=0; x<igfx->npipe; x++){
               /* disable displayport transcoders */
               igfx->pipe[x].dpctl.v &= ~(1<<31);
               igfx->pipe[x].fdi->dpctl.v &= ~(1<<31);
               if(igfx->type == TypeHSW){
                       igfx->pipe[x].dpctl.v &= ~(7<<28);
                       igfx->pipe[x].fdi->dpctl.v &= ~(7<<28);
               }else{
                       igfx->pipe[x].dpctl.v |= (3<<29);
                       igfx->pipe[x].fdi->dpctl.v |= (3<<29);
               }
               /* disable transcoder/pipe */
               igfx->pipe[x].conf.v &= ~(1<<31);
               igfx->pipe[x].fdi->conf.v &= ~(1<<31);
       }

       if((val = dbattr(m->attr, "display")) != nil){
               port = atoi(val)-1;
               if(igfx->type == TypeHSW && !igfx->dp[port-PortDPA].hdmi)
                       bpc = 6;
       }else if(dbattr(m->attr, "lcd") != nil)
               port = PortLCD;
       else
               port = PortVGA;

       trace("%s: display #%d\n", ctlr->name, port+1);

       switch(port){
       default:
       Badport:
               error("%s: display #%d not supported\n", ctlr->name, port+1);
               break;

       case PortVGA:
               if(igfx->type == TypeHSW)       /* unimplemented */
                       goto Badport;
               if(igfx->type == TypeG45)
                       x = (igfx->adpa.v >> 30) & 1;
               else
                       x = (igfx->adpa.v >> 29) & 3;
               igfx->adpa.v |= (1<<31);
               if(igfx->type == TypeG45){
                       igfx->adpa.v &= ~(3<<10);       /* Monitor DPMS: on */

                       igfx->adpa.v &= ~(1<<15);       /* ADPA Polarity Select */
                       igfx->adpa.v |= 3<<3;
                       if(m->hsync == '-')
                               igfx->adpa.v ^= 1<<3;
                       if(m->vsync == '-')
                               igfx->adpa.v ^= 1<<4;
               }
               break;

       case PortLCD:
               if(igfx->type == TypeHSW)
                       goto Badport;
               if(igfx->type == TypeG45)
                       x = (igfx->lvds.v >> 30) & 1;
               else
                       x = (igfx->lvds.v >> 29) & 3;
               igfx->lvds.v |= (1<<31);
               igfx->ppcontrol.v |= 5;

               if(igfx->type == TypeG45){
                       igfx->lvds.v &= ~(1<<24);       /* data format select 18/24bpc */

                       igfx->lvds.v &= ~(3<<20);
                       if(m->hsync == '-')
                               igfx->lvds.v ^= 1<<20;
                       if(m->vsync == '-')
                               igfx->lvds.v ^= 1<<21;

                       igfx->lvds.v |= (1<<15);        /* border enable */
               }
               break;

       case PortDPA:
       case PortDPB:
       case PortDPC:
       case PortDPD:
       case PortDPE:
               r = &igfx->dp[port - PortDPA].ctl;
               if(r->a == 0)
                       goto Badport;
               /* port enable */
               r->v |= 1<<31;
               /* use PIPE_A for displayport */
               x = 0;

               if(igfx->type == TypeHSW){
                       if(port == PortDPA){
                               /* only enable panel for eDP */
                               igfx->ppcontrol.v |= 5;
                               /* use eDP pipe */
                               x = 3;
                       }

                       /* reserved MBZ */
                       r->v &= ~(7<<28) & ~(1<<26) & ~(63<<19) & ~(3<<16) & ~(15<<11) & ~(1<<7) & ~31;
                       /* displayport SST mode */
                       r->v &= ~(1<<27);
                       /* link not in training, send normal pixels */
                       r->v |= 3<<8;
                       if(igfx->dp[port-PortDPA].hdmi){
                               /* hdmi: do not configure displayport */
                               r->a = 0;
                               r->v &= ~(1<<31);
                       }

                       r = &igfx->dp[port - PortDPA].bufctl;
                       /* buffer enable */
                       r->v |= 1<<31;
                       /* reserved MBZ */
                       r->v &= ~(7<<28) & ~(127<<17) & ~(255<<8) & ~(3<<5);
                       /* grab lanes shared with port e when using port a */
                       if(port == PortDPA)
                               r->v |= 1<<4;
                       else if(port == PortDPE)
                               igfx->dp[0].bufctl.v &= ~(1<<4);
                       /* dp port width */
                       r->v &= ~(15<<1);
                       if(!igfx->dp[port-PortDPA].hdmi){
                               nl = needlanes(m->frequency, 270*MHz, 3*bpc);
                               /* x4 unsupported on port e */
                               if(nl > 2 && port == PortDPE)
                                       goto Badport;
                               r->v |= nl-1 << 1;
                       }
                       /* port reversal: off */
                       r->v &= ~(1<<16);

                       /* buffer translation (vsl/pel) */
                       r = igfx->dp[port - PortDPA].buftrans;
                       r[1].v  = 0x0006000E;   r[0].v  = 0x00FFFFFF;
                       r[3].v  = 0x0005000A;   r[2].v  = 0x00D75FFF;
                       r[5].v  = 0x00040006;   r[4].v  = 0x00C30FFF;
                       r[7].v  = 0x000B0000;   r[6].v  = 0x80AAAFFF;
                       r[9].v  = 0x0005000A;   r[8].v  = 0x00FFFFFF;
                       r[11].v = 0x000C0004;   r[10].v = 0x00D75FFF;
                       r[13].v = 0x000B0000;   r[12].v = 0x80C30FFF;
                       r[15].v = 0x00040006;   r[14].v = 0x00FFFFFF;
                       r[17].v = 0x000B0000;   r[16].v = 0x80D75FFF;
                       r[19].v = 0x00040006;   r[18].v = 0x00FFFFFF;
                       break;
               }

               if(port == PortDPE)
                       goto Badport;

               /* port width selection */
               r->v &= ~(7<<19);
               r->v |= needlanes(m->frequency, 270*MHz, 3*bpc)-1 << 19;

               /* port reversal: off */
               r->v &= ~(1<<15);
               /* reserved MBZ */
               r->v &= ~(15<<11);
               /* displayport transcoder */
               if(port == PortDPA){
                       /* reserved MBZ */
                       r->v &= ~(15<<10);
                       /* pll frequency: 270mhz */
                       r->v &= ~(3<<16);
                       /* pll enable */
                       r->v |= 1<<14;
                       /* pipe select */
                       r->v &= ~(3<<29);
                       r->v |= x<<29;
               } else if(igfx->pipe[x].fdi->dpctl.a != 0){
                       /* reserved MBZ */
                       r->v &= ~(15<<11);
                       /* audio output: disable */
                       r->v &= ~(1<<6);
                       /* transcoder displayport configuration */
                       r = &igfx->pipe[x].fdi->dpctl;
                       /* transcoder enable */
                       r->v |= 1<<31;
                       /* port select: B,C,D */
                       r->v &= ~(3<<29);
                       r->v |= (port-PortDPB)<<29;
               }
               /* sync polarity */
               r->v |= 3<<3;
               if(m->hsync == '-')
                       r->v ^= 1<<3;
               if(m->vsync == '-')
                       r->v ^= 1<<4;
               break;
       }
       p = &igfx->pipe[x];

       /* plane enable, 32bpp */
       p->dsp->cntr.v = (1<<31) | (6<<26);
       if(igfx->type == TypeG45)
               p->dsp->cntr.v |= x<<24;        /* pipe assign */
       else
               p->dsp->cntr.v &= ~511; /* mbz */

       /* stride must be 64 byte aligned */
       p->dsp->stride.v = m->x * (m->z / 8);
       p->dsp->stride.v += 63;
       p->dsp->stride.v &= ~63;

       /* virtual width in pixels */
       vga->virtx = p->dsp->stride.v / (m->z / 8);

       /* plane position and size */
       p->dsp->pos.v = 0;
       p->dsp->size.v = (m->y - 1)<<16 | (m->x - 1);   /* sic */

       p->dsp->surf.v = 0;
       p->dsp->linoff.v = 0;
       p->dsp->tileoff.v = 0;
       p->dsp->leftsurf.v = 0;

       /* cursor plane off */
       p->cur->cntr.v = 0;
       if(igfx->type == TypeG45)
               p->cur->cntr.v |= x<<28;        /* pipe assign */
       p->cur->pos.v = 0;
       p->cur->base.v = 0;

       if(initdpll(igfx, x, m->frequency, port) < 0)
               error("%s: frequency %d out of range\n", ctlr->name, m->frequency);

       initpipe(igfx, p, m, bpc, port);

       ctlr->flag |= Finit;
}

static void
loadtrans(Igfx *igfx, Trans *t)
{
       int i;

       if(t->conf.a == 0)
               return;

       /* program trans/pipe timings */
       loadreg(igfx, t->ht);
       loadreg(igfx, t->hb);
       loadreg(igfx, t->hs);
       loadreg(igfx, t->vt);
       loadreg(igfx, t->vb);
       loadreg(igfx, t->vs);
       loadreg(igfx, t->vss);

       loadreg(igfx, t->dm[0]);
       loadreg(igfx, t->dn[0]);
       loadreg(igfx, t->lm[0]);
       loadreg(igfx, t->ln[0]);
       loadreg(igfx, t->dm[1]);
       loadreg(igfx, t->dn[1]);
       loadreg(igfx, t->lm[1]);
       loadreg(igfx, t->ln[1]);

       if(t->dpll != nil && igfx->type != TypeHSW){
               /* program dpll */
               t->dpll->ctrl.v &= ~(1<<31);
               loadreg(igfx, t->dpll->ctrl);
               loadreg(igfx, t->dpll->fp0);
               loadreg(igfx, t->dpll->fp1);

               /* enable dpll */
               t->dpll->ctrl.v |= (1<<31);
               loadreg(igfx, t->dpll->ctrl);
               sleep(10);
       }

       /* workarround: set timing override bit */
       csr(igfx, t->chicken.a, 0, 1<<31);

       /* enable displayport transcoder */
       loadreg(igfx, t->dpctl);

       /* enable trans/pipe */
       t->conf.v |= (1<<31);
       t->conf.v &= ~(1<<30);
       loadreg(igfx, t->conf);
       for(i=0; i<100; i++){
               sleep(10);
               if(rr(igfx, t->conf.a) & (1<<30))
                       break;
       }
}

static void
enablepipe(Igfx *igfx, int x)
{
       int i;
       Pipe *p;

       p = &igfx->pipe[x];
       if((p->conf.v & (1<<31)) == 0)
               return; /* pipe is disabled, done */

       /* select pipe clock */
       loadreg(igfx, p->clksel);

       if(p->fdi->rxctl.a != 0){
               p->fdi->rxctl.v &= ~(1<<31);
               p->fdi->rxctl.v &= ~(1<<4);     /* rawclk */
               p->fdi->rxctl.v |= (1<<13);     /* enable pll */
               loadreg(igfx, p->fdi->rxctl);
               sleep(5);
               p->fdi->rxctl.v |= (1<<4);      /* pcdclk */
               loadreg(igfx, p->fdi->rxctl);
               sleep(5);
               /* clear auto training bits */
               if(igfx->type == TypeSNB)
                       p->fdi->txctl.v &= ~(3<<28 | 1<<10 | 1);
               else
                       p->fdi->txctl.v &= ~(7<<8 | 1);
               p->fdi->txctl.v &= ~(1<<31);    /* disable */
               p->fdi->txctl.v |= (1<<14);     /* enable pll */
               loadreg(igfx, p->fdi->txctl);
               sleep(5);
       }

       /* image size (vga needs to be off) */
       loadreg(igfx, p->src);

       /* set panel fitter as needed */
       if(p->pfit != nil){
               loadreg(igfx, p->pfit->ctrl);
               loadreg(igfx, p->pfit->winpos);
               loadreg(igfx, p->pfit->winsize);        /* arm */
       }

       /* keep planes disabled while pipe comes up */
       if(igfx->type == TypeG45)
               p->conf.v |= 3<<18;

       /* enable cpu pipe */
       loadtrans(igfx, p);

       /* program plane */
       loadreg(igfx, p->dsp->cntr);
       loadreg(igfx, p->dsp->linoff);
       loadreg(igfx, p->dsp->stride);
       loadreg(igfx, p->dsp->tileoff);
       loadreg(igfx, p->dsp->size);
       loadreg(igfx, p->dsp->pos);
       loadreg(igfx, p->dsp->surf);    /* arm */
       loadreg(igfx, p->dsp->leftsurf);

       /* program cursor */
       loadreg(igfx, p->cur->cntr);
       loadreg(igfx, p->cur->pos);
       loadreg(igfx, p->cur->base);    /* arm */

       /* enable planes */
       if(igfx->type == TypeG45) {
               p->conf.v &= ~(3<<18);
               loadreg(igfx, p->conf);
       }

       if(p->fdi->rxctl.a != 0){
               /* enable fdi */
               loadreg(igfx, p->fdi->rxtu[1]);
               loadreg(igfx, p->fdi->rxtu[0]);
               loadreg(igfx, p->fdi->rxmisc);

               if(igfx->type == TypeSNB){
                       /* unmask bit lock and symbol lock bits */
                       csr(igfx, p->fdi->rximr.a, 3<<8, 0);

                       p->fdi->txctl.v &= ~(3<<28);    /* link train pattern1 */
                       p->fdi->txctl.v |= 1<<31;       /* enable */
                       loadreg(igfx, p->fdi->txctl);

                       p->fdi->rxctl.v &= ~(3<<8);     /* link train pattern1 */
                       p->fdi->rxctl.v |= 1<<31;       /* enable */
                       loadreg(igfx, p->fdi->rxctl);

                       /* wait for bit lock */
                       for(i=0; i<10; i++){
                               sleep(1);
                               if(rr(igfx, p->fdi->rxiir.a) & (1<<8))
                                       break;
                       }
                       csr(igfx, p->fdi->rxiir.a, 0, 1<<8);

                       /* switch to link train pattern2 */
                       csr(igfx, p->fdi->txctl.a, 3<<28, 1<<28);
                       csr(igfx, p->fdi->rxctl.a, 3<<8, 1<<8);

                       /* wait for symbol lock */
                       for(i=0; i<10; i++){
                               sleep(1);
                               if(rr(igfx, p->fdi->rxiir.a) & (1<<9))
                                       break;
                       }
                       csr(igfx, p->fdi->rxiir.a, 0, 1<<9);

                       /* switch to link train normal */
                       csr(igfx, p->fdi->txctl.a, 0, 3<<28);
                       csr(igfx, p->fdi->rxctl.a, 0, 3<<8);

                       /* wait idle pattern time */
                       sleep(5);
               } else {
                       p->fdi->rxctl.v &= ~(3<<8);     /* link train pattern 00 */
                       p->fdi->rxctl.v |= 1<<10;       /* auto train enable */
                       p->fdi->rxctl.v |= 1<<31;       /* enable */
                       loadreg(igfx, p->fdi->rxctl);

                       p->fdi->txctl.v &= ~(3<<8);     /* link train pattern 00 */
                       p->fdi->txctl.v |= 1<<10;       /* auto train enable */
                       p->fdi->txctl.v |= 1<<31;       /* enable */
                       loadreg(igfx, p->fdi->txctl);

                       /* wait for link training done */
                       for(i=0; i<200; i++){
                               sleep(5);
                               if(rr(igfx, p->fdi->txctl.a) & 2)
                                       break;
                       }
               }
       }

       /* enable the transcoder */
       loadtrans(igfx, p->fdi);
}

static void
disabletrans(Igfx *igfx, Trans *t)
{
       int i;

       /* deselect pipe clock */
       csr(igfx, t->clksel.a, 7<<29, 0);

       /* disable displayport transcoder */
       if(igfx->type == TypeHSW){
               csr(igfx, t->dpctl.a, 15<<28, 0);
               csr(igfx, t->ht.a, ~0, 0);
               csr(igfx, t->hb.a, ~0, 0);
               csr(igfx, t->hs.a, ~0, 0);
               csr(igfx, t->vt.a, ~0, 0);
               csr(igfx, t->vb.a, ~0, 0);
               csr(igfx, t->vs.a, ~0, 0);
               csr(igfx, t->vss.a, ~0, 0);
       }else
               csr(igfx, t->dpctl.a, 1<<31, 3<<29);

       /* disable transcoder / pipe */
       csr(igfx, t->conf.a, 1<<31, 0);
       for(i=0; i<100; i++){
               sleep(10);
               if((rr(igfx, t->conf.a) & (1<<30)) == 0)
                       break;
       }
       /* workarround: clear timing override bit */
       csr(igfx, t->chicken.a, 1<<31, 0);

       /* disable dpll  */
       if(igfx->type != TypeHSW && t->dpll != nil)
               csr(igfx, t->dpll->ctrl.a, 1<<31, 0);
}

static void
disablepipe(Igfx *igfx, int x)
{
       Pipe *p;

       p = &igfx->pipe[x];

       /* planes off */
       csr(igfx, p->dsp->cntr.a, 1<<31, 0);
       wr(igfx, p->dsp->surf.a, 0);    /* arm */
       /* cursor off */
       if(igfx->type == TypeHSW)
               csr(igfx, p->cur->cntr.a, 0x3F, 0);
       else
               csr(igfx, p->cur->cntr.a, 1<<5 | 7, 0);
       wr(igfx, p->cur->base.a, 0);    /* arm */

       /* display/overlay/cursor planes off */
       if(igfx->type == TypeG45)
               csr(igfx, p->conf.a, 0, 3<<18);
       csr(igfx, p->src.a, ~0, 0);

       /* disable cpu pipe */
       disabletrans(igfx, p);

       /* disable panel fitter */
       if(p->pfit != nil)
               csr(igfx, p->pfit->ctrl.a, 1<<31, 0);

       /* disable fdi transmitter and receiver */
       csr(igfx, p->fdi->txctl.a, 1<<31 | 1<<10, 0);
       csr(igfx, p->fdi->rxctl.a, 1<<31 | 1<<10, 0);

       /* disable pch transcoder */
       disabletrans(igfx, p->fdi);

       /* disable pch dpll enable bit */
       if(igfx->type != TypeHSW)
               csr(igfx, igfx->dpllsel[0].a, 8<<(x*4), 0);
}

void
checkgtt(Igfx *igfx, Mode *m)
{
       int fd, c;
       ulong n;
       char buf[64], *fl[5];
       u32int i, j, pa, nilpte, *gtt;

       if(igfx->mmio == nil)
               return;
       gtt = (u32int*)((uchar*)igfx->mmio + igfx->pci->mem[0].size/2);
       pa = (gtt[0] & ~((1<<12)-1)) + (1<<12) | 1;
       for(i=1; i<64*1024/4-4; i++, pa+=1<<12)
               if((gtt[i] & ~((1<<11)-1<<1)) != pa)
                       break;
       n = m->x * m->y * m->z / 8;
       if(i<<12 >= n)
               return;

       /* unmap pages past stolen memory */
       nilpte = gtt[64*1024/4-5];
       wr(igfx, 0x2170, 0);    /* flush write buffers */
       for(j=i; j<64*1024/4-5; j++){
               if((gtt[j] & 1) == 0 || (gtt[j] & ~((1<<11)-1<<1)) == pa)
                       break;
               pa = gtt[j];
               gtt[j] = nilpte;
       }
       wr(igfx, 0x2170, 0);    /* flush write buffers */

       trace("%s: mapping %lud additional bytes for requested mode\n", igfx->ctlr->name, n - (i<<12));
       snprint(buf, sizeof buf, "#g/igfxtra");
       if((fd = open(buf, OREAD)) >= 0){
               close(fd);
               if(remove(buf) < 0)
                       goto err;
       }
       if((fd = create(buf, OREAD, DMDIR|0777)) < 0)
               goto err;
       close(fd);
       strncat(buf, "/ctl", sizeof(buf)-strlen("/ctl"));
       if((fd = open(buf, ORDWR|OTRUNC)) < 0)
               goto err;
       snprint(buf, sizeof buf, "va 0x10000000 %#lux fixed", n - (i<<12));
       if(write(fd, buf, strlen(buf)) < 0){
               close(fd);
               goto err;
       }
       seek(fd, 0, 0);
       if((c = read(fd, buf, sizeof buf)) <= 0){
               close(fd);
               goto err;
       }
       close(fd);
       buf[c-1] = 0;
       if(getfields(buf, fl, nelem(fl), 0, " ") != nelem(fl)
       || (pa = strtoul(fl[4], nil, 16)) == 0){
               werrstr("invalid physical base address");
               goto err;
       }
       n >>= 12;
       wr(igfx, 0x2170, 0);    /* flush write buffers */
       for(; i<n; i++, pa+=1<<12)
               gtt[i] = pa | 1;
       wr(igfx, 0x2170, 0);    /* flush write buffers */
       return;
err:
       trace("%s: checkgtt: %r\n", igfx->ctlr->name);
}

static void
load(Vga* vga, Ctlr* ctlr)
{
       Igfx *igfx;
       int x, y;

       igfx = vga->private;

       /* power lcd off */
       if(igfx->ppcontrol.a != 0){
               csr(igfx, igfx->ppcontrol.a, 0xFFFF0005, 0xABCD0000);
               for(x=0; x<5000; x++){
                       sleep(10);
                       if((rr(igfx, igfx->ppstatus.a) & (1<<31)) == 0)
                               break;
               }
       }

       /* disable ports */
       csr(igfx, igfx->sdvob.a, (1<<29) | (1<<31), 0);
       csr(igfx, igfx->sdvoc.a, (1<<29) | (1<<31), 0);
       csr(igfx, igfx->adpa.a, 1<<31, 0);
       csr(igfx, igfx->lvds.a, 1<<31, 0);
       for(x = 0; x < nelem(igfx->dp); x++){
               csr(igfx, igfx->dp[x].ctl.a, 1<<31, 0);
               if(igfx->dp[x].bufctl.a != 0){
                       csr(igfx, igfx->dp[x].bufctl.a, 1<<31, 0);
                       /* wait for buffers to return to idle */
                       for(y=0; y<5; y++){
                               sleep(10);
                               if(rr(igfx, igfx->dp[x].bufctl.a) & 1<<7)
                                       break;
                       }
               }
       }
       for(x = 0; x < nelem(igfx->hdmi); x++)
               csr(igfx, igfx->hdmi[x].ctl.a, 1<<31, 0);

       /* disable vga plane */
       csr(igfx, igfx->vgacntrl.a, 0, 1<<31);

       /* turn off all pipes */
       for(x = 0; x < igfx->npipe; x++)
               disablepipe(igfx, x);

       /* check if enough memory has been mapped for requested mode */
       checkgtt(igfx, vga->mode);

       if(igfx->type == TypeG45){
               /* toggle dsp a on and off (from enable sequence) */
               csr(igfx, igfx->pipe[0].conf.a, 3<<18, 0);
               csr(igfx, igfx->pipe[0].dsp->cntr.a, 0, 1<<31);
               wr(igfx, igfx->pipe[0].dsp->surf.a, 0);         /* arm */
               csr(igfx, igfx->pipe[0].dsp->cntr.a, 1<<31, 0);
               wr(igfx, igfx->pipe[0].dsp->surf.a, 0);         /* arm */
               csr(igfx, igfx->pipe[0].conf.a, 0, 3<<18);
       }

       if(igfx->type == TypeHSW){
               /* deselect port clock */
               for(x=0; x<nelem(igfx->dpllsel); x++)
                       csr(igfx, igfx->dpllsel[x].a, 0, 7<<29);

               /* disable dpll's other than LCPLL */
               for(x=1; x<nelem(igfx->dpll); x++)
                       csr(igfx, igfx->dpll[x].ctrl.a, 1<<31, 0);
       }

       /* program new clock sources */
       loadreg(igfx, igfx->rawclkfreq);
       loadreg(igfx, igfx->drefctl);
       /* program cpu pll */
       if(igfx->type == TypeHSW)
               for(x=0; x<nelem(igfx->dpll); x++)
                       loadreg(igfx, igfx->dpll[x].ctrl);
       sleep(10);

       /* set lvds before enabling dpll */
       loadreg(igfx, igfx->lvds);

       /* new dpll setting */
       for(x=0; x<nelem(igfx->dpllsel); x++)
               loadreg(igfx, igfx->dpllsel[x]);

       /* program all pipes */
       for(x = 0; x < igfx->npipe; x++)
               enablepipe(igfx, x);

       /* program vga plane */
       loadreg(igfx, igfx->vgacntrl);

       /* program ports */
       loadreg(igfx, igfx->adpa);
       loadreg(igfx, igfx->sdvob);
       loadreg(igfx, igfx->sdvoc);
       for(x = 0; x < nelem(igfx->dp); x++){
               for(y=0; y<nelem(igfx->dp[x].buftrans); y++)
                       loadreg(igfx, igfx->dp[x].buftrans[y]);
               loadreg(igfx, igfx->dp[x].bufctl);
               if(enabledp(igfx, &igfx->dp[x]) < 0)
                       ctlr->flag |= Ferror;
       }

       /* program lcd power */
       loadreg(igfx, igfx->ppcontrol);

       ctlr->flag |= Fload;
}

static void
dumpreg(char *name, char *item, Reg r)
{
       if(r.a == 0)
               return;

       printitem(name, item);
       Bprint(&stdout, " [%.8ux] = %.8ux\n", r.a, r.v);
}

static void
dumphex(char *name, char *item, uchar *data, int len)
{
       int i;

       for(i=0; i<len; i++){
               if((i & 15) == 0){
                       if(i > 0)
                               Bprint(&stdout, "\n");
                       printitem(name, item);
                       Bprint(&stdout, " [%.2x] =", i);
               }
               Bprint(&stdout, " %.2X", data[i]);
       }
       Bprint(&stdout, "\n");
}

static void
dumptiming(char *name, Trans *t)
{
       int tu, m, n;

       if(t->dm[0].a != 0 && t->dm[0].v != 0){
               tu = 1+((t->dm[0].v >> 25) & 0x3f);
               printitem(name, "dm1 tu");
               Bprint(&stdout, " %d\n", tu);

               m = t->dm[0].v & 0xffffff;
               n = t->dn[0].v;
               if(n > 0){
                       printitem(name, "dm1/dn1");
                       Bprint(&stdout, " %f\n", (double)m / (double)n);
               }

               m = t->lm[0].v;
               n = t->ln[0].v;
               if(n > 0){
                       printitem(name, "lm1/ln1");
                       Bprint(&stdout, " %f\n", (double)m / (double)n);
               }
       }
}

static void
dumptrans(char *name, Trans *t)
{
       dumpreg(name, "conf", t->conf);

       dumpreg(name, "dm1", t->dm[0]);
       dumpreg(name, "dn1", t->dn[0]);
       dumpreg(name, "lm1", t->lm[0]);
       dumpreg(name, "ln1", t->ln[0]);
       dumpreg(name, "dm2", t->dm[1]);
       dumpreg(name, "dn2", t->dn[1]);
       dumpreg(name, "lm2", t->lm[1]);
       dumpreg(name, "ln2", t->ln[1]);

       dumptiming(name, t);

       dumpreg(name, "ht", t->ht);
       dumpreg(name, "hb", t->hb);
       dumpreg(name, "hs", t->hs);

       dumpreg(name, "vt", t->vt);
       dumpreg(name, "vb", t->vb);
       dumpreg(name, "vs", t->vs);
       dumpreg(name, "vss", t->vss);

       dumpreg(name, "dpctl", t->dpctl);
       dumpreg(name, "clksel", t->clksel);
}

static void
dumppipe(Igfx *igfx, int x)
{
       char name[32];
       Pipe *p;

       p = &igfx->pipe[x];

       snprint(name, sizeof(name), "%s pipe %c", igfx->ctlr->name, 'a'+x);
       dumpreg(name, "src", p->src);
       dumptrans(name, p);

       snprint(name, sizeof(name), "%s fdi %c", igfx->ctlr->name, 'a'+x);
       dumptrans(name, p->fdi);
       dumpreg(name, "txctl", p->fdi->txctl);
       dumpreg(name, "rxctl", p->fdi->rxctl);
       dumpreg(name, "rxmisc", p->fdi->rxmisc);
       dumpreg(name, "rxtu1", p->fdi->rxtu[0]);
       dumpreg(name, "rxtu2", p->fdi->rxtu[1]);

       snprint(name, sizeof(name), "%s dsp %c", igfx->ctlr->name, 'a'+x);
       dumpreg(name, "cntr", p->dsp->cntr);
       dumpreg(name, "linoff", p->dsp->linoff);
       dumpreg(name, "stride", p->dsp->stride);
       dumpreg(name, "surf", p->dsp->surf);
       dumpreg(name, "tileoff", p->dsp->tileoff);
       dumpreg(name, "leftsurf", p->dsp->leftsurf);
       dumpreg(name, "pos", p->dsp->pos);
       dumpreg(name, "size", p->dsp->size);

       snprint(name, sizeof(name), "%s cur %c", igfx->ctlr->name, 'a'+x);
       dumpreg(name, "cntr", p->cur->cntr);
       dumpreg(name, "base", p->cur->base);
       dumpreg(name, "pos", p->cur->pos);
}

static void
dumpdpll(Igfx *igfx, int x)
{
       int cref, m1, m2, n, p1, p2;
       uvlong freq;
       char name[32];
       Dpll *dpll;
       u32int m;

       dpll = &igfx->dpll[x];
       snprint(name, sizeof(name), "%s dpll %c", igfx->ctlr->name, 'a'+x);

       dumpreg(name, "ctrl", dpll->ctrl);
       dumpreg(name, "fp0", dpll->fp0);
       dumpreg(name, "fp1", dpll->fp1);

       if(igfx->type == TypeHSW)
               return;

       p2 = ((dpll->ctrl.v >> 13) & 3) == 3 ? 14 : 10;
       if(((dpll->ctrl.v >> 24) & 3) == 1)
               p2 >>= 1;
       m = (dpll->ctrl.v >> 16) & 0xFF;
       for(p1 = 1; p1 <= 8; p1++)
               if(m & (1<<(p1-1)))
                       break;
       printitem(name, "ctrl p1");
       Bprint(&stdout, " %d\n", p1);
       printitem(name, "ctrl p2");
       Bprint(&stdout, " %d\n", p2);

       n = (dpll->fp0.v >> 16) & 0x3f;
       m1 = (dpll->fp0.v >> 8) & 0x3f;
       m2 = (dpll->fp0.v >> 0) & 0x3f;

       cref = getcref(igfx, x);
       freq = ((uvlong)cref * (5*(m1+2) + (m2+2)) / (n+2)) / (p1 * p2);

       printitem(name, "fp0 m1");
       Bprint(&stdout, " %d\n", m1);
       printitem(name, "fp0 m2");
       Bprint(&stdout, " %d\n", m2);
       printitem(name, "fp0 n");
       Bprint(&stdout, " %d\n", n);

       printitem(name, "cref");
       Bprint(&stdout, " %d\n", cref);
       printitem(name, "fp0 freq");
       Bprint(&stdout, " %lld\n", freq);
}

static void
dump(Vga* vga, Ctlr* ctlr)
{
       char name[32];
       Igfx *igfx;
       int x, y;

       if((igfx = vga->private) == nil)
               return;

       for(x=0; x<igfx->npipe; x++)
               dumppipe(igfx, x);

       for(x=0; x<nelem(igfx->dpll); x++)
               dumpdpll(igfx, x);

       for(x=0; x<nelem(igfx->dpllsel); x++){
               snprint(name, sizeof(name), "%s dpllsel %c", ctlr->name, 'a'+x);
               dumpreg(name, "dpllsel", igfx->dpllsel[x]);
       }

       dumpreg(ctlr->name, "drefctl", igfx->drefctl);
       dumpreg(ctlr->name, "rawclkfreq", igfx->rawclkfreq);
       dumpreg(ctlr->name, "ssc4params", igfx->ssc4params);

       for(x=0; x<nelem(igfx->dp); x++){
               if(igfx->dp[x].ctl.a == 0)
                       continue;
               snprint(name, sizeof(name), "%s dp %c", ctlr->name, 'a'+x);
               dumpreg(name, "ctl", igfx->dp[x].ctl);
               dumpreg(name, "bufctl", igfx->dp[x].bufctl);
               dumpreg(name, "stat", igfx->dp[x].stat);
               dumphex(name, "dpcd", igfx->dp[x].dpcd, sizeof(igfx->dp[x].dpcd));
               for(y=0; y<nelem(igfx->dp[x].buftrans); y++){
                       snprint(name, sizeof(name), "%s buftrans %c %d", ctlr->name, 'a'+x, y);
                       dumpreg(name, "ctl", igfx->dp[x].buftrans[y]);
               }
       }
       for(x=0; x<nelem(igfx->hdmi); x++){
               snprint(name, sizeof(name), "%s hdmi %c", ctlr->name, 'a'+x);
               dumpreg(name, "ctl", igfx->hdmi[x].ctl);
       }

       for(x=0; x<nelem(igfx->pfit); x++){
               snprint(name, sizeof(name), "%s pfit %c", ctlr->name, 'a'+x);
               dumpreg(name, "ctrl", igfx->pfit[x].ctrl);
               dumpreg(name, "winpos", igfx->pfit[x].winpos);
               dumpreg(name, "winsize", igfx->pfit[x].winsize);
               dumpreg(name, "pwrgate", igfx->pfit[x].pwrgate);
       }

       dumpreg(ctlr->name, "ppcontrol", igfx->ppcontrol);
       dumpreg(ctlr->name, "ppstatus", igfx->ppstatus);

       dumpreg(ctlr->name, "adpa", igfx->adpa);
       dumpreg(ctlr->name, "lvds", igfx->lvds);
       dumpreg(ctlr->name, "sdvob", igfx->sdvob);
       dumpreg(ctlr->name, "sdvoc", igfx->sdvoc);

       dumpreg(ctlr->name, "vgacntrl", igfx->vgacntrl);
}

static int
dpauxio(Igfx *igfx, Dp *dp, uchar buf[20], int len)
{
       int t, i;
       u32int w;

       if(dp->auxctl.a == 0){
               werrstr("not present");
               return -1;
       }

       t = 0;
       while(rr(igfx, dp->auxctl.a) & (1<<31)){
               if(++t >= 10){
                       werrstr("busy");
                       return -1;
               }
               sleep(5);
       }

       /* clear sticky bits */
       wr(igfx, dp->auxctl.a, (1<<28) | (1<<25) | (1<<30));

       for(i=0; i<nelem(dp->auxdat); i++){
               w  = buf[i*4+0]<<24;
               w |= buf[i*4+1]<<16;
               w |= buf[i*4+2]<<8;
               w |= buf[i*4+3];
               wr(igfx, dp->auxdat[i].a, w);
       }

       /* 2X Bit Clock divider */
       w = ((dp == &igfx->dp[0]) ? igfx->cdclk : (igfx->rawclkfreq.v & 0x3ff)) >> 1;
       if(w < 1 || w > 0x3fd){
               werrstr("bad clock");
               return -1;
       }

       /* hack: slow down a bit */
       w += 2;

       w |= 1<<31;     /* SendBusy */
       w |= 1<<29;     /* interrupt disabled */
       w |= 3<<26;     /* timeout 1600µs */
       w |= len<<20;   /* send bytes */
       w |= 5<<16;     /* precharge time (5*2 = 10µs) */
       wr(igfx, dp->auxctl.a, w);

       t = 0;
       for(;;){
               w = rr(igfx, dp->auxctl.a);
               if((w & (1<<30)) != 0)
                       break;
               if(++t >= 10){
                       werrstr("busy");
                       return -1;
               }
               sleep(5);
       }
       if(w & (1<<28)){
               werrstr("receive timeout");
               return -1;
       }
       if(w & (1<<25)){
               werrstr("receive error");
               return -1;
       }

       len = (w >> 20) & 0x1f;
       for(i=0; i<nelem(dp->auxdat); i++){
               w = rr(igfx, dp->auxdat[i].a);
               buf[i*4+0] = w>>24;
               buf[i*4+1] = w>>16;
               buf[i*4+2] = w>>8;
               buf[i*4+3] = w;
       }

       return len;
}

enum {
       CmdNative       = 8,
       CmdMot          = 4,
       CmdRead         = 1,
       CmdWrite        = 0,
};

static int
dpauxtra(Igfx *igfx, Dp *dp, int cmd, int addr, uchar *data, int len)
{
       uchar buf[20];
       int r;

       assert(len <= 16);

       memset(buf, 0, sizeof(buf));
       buf[0] = (cmd << 4) | ((addr >> 16) & 0xF);
       buf[1] = addr >> 8;
       buf[2] = addr;
       buf[3] = len-1;
       r = 3;
       if(data != nil && len > 0){
               if((cmd & CmdRead) == 0)
                       memmove(buf+4, data, len);
               r = 4;
               if((cmd & CmdRead) == 0)
                       r += len;
       }
       if((r = dpauxio(igfx, dp, buf, r)) < 0){
               trace("%s: dpauxio: dp %c, cmd %x, addr %x, len %d: %r\n",
                       igfx->ctlr->name, 'a'+(int)(dp - &igfx->dp[0]), cmd, addr, len);
               return -1;
       }
       if(r == 0 || data == nil || len == 0)
               return 0;
       if((cmd & CmdRead) != 0){
               if(--r < len)
                       len = r;
               memmove(data, buf+1, len);
       }
       return len;
}

static int
rdpaux(Igfx *igfx, Dp *dp, int addr)
{
       uchar buf[1];
       if(dpauxtra(igfx, dp, CmdNative|CmdRead, addr, buf, 1) != 1)
               return -1;
       return buf[0];
}
static int
wdpaux(Igfx *igfx, Dp *dp, int addr, uchar val)
{
       if(dpauxtra(igfx, dp, CmdNative|CmdWrite, addr, &val, 1) != 1)
               return -1;
       return 0;
}

static int
enabledp(Igfx *igfx, Dp *dp)
{
       int try, r;
       u32int w;

       if(dp->ctl.a == 0)
               return 0;
       if((dp->ctl.v & (1<<31)) == 0)
               return 0;

       /* FIXME: always times out */
       if(igfx->type == TypeHSW && dp == &igfx->dp[0])
               goto Skip;

       /* Link configuration */
       wdpaux(igfx, dp, 0x100, (270*MHz) / 27000000);
       w = dp->ctl.v >> (igfx->type == TypeHSW ? 1 : 19) & 7;
       wdpaux(igfx, dp, 0x101, w+1);

       r = 0;

       /* Link training pattern 1 */
       dp->ctl.v &= ~(7<<8);
       loadreg(igfx, dp->ctl);
       for(try = 0;;try++){
               if(try > 5)
                       goto Fail;
               /* Link training pattern 1 */
               wdpaux(igfx, dp, 0x102, 0x01);
               sleep(100);
               if((r = rdpaux(igfx, dp, 0x202)) < 0)
                       goto Fail;
               if(r & 1)       /* LANE0_CR_DONE */
                       break;
       }
       trace("pattern1 finished: %x\n", r);

       /* Link training pattern 2 */
       dp->ctl.v &= ~(7<<8);
       dp->ctl.v |= 1<<8;
       loadreg(igfx, dp->ctl);
       for(try = 0;;try++){
               if(try > 5)
                       goto Fail;
               /* Link training pattern 2 */
               wdpaux(igfx, dp, 0x102, 0x02);
               sleep(100);
               if((r = rdpaux(igfx, dp, 0x202)) < 0)
                       goto Fail;
               if((r & 7) == 7)
                       break;
       }
       trace("pattern2 finished: %x\n", r);

       if(igfx->type == TypeHSW){
               /* set link training to idle pattern and wait for 5 idle
                * patterns */
               dp->ctl.v &= ~(7<<8);
               dp->ctl.v |= 2<<8;
               loadreg(igfx, dp->ctl);
               for(try=0; try<10; try++){
                       sleep(10);
                       if(rr(igfx, dp->stat.a) & (1<<25))
                               break;
               }
       }
Skip:
       /* stop training */
       dp->ctl.v &= ~(7<<8);
       dp->ctl.v |= 3<<8;
       loadreg(igfx, dp->ctl);
       wdpaux(igfx, dp, 0x102, 0x00);
       return 1;

Fail:
       trace("training failed: %x\n", r);

       /* disable port */
       dp->ctl.v &= ~(1<<31);
       loadreg(igfx, dp->ctl);
       wdpaux(igfx, dp, 0x102, 0x00);
       return -1;
}

static uchar*
edidshift(uchar buf[256])
{
       uchar tmp[256];
       int i;

       /* shift if neccesary so edid block is at the start */
       for(i=0; i<256-8; i++){
               if(buf[i+0] == 0x00 && buf[i+1] == 0xFF && buf[i+2] == 0xFF && buf[i+3] == 0xFF
               && buf[i+4] == 0xFF && buf[i+5] == 0xFF && buf[i+6] == 0xFF && buf[i+7] == 0x00){
                       memmove(tmp, buf, i);
                       memmove(buf, buf + i, 256 - i);
                       memmove(buf + (256 - i), tmp, i);
                       break;
               }
       }
       return buf;
}

static Edid*
snarfdpedid(Igfx *igfx, Dp *dp, int addr)
{
       uchar buf[256];
       int i;
       Edid *e;

       for(i=0; i<sizeof(dp->dpcd); i+=16)
               if(dpauxtra(igfx, dp, CmdNative|CmdRead, i, dp->dpcd+i, 16) != 16)
                       return nil;

       if(dp->dpcd[0] == 0)    /* nothing there, dont try to get edid */
               return nil;

       if(dpauxtra(igfx, dp, CmdMot|CmdRead, addr, nil, 0) < 0)
               return nil;

       for(i=0; i<sizeof(buf); i+=16){
               if(dpauxtra(igfx, dp, CmdMot|CmdRead, addr, buf+i, 16) == 16)
                       continue;
               if(dpauxtra(igfx, dp, CmdMot|CmdRead, addr, buf+i, 16) == 16)
                       continue;
               if(dpauxtra(igfx, dp, CmdMot|CmdRead, addr, buf+i, 16) == 16)
                       continue;
               if(dpauxtra(igfx, dp, CmdMot|CmdRead, addr, buf+i, 16) == 16)
                       continue;
               if(dpauxtra(igfx, dp, CmdMot|CmdRead, addr, buf+i, 16) == 16)
                       continue;
               return nil;
       }

       dpauxtra(igfx, dp, CmdRead, addr, nil, 0);

       if((e = parseedid128(edidshift(buf))) == nil)
               trace("%s: snarfdpedid: dp %c: %r\n", igfx->ctlr->name, 'a'+(int)(dp - &igfx->dp[0]));
       return e;
}

enum {
       GMBUSCP = 0,    /* Clock/Port selection */
       GMBUSCS = 1,    /* Command/Status */
       GMBUSST = 2,    /* Status Register */
       GMBUSDB = 3,    /* Data Buffer Register */
       GMBUSIM = 4,    /* Interrupt Mask */
       GMBUSIX = 5,    /* Index Register */
};

static int
gmbusread(Igfx *igfx, int port, int addr, uchar *data, int len)
{
       u32int x, y;
       int n, t;

       if(igfx->gmbus[GMBUSCP].a == 0)
               return -1;

       wr(igfx, igfx->gmbus[GMBUSCP].a, port);
       wr(igfx, igfx->gmbus[GMBUSIX].a, 0);

       /* bus cycle without index and stop, byte count, slave address, read */
       wr(igfx, igfx->gmbus[GMBUSCS].a, 1<<30 | 5<<25 | len<<16 | addr<<1 | 1);

       n = 0;
       while(len > 0){
               x = 0;
               for(t=0; t<100; t++){
                       x = rr(igfx, igfx->gmbus[GMBUSST].a);
                       if(x & (1<<11))
                               break;
                       sleep(5);
               }
               if((x & (1<<11)) == 0)
                       return -1;

               t = 4 - (x & 3);
               if(t > len)
                       t = len;
               len -= t;

               y = rr(igfx, igfx->gmbus[GMBUSDB].a);
               switch(t){
               case 4:
                       data[n++] = y & 0xff, y >>= 8;
               case 3:
                       data[n++] = y & 0xff, y >>= 8;
               case 2:
                       data[n++] = y & 0xff, y >>= 8;
               case 1:
                       data[n++] = y & 0xff;
               }
       }

       return n;
}

static Edid*
snarfgmedid(Igfx *igfx, int port, int addr)
{
       uchar buf[256];

       /* read twice */
       if(gmbusread(igfx, port, addr, buf, 128) != 128)
               return nil;
       if(gmbusread(igfx, port, addr, buf + 128, 128) != 128)
               return nil;

       return parseedid128(edidshift(buf));
}

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

Ctlr igfxhwgc = {
       "igfxhwgc",
};