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

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

/*
* 3Dfx.
*/
enum {
       dramInit0               = 0x018/4,
       dramInit1               = 0x01C/4,
       vgaInit0                = 0x028/4,
       pllCtrl0                = 0x040/4,
       pllCtrl1                = 0x044/4,
       pllCtrl2                = 0x048/4,
       dacMode                 = 0x04C/4,
       vidProcCfg              = 0x05C/4,
       vidScreenSize           = 0x098/4,
       vidDesktopOverlayStride = 0x0E8/4,

       Nior                    = 0x100/4,
};

typedef struct Tdfx {
       ulong   io;
       Pcidev* pci;

       ulong   r[Nior];
} Tdfx;

static ulong
io32r(Tdfx* tdfx, int r)
{
       return inportl(tdfx->io+(r*4));
}

static void
io32w(Tdfx* tdfx, int r, ulong l)
{
       outportl(tdfx->io+(r*4), l);
}

static void
snarf(Vga* vga, Ctlr* ctlr)
{
       int i;
       ulong v;
       Tdfx *tdfx;

       if(vga->private == nil){
               tdfx = alloc(sizeof(Tdfx));
               tdfx->pci = vga->pci;
               if(tdfx->pci == nil)
                       tdfx->pci = pcimatch(0, 0x121A, 0);
               if(tdfx->pci == nil || tdfx->pci->vid != 0x121A)
                       error("%s: not found\n", ctlr->name);
               switch(tdfx->pci->did){
               default:
                       error("%s: unknown chip - DID %4.4uX\n",
                               ctlr->name, tdfx->pci->did);
                       break;
               case 0x0003:            /* Banshee */
                       vga->f[1] = 270000000;
                       break;
               case 0x0005:            /* Avenger (a.k.a. Voodoo3) */
                       vga->f[1] = 300000000;
                       break;
               case 0x0009:            /* Voodoo5 */
                       vga->f[1] = 350000000;
                       break;
               }
               /*
                * Frequency output of PLL's is given by
                *      fout = RefFreq*(n+2)/((m+2)*2^p)
                * where there are 6 bits for m, 8 bits for n
                * and 2 bits for p (k).
                */
               vga->m[1] = 64;
               vga->n[1] = 256;
               vga->p[1] = 4;

               if((v = (tdfx->pci->mem[2].bar & ~0x3)) == 0)
                       error("%s: I/O not mapped\n", ctlr->name);
               tdfx->io = v;

               vga->private = tdfx;
       }
       tdfx = vga->private;

       vga->crt[0x1A] = vgaxi(Crtx, 0x1A);
       vga->crt[0x1B] = vgaxi(Crtx, 0x1B);
       for(i = 0; i < Nior; i++)
               tdfx->r[i] = io32r(tdfx, i);

       /*
        * If SDRAM then there's 16MB memory else it's SGRAM
        * and can count it based on the power-on straps -
        * chip size can be 8Mb or 16Mb, and there can be 4 or
        * 8 of them.
        */
       vga->vma = tdfx->pci->mem[1].size;
       if(tdfx->r[dramInit1] & 0x40000000)
               vga->vmz = 16*1024*1024;
       else{
               if(tdfx->r[dramInit0] & 0x08000000)
                       i = 16*1024*1024/8;
               else
                       i = 8*1024*1024/8;
               if(tdfx->r[dramInit0] & 0x04000000)
                       i *= 8;
               else
                       i *= 4;
               vga->vmz = i;
       }

       ctlr->flag |= Fsnarf;
}

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

static void
tdfxclock(Vga* vga, Ctlr*)
{
       int d, dmin;
       uint f, m, n, p;

       dmin = vga->f[0];
       for(m = 1; m < vga->m[1]; m++){
               for(n = 1; n < vga->n[1]; n++){
                       f = (RefFreq*(n+2))/(m+2);
                       for(p = 0; p < vga->p[1]; p++){
                               d = vga->f[0] - (f/(1<<p));
                               if(d < 0)
                                       d = -d;
                               if(d >= dmin)
                                       continue;
                               dmin = d;
                               vga->m[0] = m;
                               vga->n[0] = n;
                               vga->p[0] = p;
                       }
               }
       }
}

