#include <u.h>
#include <libc.h>
#include <bio.h>
#include "pci.h"
#include "vga.h"
/*
* ATI Mach64 family.
*/
enum {
HTotalDisp,
HSyncStrtWid,
VTotalDisp,
VSyncStrtWid,
VlineCrntVline,
OffPitch,
IntCntl,
CrtcGenCntl,
OvrClr,
OvrWidLR,
OvrWidTB,
CurClr0,
CurClr1,
CurOffset,
CurHVposn,
CurHVoff,
ScratchReg0,
ScratchReg1, /* Scratch Register (BIOS info) */
ClockCntl,
BusCntl,
MemCntl,
ExtMemCntl,
MemVgaWpSel,
MemVgaRpSel,
DacRegs,
DacCntl,
GenTestCntl,
ConfigCntl, /* Configuration control */
ConfigChipId,
ConfigStat0, /* Configuration status 0 */
ConfigStat1, /* Configuration status 1 */
ConfigStat2,
DspConfig, /* Rage */
DspOnOff, /* Rage */
DpBkgdClr,
DpChainMsk,
DpFrgdClr,
DpMix,
DpPixWidth,
DpSrc,
DpWriteMsk,
LcdIndex,
LcdData,
Nreg,
TvIndex = 0x1D,
TvData = 0x27,
LCD_ConfigPanel = 0,
LCD_GenCtrl,
LCD_DstnCntl,
LCD_HfbPitchAddr,
LCD_HorzStretch,
LCD_VertStretch,
LCD_ExtVertStretch,
LCD_LtGio,
LCD_PowerMngmnt,
LCD_ZvgPio,
Nlcd,
};
static char* iorname[Nreg] = {
"HTotalDisp",
"HSyncStrtWid",
"VTotalDisp",
"VSyncStrtWid",
"VlineCrntVline",
"OffPitch",
"IntCntl",
"CrtcGenCntl",
"OvrClr",
"OvrWidLR",
"OvrWidTB",
"CurClr0",
"CurClr1",
"CurOffset",
"CurHVposn",
"CurHVoff",
"ScratchReg0",
"ScratchReg1",
"ClockCntl",
"BusCntl",
"MemCntl",
"ExtMemCntl",
"MemVgaWpSel",
"MemVgaRpSel",
"DacRegs",
"DacCntl",
"GenTestCntl",
"ConfigCntl",
"ConfigChipId",
"ConfigStat0",
"ConfigStat1",
"ConfigStat2",
"DspConfig",
"DspOnOff",
"DpBkgdClr",
"DpChainMsk",
"DpFrgdClr",
"DpMix",
"DpPixWidth",
"DpSrc",
"DpWriteMsk",
"LcdIndex",
"LcdData",
};
static char* lcdname[Nlcd] = {
"LCD ConfigPanel",
"LCD GenCntl",
"LCD DstnCntl",
"LCD HfbPitchAddr",
"LCD HorzStretch",
"LCD VertStretch",
"LCD ExtVertStretch",
"LCD LtGio",
"LCD PowerMngmnt",
"LCD ZvgPio"
};
/*
* Crummy hack: all io register offsets
* here get IOREG or'ed in, so that we can
* tell the difference between an uninitialized
* array entry and HTotalDisp.
*/
enum {
IOREG = 0x10000,
};
static ushort ioregs[Nreg] = {
[HTotalDisp] IOREG|0x0000,
[HSyncStrtWid] IOREG|0x0100,
[VTotalDisp] IOREG|0x0200,
[VSyncStrtWid] IOREG|0x0300,
[VlineCrntVline] IOREG|0x0400,
[OffPitch] IOREG|0x0500,
[IntCntl] IOREG|0x0600,
[CrtcGenCntl] IOREG|0x0700,
[OvrClr] IOREG|0x0800,
[OvrWidLR] IOREG|0x0900,
[OvrWidTB] IOREG|0x0A00,
[CurClr0] IOREG|0x0B00,
[CurClr1] IOREG|0x0C00,
[CurOffset] IOREG|0x0D00,
[CurHVposn] IOREG|0x0E00,
[CurHVoff] IOREG|0x0F00,
[ScratchReg0] IOREG|0x1000,
[ScratchReg1] IOREG|0x1100,
[ClockCntl] IOREG|0x1200,
[BusCntl] IOREG|0x1300,
[MemCntl] IOREG|0x1400,
[MemVgaWpSel] IOREG|0x1500,
[MemVgaRpSel] IOREG|0x1600,
[DacRegs] IOREG|0x1700,
[DacCntl] IOREG|0x1800,
[GenTestCntl] IOREG|0x1900,
[ConfigCntl] IOREG|0x1A00,
[ConfigChipId] IOREG|0x1B00,
[ConfigStat0] IOREG|0x1C00,
[ConfigStat1] IOREG|0x1D00,
/* [GpIo] IOREG|0x1E00, */
/* [HTotalDisp] IOREG|0x1F00, duplicate, says XFree86 */
};
static ushort pciregs[Nreg] = {
[HTotalDisp] 0x00,
[HSyncStrtWid] 0x01,
[VTotalDisp] 0x02,
[VSyncStrtWid] 0x03,
[VlineCrntVline] 0x04,
[OffPitch] 0x05,
[IntCntl] 0x06,
[CrtcGenCntl] 0x07,
[DspConfig] 0x08,
[DspOnOff] 0x09,
[OvrClr] 0x10,
[OvrWidLR] 0x11,
[OvrWidTB] 0x12,
[CurClr0] 0x18,
[CurClr1] 0x19,
[CurOffset] 0x1A,
[CurHVposn] 0x1B,
[CurHVoff] 0x1C,
[ScratchReg0] 0x20,
[ScratchReg1] 0x21,
[ClockCntl] 0x24,
[BusCntl] 0x28,
[LcdIndex] 0x29,
[LcdData] 0x2A,
[ExtMemCntl] 0x2B,
[MemCntl] 0x2C,
[MemVgaWpSel] 0x2D,
[MemVgaRpSel] 0x2E,
[DacRegs] 0x30,
[DacCntl] 0x31,
[GenTestCntl] 0x34,
[ConfigCntl] 0x37,
[ConfigChipId] 0x38,
[ConfigStat0] 0x39,
[ConfigStat1] 0x25, /* rsc: was 0x3A, but that's not what the LT manual says */
[ConfigStat2] 0x26,
[DpBkgdClr] 0xB0,
[DpChainMsk] 0xB3,
[DpFrgdClr] 0xB1,
[DpMix] 0xB5,
[DpPixWidth] 0xB4,
[DpSrc] 0xB6,
[DpWriteMsk] 0xB2,
};
enum {
PLLm = 0x02,
PLLp = 0x06,
PLLn0 = 0x07,
PLLn1 = 0x08,
PLLn2 = 0x09,
PLLn3 = 0x0A,
PLLx = 0x0B, /* external divisor (Rage) */
Npll = 32,
Ntv = 1, /* actually 256, but not used */
};
typedef struct Mach64xx Mach64xx;
struct Mach64xx {
ulong io;
Pcidev* pci;
int bigmem;
int lcdon;
int lcdpanelid;
ulong reg[Nreg];
ulong lcd[Nlcd];
ulong tv[Ntv];
uchar pll[Npll];
ulong (*ior32)(Mach64xx*, int);
void (*iow32)(Mach64xx*, int, ulong);
};
static ulong
portior32(Mach64xx* mp, int r)
{
if((ioregs[r] & IOREG) == 0)
return ~0;
return inportl(((ioregs[r] & ~IOREG)<<2)+mp->io);
}
static void
portiow32(Mach64xx* mp, int r, ulong l)
{
if((ioregs[r] & IOREG) == 0)
return;
outportl(((ioregs[r] & ~IOREG)<<2)+mp->io, l);
}
static ulong
pciior32(Mach64xx* mp, int r)
{
return inportl((pciregs[r]<<2)+mp->io);
}
static void
pciiow32(Mach64xx* mp, int r, ulong l)
{
outportl((pciregs[r]<<2)+mp->io, l);
}
static uchar
pllr(Mach64xx* mp, int r)
{
int io;
if(mp->ior32 == portior32)
io = ((ioregs[ClockCntl]&~IOREG)<<2)+mp->io;
else
io = (pciregs[ClockCntl]<<2)+mp->io;
outportb(io+1, r<<2);
return inportb(io+2);
}
static void
pllw(Mach64xx* mp, int r, uchar b)
{
int io;
if(mp->ior32 == portior32)
io = ((ioregs[ClockCntl]&~IOREG)<<2)+mp->io;
else
io = (pciregs[ClockCntl]<<2)+mp->io;
outportb(io+1, (r<<2)|0x02);
outportb(io+2, b);
}
static ulong
lcdr32(Mach64xx *mp, ulong r)
{
ulong or;
or = mp->ior32(mp, LcdIndex);
mp->iow32(mp, LcdIndex, (or&~0x0F) | (r&0x0F));
return mp->ior32(mp, LcdData);
}
static void
lcdw32(Mach64xx *mp, ulong r, ulong v)
{
ulong or;
or = mp->ior32(mp, LcdIndex);
mp->iow32(mp, LcdIndex, (or&~0x0F) | (r&0x0F));
mp->iow32(mp, LcdData, v);
}
static ulong
tvr32(Mach64xx *mp, ulong r)
{
outportb(mp->io+(TvIndex<<2), r&0x0F);
return inportl(mp->io+(TvData<<2));
}
static void
tvw32(Mach64xx *mp, ulong r, ulong v)
{
outportb(mp->io+(TvIndex<<2), r&0x0F);
outportl(mp->io+(TvData<<2), v);
}
static int smallmem[] = {
512*1024, 1024*1024, 2*1024*1024, 4*1024*1024,
6*1024*1024, 8*1024*1024, 12*1024*1024, 16*1024*1024,
};
static int bigmem[] = {
512*1024, 2*512*1024, 3*512*1024, 4*512*1024,
5*512*1024, 6*512*1024, 7*512*1024, 8*512*1024,
5*1024*1024, 6*1024*1024, 7*1024*1024, 8*1024*1024,
10*1024*1024, 12*1024*1024, 14*1024*1024, 16*1024*1024,
};
static void
snarf(Vga* vga, Ctlr* ctlr)
{
Mach64xx *mp;
int i;
ulong v;
if(vga->private == nil){
vga->private = alloc(sizeof(Mach64xx));
mp = vga->private;
mp->io = 0x2EC;
mp->ior32 = portior32;
mp->iow32 = portiow32;
mp->pci = pcimatch(0, 0x1002, 0);
if (mp->pci) {
if(v = mp->pci->mem[1].bar & ~0x3) {
mp->io = v;
mp->ior32 = pciior32;
mp->iow32 = pciiow32;
}
}
}
mp = vga->private;
for(i = 0; i < Nreg; i++)
mp->reg[i] = mp->ior32(mp, i);
for(i = 0; i < Npll; i++)
mp->pll[i] = pllr(mp, i);
switch(mp->reg[ConfigChipId] & 0xFFFF){
default:
mp->lcdpanelid = 0;
break;
case ('L'<<8)|'B': /* 4C42: Rage LTPro AGP */
case ('L'<<8)|'I': /* 4C49: Rage 3D LTPro */
case ('L'<<8)|'M': /* 4C4D: Rage Mobility */
case ('L'<<8)|'P': /* 4C50: Rage 3D LTPro */
for(i = 0; i < Nlcd; i++)
mp->lcd[i] = lcdr32(mp, i);
if(mp->lcd[LCD_GenCtrl] & 0x02)
mp->lcdon = 1;
mp->lcdpanelid = ((mp->reg[ConfigStat2]>>14) & 0x1F);
break;
}
/*
* Check which memory size map we are using.
*/
mp->bigmem = 0;
switch(mp->reg[ConfigChipId] & 0xFFFF){
case ('G'<<8)|'B': /* 4742: 264GT PRO */
case ('G'<<8)|'D': /* 4744: 264GT PRO */
case ('G'<<8)|'I': /* 4749: 264GT PRO */
case ('G'<<8)|'M': /* 474D: Rage XL */
case ('G'<<8)|'P': /* 4750: 264GT PRO */
case ('G'<<8)|'Q': /* 4751: 264GT PRO */
case ('G'<<8)|'R': /* 4752: */
case ('G'<<8)|'U': /* 4755: 264GT DVD */
case ('G'<<8)|'V': /* 4756: Rage2C */
case ('G'<<8)|'Z': /* 475A: Rage2C */
case ('V'<<8)|'U': /* 5655: 264VT3 */
case ('V'<<8)|'V': /* 5656: 264VT4 */
case ('L'<<8)|'B': /* 4C42: Rage LTPro AGP */
case ('L'<<8)|'I': /* 4C49: Rage 3D LTPro */
case ('L'<<8)|'M': /* 4C4D: Rage Mobility */
case ('L'<<8)|'P': /* 4C50: Rage 3D LTPro */
mp->bigmem = 1;
break;
case ('G'<<8)|'T': /* 4754: 264GT[B] */
case ('V'<<8)|'T': /* 5654: 264VT/GT/VTB */
/*
* Only the VTB and GTB use the new memory encoding,
* and they are identified by a nonzero ChipVersion,
* apparently.
*/
if((mp->reg[ConfigChipId] >> 24) & 0x7)
mp->bigmem = 1;
break;
}
/*
* Memory size and aperture. It's recommended
* to use an 8Mb aperture on a 16Mb boundary.
*/
if(mp->bigmem)
vga->vmz = bigmem[mp->reg[MemCntl] & 0x0F];
else
vga->vmz = smallmem[mp->reg[MemCntl] & 0x07];
vga->vma = 16*1024*1024;
switch(mp->reg[ConfigCntl]&0x3){
case 0:
vga->apz = 16*1024*1024; /* empirical -rsc */
break;
case 1:
vga->apz = 4*1024*1024;
break;
case 2:
vga->apz = 8*1024*1024;
break;
case 3:
vga->apz = 2*1024*1024; /* empirical: mach64GX -rsc */
break;
}
ctlr->flag |= Fsnarf;
}
static void
options(Vga*, Ctlr* ctlr)
{
ctlr->flag |= Hlinear|Foptions;
}
static void
clock(Vga* vga, Ctlr* ctlr)
{
int clk, m, n, p;
double f, q;
Mach64xx *mp;
mp = vga->private;
/*
* Don't compute clock timings for LCD panels.
* Just use what's already there. We can't just use
* the frequency in the vgadb for this because
* the frequency being programmed into the PLLs
* is not the frequency being used to compute the DSP
* settings. The DSP-relevant frequency is the one
* we keep in /lib/vgadb.
*/
if(mp->lcdon){
clk = mp->reg[ClockCntl] & 0x03;
n = mp->pll[7+clk];
p = (mp->pll[6]>>(clk*2)) & 0x03;
p |= (mp->pll[11]>>(2+clk)) & 0x04;
switch(p){
case 0:
case 1:
case 2:
case 3:
p = 1<<p;
break;
case 4+0:
p = 3;
break;
case 4+2:
p = 6;
break;
case 4+3:
p = 12;
break;
default:
case 4+1:
p = -1;
break;
}
m = mp->pll[PLLm];
f = (2.0*RefFreq*n)/(m*p) + 0.5;
vga->m[0] = m;
vga->p[0] = p;
vga->n[0] = n;
vga->f[0] = f;
return;
}
if(vga->f[0] == 0)
vga->f[0] = vga->mode->frequency;
f = vga->f[0];
/*
* To generate a specific output frequency, the reference (m),
* feedback (n), and post dividers (p) must be loaded with the
* appropriate divide-down ratios. In the following r is the
* XTALIN frequency (usually RefFreq) and t is the target frequency
* (vga->f).
*
* Use the maximum reference divider left by the BIOS for now,
* otherwise MCLK might be a concern. It can be calculated as
* follows:
* Upper Limit of PLL Lock Range
* Minimum PLLREFCLK = -----------------------------
* (2*255)
*
* XTALIN
* m = Floor[-----------------]
* Minimum PLLREFCLK
*
* For an upper limit of 135MHz and XTALIN of 14.318MHz m
* would be 54.
*/
m = mp->pll[PLLm];
vga->m[0] = m;
/*
* The post divider may be 1, 2, 4 or 8 and is determined by
* calculating
* t*m
* q = -----
* (2*r)
* and using the result to look-up p.
*/
q = (f*m)/(2*RefFreq);
if(ctlr->flag&Uenhanced){
if(q > 255 || q < 10.6666666667)
error("%s: vclk %lud out of range\n", ctlr->name, vga->f[0]);
if(q > 127.5)
p = 1;
else if(q > 85)
p = 2;
else if(q > 63.75)
p = 3;
else if(q > 42.5)
p = 4;
else if(q > 31.875)
p = 6;
else if(q > 21.25)
p = 8;
else
p = 12;
}else{
if(q > 255 || q < 16)
error("%s: vclk %lud out of range\n", ctlr->name, vga->f[0]);
if(q >= 127.5)
p = 1;
else if(q >= 63.5)
p = 2;
else if(q >= 31.5)
p = 4;
else
p = 8;
}
vga->p[0] = p;
/*
* The feedback divider should be kept in the range 0x80 to 0xFF
* and is found from
* n = q*p
* rounded to the nearest whole number.
*/
vga->n[0] = (q*p)+0.5;
}
typedef struct Meminfo Meminfo;
struct Meminfo {
int latency;
int latch;
int trp; /* filled in from card */
int trcd; /* filled in from card */
int tcrd; /* filled in from card */
int tras; /* filled in from card */
};
enum {
Mdram,
Medo,
Msdram,
Mwram,
};
/*
* The manuals and documentation are silent on which settings
* to use for Mwdram, or how to tell which to use.
*/
static Meminfo meminfo[] = {
[Mdram] { 1, 0 },
[Medo] { 1, 2 },
[Msdram] { 3, 1 },
[Mwram] { 1, 3 }, /* non TYPE_A */
};
static ushort looplatencytab[2][2] = {
{ 8, 6 }, /* DRAM: ≤1M, > 1M */
{ 9, 8 }, /* SDRAM: ≤1M, > 1M */
};
static ushort cyclesperqwordtab[2][2] = {
{ 3, 2 }, /* DRAM: ≤1M, > 1M */
{ 2, 1 }, /* SDRAM: ≤1M, > 1M */
};
static int memtype[] = {
-1, /* disable memory access */
Mdram, /* basic DRAM */
Medo, /* EDO */
Medo, /* hyper page DRAM or EDO */
Msdram, /* SDRAM */
Msdram, /* SGRAM */
Mwram,
Mwram
};
/*
* Calculate various memory parameters so that the card
* fetches the right bytes at the right time. I don't claim to
* understand the actual calculations very well.
*
* This is remarkably useful on laptops, since knowledge of
* x lets us find the frequency that the screen is really running
* at, which is not necessarily in the VCLKs.
*/
static void
setdsp(Vga* vga, Ctlr*)
{
Mach64xx *mp;
Meminfo *mem;
ushort table, memclk, memtyp;
int i, prec, xprec, fprec;
ulong t;
double pw, x, fifosz, fifoon, fifooff;
ushort dspon, dspoff;
int afifosz, lat, ncycle, pfc, rcc;
mp = vga->private;
/*
* Get video ram configuration from BIOS and chip
*/
table = *(ushort*)readbios(sizeof table, 0xc0048);
trace("rom table offset %uX\n", table);
table = *(ushort*)readbios(sizeof table, 0xc0000+table+16);
trace("freq table offset %uX\n", table);
memclk = *(ushort*)readbios(sizeof memclk, 0xc0000+table+18);
trace("memclk %ud\n", memclk);
memtyp = memtype[mp->reg[ConfigStat0]&07];
mem = &meminfo[memtyp];
/*
* First we need to calculate x, the number of
* XCLKs that one QWORD occupies in the display FIFO.
*
* For some reason, x gets stretched out if LCD stretching
* is turned on.
*/
x = ((double)memclk*640000.0) /
((double)vga->mode->frequency * (double)vga->mode->z);
if(mp->lcd[LCD_HorzStretch] & (1<<31))
x *= 4096.0 / (double)(mp->lcd[LCD_HorzStretch] & 0xFFFF);
trace("memclk %d... x %f...", memclk, x);
/*
* We have 14 bits to specify x in. Decide where to
* put the decimal (err, binary) point by counting how
* many significant bits are in the integer portion of x.
*/
t = x;
for(i=31; i>=0; i--)
if(t & (1<<i))
break;
xprec = i+1;
trace("t %lud... xprec %d...", t, xprec);
/*
* The maximum FIFO size is the number of XCLKs per QWORD
* multiplied by 32, for some reason. We have 11 bits to
* specify fifosz.
*/
fifosz = x * 32.0;
trace("fifosz %f...", fifosz);
t = fifosz;
for(i=31; i>=0; i--)
if(t & (1<<i))
break;
fprec = i+1;
trace("fprec %d...", fprec);
/*
* Precision is specified as 3 less than the number of bits
* in the integer part of x, and 5 less than the number of bits
* in the integer part of fifosz.
*
* It is bounded by zero and seven.
*/
prec = (xprec-3 > fprec-5) ? xprec-3 : fprec-5;
if(prec < 0)
prec = 0;
if(prec > 7)
prec = 7;
xprec = prec+3;
fprec = prec+5;
trace("prec %d...", prec);
/*
* Actual fifo size
*/
afifosz = (1<<fprec) / x;
if(afifosz > 32)
afifosz = 32;
fifooff = ceil(x*(afifosz-1));
/*
* I am suspicious of this table, lifted from ATI docs,
* because it doesn't agree with the Windows drivers.
* We always get 0x0A for lat+2 while Windows uses 0x08.
*/
lat = looplatencytab[memtyp > 1][vga->vmz > 1*1024*1024];
trace("afifosz %d...fifooff %f...", afifosz, fifooff);
/*
* Page fault clock
*/
t = mp->reg[MemCntl];
mem->trp = (t>>8)&3; /* RAS precharge time */
mem->trcd = (t>>10)&3; /* RAS to CAS delay */
mem->tcrd = (t>>12)&1; /* CAS to RAS delay */
mem->tras = (t>>16)&7; /* RAS low minimum pulse width */
pfc = mem->trp + 1 + mem->trcd + 1 + mem->tcrd;
trace("pfc %d...", pfc);
/*
* Maximum random access cycle clock.
*/
ncycle = cyclesperqwordtab[memtyp > 1][vga->vmz > 1*1024*1024];
rcc = mem->trp + 1 + mem->tras + 1;
if(rcc < pfc+ncycle)
rcc = pfc+ncycle;
trace("rcc %d...", rcc);
fifoon = (rcc > floor(x)) ? rcc : floor(x);
fifoon += (3.0 * rcc) - 1 + pfc + ncycle;
trace("fifoon %f...\n", fifoon);
/*
* Now finally put the bits together.
* x is stored in a 14 bit field with xprec bits of integer.
*/
pw = x * (1<<(14-xprec));
mp->reg[DspConfig] = (ulong)pw | (((lat+2)&0xF)<<16) | ((prec&7)<<20);
/*
* These are stored in an 11 bit field with fprec bits of integer.
*/
dspon = (ushort)fifoon << (11-fprec);
dspoff = (ushort)fifooff << (11-fprec);
mp->reg[DspOnOff] = ((dspon&0x7ff) << 16) | (dspoff&0x7ff);
}
static void
init(Vga* vga, Ctlr* ctlr)
{
Mode *mode;
Mach64xx *mp;
int p, x, y;
mode = vga->mode;
if((mode->x > 640 || mode->y > 480) && mode->z == 1)
error("%s: no support for 1-bit mode other than 640x480x1\n",
ctlr->name);
mp = vga->private;
if(mode->z > 8 && mp->pci == nil)
error("%s: no support for >8-bit color without PCI\n",
ctlr->name);
/*
* Check for Rage chip
*/
switch (mp->reg[ConfigChipId]&0xffff) {
case ('G'<<8)|'B': /* 4742: 264GT PRO */
case ('G'<<8)|'D': /* 4744: 264GT PRO */
case ('G'<<8)|'I': /* 4749: 264GT PRO */
case ('G'<<8)|'M': /* 474D: Rage XL */
case ('G'<<8)|'P': /* 4750: 264GT PRO */
case ('G'<<8)|'Q': /* 4751: 264GT PRO */
case ('G'<<8)|'R': /* 4752: */
case ('G'<<8)|'U': /* 4755: 264GT DVD */
case ('G'<<8)|'V': /* 4756: Rage2C */
case ('G'<<8)|'Z': /* 475A: Rage2C */
case ('V'<<8)|'U': /* 5655: 264VT3 */
case ('V'<<8)|'V': /* 5656: 264VT4 */
case ('G'<<8)|'T': /* 4754: 264GT[B] */
case ('V'<<8)|'T': /* 5654: 264VT/GT/VTB */
case ('L'<<8)|'B': /* 4C42: Rage LTPro AGP */
case ('L'<<8)|'I': /* 4C49: 264LT PRO */
case ('L'<<8)|'M': /* 4C4D: Rage Mobility */
case ('L'<<8)|'P': /* 4C50: 264LT PRO */
ctlr->flag |= Uenhanced;
break;
}
/*
* Always use VCLK2.
*/
clock(vga, ctlr);
mp->pll[PLLn2] = vga->n[0];
mp->pll[PLLp] &= ~(0x03<<(2*2));
switch(vga->p[0]){
case 1:
case 3:
p = 0;
break;
case 2:
p = 1;
break;
case 4:
case 6:
p = 2;
break;
case 8:
case 12:
p = 3;
break;
default:
p = 3;
break;
}
mp->pll[PLLp] |= p<<(2*2);
if ((1<<p) != vga->p[0])
mp->pll[PLLx] |= 1<<(4+2);
else
mp->pll[PLLx] &= ~(1<<(4+2));
mp->reg[ClockCntl] = 2;
mp->reg[ConfigCntl] = 0;
mp->reg[CrtcGenCntl] = 0x02000000|(mp->reg[CrtcGenCntl] & ~0x01400700);
switch(mode->z){
default:
case 1:
mp->reg[CrtcGenCntl] |= 0x00000100;
mp->reg[DpPixWidth] = 0x00000000;
break;
case 8:
mp->reg[CrtcGenCntl] |= 0x01000200;
mp->reg[DpPixWidth] = 0x00020202;
break;
case 15:
mp->reg[CrtcGenCntl] |= 0x01000300;
mp->reg[DpPixWidth] = 0x00030303;
break;
case 16:
mp->reg[CrtcGenCntl] |= 0x01000400;
mp->reg[DpPixWidth] = 0x00040404;
break;
case 24:
mp->reg[CrtcGenCntl] |= 0x01000500;
mp->reg[DpPixWidth] = 0x00050505;
break;
case 32:
mp->reg[CrtcGenCntl] |= 0x01000600;
mp->reg[DpPixWidth] = 0x00060606;
break;
}
mp->reg[HTotalDisp] = (((mode->x>>3)-1)<<16)|((mode->ht>>3)-1);
mp->reg[HSyncStrtWid] = (((mode->ehs - mode->shs)>>3)<<16)
|((mode->shs>>3)-1);
if(mode->hsync == '-')
mp->reg[HSyncStrtWid] |= 0x00200000;
mp->reg[VTotalDisp] = ((mode->y-1)<<16)|(mode->vt-1);
mp->reg[VSyncStrtWid] = ((mode->vre - mode->vrs)<<16)|(mode->vrs-1);
if(mode->vsync == '-')
mp->reg[VSyncStrtWid] |= 0x00200000;
mp->reg[IntCntl] = 0;
/*
* This used to set it to (mode->x/(8*2))<<22 for depths < 8,
* but from the manual that seems wrong to me. -rsc
*/
mp->reg[OffPitch] = (vga->virtx/8)<<22;
mp->reg[OvrClr] = Pblack;
if(vga->linear && mode->z != 1)
ctlr->flag |= Ulinear;
/*
* Heuristic fiddling on LT PRO.
* Do this before setdsp so the stretching is right.
*/
if(mp->lcdon){
/* use non-shadowed registers */
mp->lcd[LCD_GenCtrl] &= ~0x00000404;
mp->lcd[LCD_ConfigPanel] |= 0x00004000;
mp->lcd[LCD_VertStretch] = 0;
y = ((mp->lcd[LCD_ExtVertStretch]>>11) & 0x7FF)+1;
if(mode->y < y){
x = (mode->y*1024)/y;
mp->lcd[LCD_VertStretch] = 0xC0000000|x;
}
mp->lcd[LCD_ExtVertStretch] &= ~0x00400400;
/*
* The x value doesn't seem to be available on all
* chips so intuit it from the y value which seems to
* be reliable.
*/
mp->lcd[LCD_HorzStretch] &= ~0xC00000FF;
x = (mp->lcd[LCD_HorzStretch]>>20) & 0xFF;
if(x == 0){
switch(y){
default:
break;
case 480:
x = 640;
break;
case 600:
x = 800;
break;
case 768:
x = 1024;
break;
case 1024:
x = 1280;
break;
}
}
else
x = (x+1)*8;
if(mode->x < x){
x = (mode->x*4096)/x;
mp->lcd[LCD_HorzStretch] |= 0xC0000000|x;
}
}
if(ctlr->flag&Uenhanced)
setdsp(vga, ctlr);
ctlr->flag |= Finit;
}
static void
load(Vga* vga, Ctlr* ctlr)
{
Mach64xx *mp;
int i;
mp = vga->private;
/*
* Unlock the CRTC and LCD registers.
*/
mp->iow32(mp, CrtcGenCntl, mp->ior32(mp, CrtcGenCntl)&~0x00400000);
if(mp->lcdon)
lcdw32(mp, LCD_GenCtrl, mp->lcd[LCD_GenCtrl]|0x80000000);
/*
* Always use an aperture on a 16Mb boundary.
*/
if(ctlr->flag & Ulinear)
mp->reg[ConfigCntl] = ((vga->vmb/(4*1024*1024))<<4)|0x02;
mp->iow32(mp, ConfigCntl, mp->reg[ConfigCntl]);
mp->iow32(mp, GenTestCntl, 0);
mp->iow32(mp, GenTestCntl, 0x100);
if((ctlr->flag&Uenhanced) == 0)
mp->iow32(mp, MemCntl, mp->reg[MemCntl] & ~0x70000);
mp->iow32(mp, BusCntl, mp->reg[BusCntl]);
mp->iow32(mp, HTotalDisp, mp->reg[HTotalDisp]);
mp->iow32(mp, HSyncStrtWid, mp->reg[HSyncStrtWid]);
mp->iow32(mp, VTotalDisp, mp->reg[VTotalDisp]);
mp->iow32(mp, VSyncStrtWid, mp->reg[VSyncStrtWid]);
mp->iow32(mp, IntCntl, mp->reg[IntCntl]);
mp->iow32(mp, OffPitch, mp->reg[OffPitch]);
if(mp->lcdon){
for(i=0; i<Nlcd; i++)
lcdw32(mp, i, mp->lcd[i]);
}
mp->iow32(mp, GenTestCntl, mp->reg[GenTestCntl]);
mp->iow32(mp, ConfigCntl, mp->reg[ConfigCntl]);
mp->iow32(mp, CrtcGenCntl, mp->reg[CrtcGenCntl]);
mp->iow32(mp, OvrClr, mp->reg[OvrClr]);
mp->iow32(mp, OvrWidLR, mp->reg[OvrWidLR]);
mp->iow32(mp, OvrWidTB, mp->reg[OvrWidTB]);
if(ctlr->flag&Uenhanced){
mp->iow32(mp, DacRegs, mp->reg[DacRegs]);
mp->iow32(mp, DacCntl, mp->reg[DacCntl]);
mp->iow32(mp, CrtcGenCntl, mp->reg[CrtcGenCntl]&~0x02000000);
mp->iow32(mp, DspOnOff, mp->reg[DspOnOff]);
mp->iow32(mp, DspConfig, mp->reg[DspConfig]);
mp->iow32(mp, CrtcGenCntl, mp->reg[CrtcGenCntl]);
pllw(mp, PLLx, mp->pll[PLLx]);
}
pllw(mp, PLLn2, mp->pll[PLLn2]);
pllw(mp, PLLp, mp->pll[PLLp]);
pllw(mp, PLLn3, mp->pll[PLLn3]);
mp->iow32(mp, ClockCntl, mp->reg[ClockCntl]);
mp->iow32(mp, ClockCntl, 0x40|mp->reg[ClockCntl]);
mp->iow32(mp, DpPixWidth, mp->reg[DpPixWidth]);
if(vga->mode->z > 8){
int sh, i;
/*
* We need to initialize the palette, since the DACs use it
* in true color modes. First see if the card supports an
* 8-bit DAC.
*/
mp->iow32(mp, DacCntl, mp->reg[DacCntl] | 0x100);
if(mp->ior32(mp, DacCntl)&0x100){
/* card appears to support it */
vgactlw("palettedepth", "8");
mp->reg[DacCntl] |= 0x100;
}
if(mp->reg[DacCntl] & 0x100)
sh = 0; /* 8-bit DAC */
else
sh = 2; /* 6-bit DAC */
for(i=0; i<256; i++)
setpalette(i, i>>sh, i>>sh, i>>sh);
}
ctlr->flag |= Fload;
}
static void
pixelclock(Vga* vga, Ctlr* ctlr)
{
Mach64xx *mp;
ushort table, s;
int memclk, ref_freq, ref_divider, min_freq, max_freq;
int feedback, nmult, pd, post, value;
int clock;
/*
* Find the pixel clock from the BIOS and current
* settings. Lifted from the ATI-supplied example code.
* The clocks stored in the BIOS table are in kHz/10.
*
* This is the clock LCDs use in vgadb to set the DSP
* values.
*/
mp = vga->private;
/*
* GetPLLInfo()
*/
table = *(ushort*)readbios(sizeof table, 0xc0048);
trace("rom table offset %uX\n", table);
table = *(ushort*)readbios(sizeof table, 0xc0000+table+16);
trace("freq table offset %uX\n", table);
s = *(ushort*)readbios(sizeof s, 0xc0000+table+18);
memclk = s*10000;
trace("memclk %ud\n", memclk);
s = *(ushort*)readbios(sizeof s, 0xc0000+table+8);
ref_freq = s*10000;
trace("ref_freq %ud\n", ref_freq);
s = *(ushort*)readbios(sizeof s, 0xc0000+table+10);
ref_divider = s;
trace("ref_divider %ud\n", ref_divider);
s = *(ushort*)readbios(sizeof s, 0xc0000+table+2);
min_freq = s*10000;
trace("min_freq %ud\n", min_freq);
s = *(ushort*)readbios(sizeof s, 0xc0000+table+4);
max_freq = s*10000;
trace("max_freq %ud\n", max_freq);
/*
* GetDivider()
*/
pd = mp->pll[PLLp] & 0x03;
value = (mp->pll[PLLx] & 0x10)>>2;
trace("pd %uX value %uX (|%d)\n", pd, value, value|pd);
value |= pd;
post = 0;
switch(value){
case 0:
post = 1;
break;
case 1:
post = 2;
break;
case 2:
post = 4;
break;
case 3:
post = 8;
break;
case 4:
post = 3;
break;
case 5:
post = 0;
break;
case 6:
post = 6;
break;
case 7:
post = 12;
break;
}
trace("post = %d\n", post);
feedback = mp->pll[PLLn0];
if(mp->pll[PLLx] & 0x08)
nmult = 4;
else
nmult = 2;
clock = (ref_freq/10000)*nmult*feedback;
clock /= ref_divider*post;
clock *= 10000;
Bprint(&stdout, "%s pixel clock = %ud\n", ctlr->name, clock);
}
static void dumpmach64bios(Mach64xx*);
static void
dump(Vga* vga, Ctlr* ctlr)
{
Mach64xx *mp;
int i, m, n, p;
double f;
static int first = 1;
if((mp = vga->private) == 0)
return;
Bprint(&stdout, "%s pci %p io %lux %s\n", ctlr->name,
mp->pci, mp->io, mp->ior32 == pciior32 ? "pciregs" : "ioregs");
if(mp->pci)
Bprint(&stdout, "%s ccru %ux\n", ctlr->name, mp->pci->ccru);
for(i = 0; i < Nreg; i++)
Bprint(&stdout, "%s %-*s%.8luX\n",
ctlr->name, 20, iorname[i], mp->reg[i]);
printitem(ctlr->name, "PLL");
for(i = 0; i < Npll; i++)
printreg(mp->pll[i]);
Bprint(&stdout, "\n");
switch(mp->reg[ConfigChipId] & 0xFFFF){
default:
break;
case ('L'<<8)|'B': /* 4C42: Rage LTPro AGP */
case ('L'<<8)|'I': /* 4C49: Rage 3D LTPro */
case ('L'<<8)|'M': /* 4C4D: Rage Mobility */
case ('L'<<8)|'P': /* 4C50: Rage 3D LTPro */
for(i = 0; i < Nlcd; i++)
Bprint(&stdout, "%s %-*s%.8luX\n",
ctlr->name, 20, lcdname[i], mp->lcd[i]);
break;
}
/*
* (2*r*n)
* f = -------
* (m*p)
*/
m = mp->pll[2];
for(i = 0; i < 4; i++){
n = mp->pll[7+i];
p = (mp->pll[6]>>(i*2)) & 0x03;
p |= (mp->pll[11]>>(2+i)) & 0x04;
switch(p){
case 0:
case 1:
case 2:
case 3:
p = 1<<p;
break;
case 4+0:
p = 3;
break;
case 4+2:
p = 6;
break;
case 4+3:
p = 12;
break;
default:
case 4+1:
p = -1;
break;
}
if(m*p == 0)
Bprint(&stdout, "unknown VCLK%d\n", i);
else {
f = (2.0*RefFreq*n)/(m*p) + 0.5;
Bprint(&stdout, "%s VCLK%d\t%ud\n", ctlr->name, i, (int)f);
}
}
pixelclock(vga, ctlr);
if(first) {
first = 0;
dumpmach64bios(mp);
}
}
enum {
ClockFixed=0,
ClockIcs2595,
ClockStg1703,
ClockCh8398,
ClockInternal,
ClockAtt20c408,
ClockIbmrgb514
};
/*
* mostly derived from the xfree86 probe routines.
*/
static void
dumpmach64bios(Mach64xx *mp)
{
int i, romtable, clocktable, freqtable, lcdtable, lcdpanel;
uchar bios[0x1000];
memmove(bios, readbios(sizeof bios, 0xC0000), sizeof bios);
/* find magic string */
for(i=0; i<1024; i++)
if(strncmp((char*)bios+i, " 761295520", 10) == 0)
break;
if(i==1024) {
Bprint(&stdout, "no ATI bios found\n");
return;
}
/* this is horribly endian dependent. sorry. */
romtable = *(ushort*)(bios+0x48);
if(romtable+0x12 > sizeof(bios)) {
Bprint(&stdout, "couldn't find ATI rom table\n");
return;
}
clocktable = *(ushort*)(bios+romtable+0x10);
if(clocktable+0x0C > sizeof(bios)) {
Bprint(&stdout, "couldn't find ATI clock table\n");
return;
}
freqtable = *(ushort*)(bios-2);
if(freqtable+0x20 > sizeof(bios)) {
Bprint(&stdout, "couldn't find ATI frequency table\n");
return;
}
Bprint(&stdout, "ATI BIOS rom 0x%x freq 0x%x clock 0x%x\n", romtable, freqtable, clocktable);
Bprint(&stdout, "clocks:");
for(i=0; i<16; i++)
Bprint(&stdout, " %d", *(ushort*)(bios+freqtable+2*i));
Bprint(&stdout, "\n");
Bprint(&stdout, "programmable clock: %d\n", bios[clocktable]);
Bprint(&stdout, "clock to program: %d\n", bios[clocktable+6]);
if(*(ushort*)(bios+clocktable+8) != 1430) {
Bprint(&stdout, "reference numerator: %d\n", *(ushort*)(bios+clocktable+8)*10);
Bprint(&stdout, "reference denominator: 1\n");
} else {
Bprint(&stdout, "default reference numerator: 157500\n");
Bprint(&stdout, "default reference denominator: 11\n");
}
switch(bios[clocktable]) {
case ClockIcs2595:
Bprint(&stdout, "ics2595\n");
Bprint(&stdout, "reference divider: %d\n", *(ushort*)(bios+clocktable+0x0A));
break;
case ClockStg1703:
Bprint(&stdout, "stg1703\n");
break;
case ClockCh8398:
Bprint(&stdout, "ch8398\n");
break;
case ClockInternal:
Bprint(&stdout, "internal clock\n");
Bprint(&stdout, "reference divider in plls\n");
break;
case ClockAtt20c408:
Bprint(&stdout, "att 20c408\n");
break;
case ClockIbmrgb514:
Bprint(&stdout, "ibm rgb514\n");
Bprint(&stdout, "clock to program = 7\n");
break;
default:
Bprint(&stdout, "unknown clock\n");
break;
}
USED(mp);
if(1 || mp->lcdpanelid) {
lcdtable = *(ushort*)(bios+0x78);
if(lcdtable+5 > sizeof bios || lcdtable+bios[lcdtable+5] > sizeof bios) {
Bprint(&stdout, "can't find lcd bios table\n");
goto NoLcd;
}
lcdpanel = *(ushort*)(bios+lcdtable+0x0A);
if(lcdpanel+0x1D > sizeof bios /*|| bios[lcdpanel] != mp->lcdpanelid*/) {
Bprint(&stdout, "can't find lcd bios table0\n");
goto NoLcd;
}
Bprint(&stdout, "panelid %d x %d y %d\n", bios[lcdpanel], *(ushort*)(bios+lcdpanel+0x19), *(ushort*)(bios+lcdpanel+0x1B));
}
NoLcd:;
}
Ctlr mach64xx = {
"mach64xx", /* name */
snarf, /* snarf */
0, /* options */
init, /* init */
load, /* load */
dump, /* dump */
};
Ctlr mach64xxhwgc = {
"mach64xxhwgc", /* name */
0, /* snarf */
0, /* options */
0, /* init */
0, /* load */
0, /* dump */
};
|