static void
init(Vga* vga, Ctlr* ctlr)
{
       int x;
       Mode *mode;
       Tdfx *tdfx;

       mode = vga->mode;
       tdfx = vga->private;

       if(vga->linear && (ctlr->flag & Hlinear))
               ctlr->flag |= Ulinear;

       /*
        * Clock bits. If the desired video clock is
        * one of the two standard VGA clocks or 50MHz it can just be
        * set using bits <3:2> of vga->misc, otherwise we
        * need to programme the PLL.
        */
       if(vga->f[0] == 0)
               vga->f[0] = mode->frequency;
       vga->misc &= ~0x0C;
       if(vga->f[0] == VgaFreq0){
               /* nothing to do */;
       }
       else if(vga->f[0] == VgaFreq1)
               vga->misc |= 0x04;
       else if(vga->f[0] == 50000000)
               vga->misc |= 0x08;
       else{
               if(vga->f[0] > vga->f[1])
                       error("%s: invalid pclk - %lud\n",
                               ctlr->name, vga->f[0]);
               if(vga->f[0] > 135000000 && (ctlr->flag & Hclk2)){
                       if(mode->x%16)
                               error("%s: f > 135MHz requires (x%%16) == 0\n",
                                       ctlr->name);
                       ctlr->flag |= Uclk2;
               }
               tdfxclock(vga, ctlr);

               tdfx->r[pllCtrl0] = (vga->n[0]<<8)|(vga->m[0]<<2)|vga->p[0];
               vga->misc |= 0x0C;
       }

       /*
        * Pixel format and memory stride.
        */
       tdfx->r[vidScreenSize] = (mode->y<<12)|mode->x;
       tdfx->r[vidProcCfg] = 0x00000081;
       switch(mode->z){
       default:
               error("%s: %d-bit mode not supported\n", ctlr->name, mode->z);
               break;
       case 8:
               tdfx->r[vidDesktopOverlayStride] = mode->x;
               break;
       case 16:
               tdfx->r[vidDesktopOverlayStride] = mode->x*2;
               tdfx->r[vidProcCfg] |= 0x00040400;
               break;
       case 32:
               tdfx->r[vidDesktopOverlayStride] = mode->x*4;
               tdfx->r[vidProcCfg] |= 0x000C0400;
               break;
       }
       tdfx->r[vgaInit0] = 0x140;

       /*
        * Adjust horizontal timing if doing two screen pixels per clock.
        */
       tdfx->r[dacMode] = 0;
       if(ctlr->flag & Uclk2){
               vga->crt[0x00] = ((mode->ht/2)>>3)-5;
               vga->crt[0x01] = ((mode->x/2)>>3)-1;
               vga->crt[0x02] = ((mode->shb/2)>>3)-1;

               x = (mode->ehb/2)>>3;
               vga->crt[0x03] = 0x80|(x & 0x1F);
               vga->crt[0x04] = (mode->shs/2)>>3;
               vga->crt[0x05] = ((mode->ehs/2)>>3) & 0x1F;
               if(x & 0x20)
                       vga->crt[0x05] |= 0x80;

               tdfx->r[dacMode] |= 0x01;
               tdfx->r[vidProcCfg] |= 0x04000000;
       }

       /*
        * Overflow.
        */
       vga->crt[0x1A] = 0x00;
       if(vga->crt[0x00] & 0x100)
               vga->crt[0x1A] |= 0x01;
       if(vga->crt[0x01] & 0x100)
               vga->crt[0x1A] |= 0x04;
       if(vga->crt[0x03] & 0x100)
               vga->crt[0x1A] |= 0x10;
       x = mode->ehb;
       if(ctlr->flag & Uclk2)
               x /= 2;
       if((x>>3) & 0x40)
               vga->crt[0x1A] |= 0x20;
       if(vga->crt[0x04] & 0x100)
               vga->crt[0x1A] |= 0x40;
       x = mode->ehs;
       if(ctlr->flag & Uclk2)
               x /= 2;
       if((x>>3) & 0x20)
               vga->crt[0x1A] |= 0x80;

       vga->crt[0x1B] = 0x00;
       if(vga->crt[0x06] & 0x400)
               vga->crt[0x1B] |= 0x01;
       if(vga->crt[0x12] & 0x400)
               vga->crt[0x1B] |= 0x04;
       if(vga->crt[0x15] & 0x400)
               vga->crt[0x1B] |= 0x10;
       if(vga->crt[0x10] & 0x400)
               vga->crt[0x1B] |= 0x40;

       vga->attribute[0x11] = Pblack;

       ctlr->flag |= Finit;
}

static void
load(Vga* vga, Ctlr* ctlr)
{
       Tdfx *tdfx;

       vgaxo(Crtx, 0x1A, vga->crt[0x1A]);
       vgaxo(Crtx, 0x1B, vga->crt[0x1B]);

       tdfx = vga->private;
       io32w(tdfx, dacMode, tdfx->r[dacMode]);
       io32w(tdfx, vidScreenSize, tdfx->r[vidScreenSize]);
       io32w(tdfx, vidDesktopOverlayStride, tdfx->r[vidDesktopOverlayStride]);
       io32w(tdfx, vidProcCfg, tdfx->r[vidProcCfg]);
       io32w(tdfx, vgaInit0, tdfx->r[vgaInit0]);

       if((vga->misc & 0x0C) == 0x0C)
               io32w(tdfx, pllCtrl0, tdfx->r[pllCtrl0]);

       ctlr->flag |= Fload;
}

static uint
pllctrl(Tdfx* tdfx, int pll)
{
       uint k, m, n, r;

       r = tdfx->r[pllCtrl0+pll];
       k = r & 0x03;
       m = (r>>2) & 0x3F;
       n = (r>>8) & 0xFF;

       return (RefFreq*(n+2))/((m+2)*(1<<k));
}

static void
dump(Vga* vga, Ctlr* ctlr)
{
       int i;
       Tdfx *tdfx;

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

       printitem(ctlr->name, "Crt1A");
       printreg(vga->crt[0x1A]);
       printreg(vga->crt[0x1B]);

       Bprint(&stdout, "\n");
       for(i = 0; i < Nior; i++)
               Bprint(&stdout, "%s %2.2uX\t%.8luX\n",
                       ctlr->name, i*4, tdfx->r[i]);

       printitem(ctlr->name, "pllCtrl");
       Bprint(&stdout, "%9ud %8ud\n", pllctrl(tdfx, 0), pllctrl(tdfx, 1));
}

Ctlr tdfx = {
       "3dfx",                         /* name */
       snarf,                          /* snarf */
       options,                        /* options */
       init,                           /* init */
       load,                           /* load */
       dump,                           /* dump */
};

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