/*
 * Copyright 2004-2005 The Unichrome Project  [unichrome.sf.net]
 * Copyright 1998-2003 VIA Technologies, Inc. All Rights Reserved.
 * Copyright 2001-2003 S3 Graphics, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "via_driver.h"
#include "via_vgahw.h"
#include "via_vt162x.h"
#include "via_id.h"

/*
 *
 */
static void
VT162xPrintRegs(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    CARD8 i, buf;
    

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Printing registers for %s\n",
	       pBIOSInfo->TVI2CDev->DevName);

    for (i = 0; i < pBIOSInfo->TVNumRegs; i++) {
	xf86I2CReadByte(pBIOSInfo->TVI2CDev, i, &buf);
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "TV%02X: 0x%02X\n", i, buf);
    }

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "End of TV registers.\n");
}

/*
 *                 
 */
I2CDevPtr
ViaVT162xDetect(ScrnInfoPtr pScrn, I2CBusPtr pBus, CARD8 Address)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    I2CDevPtr pDev = xf86CreateI2CDevRec();
    CARD8 buf;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaVT162xDetect\n"));

    pDev->DevName = "VT162x";
    pDev->SlaveAddr = Address;
    pDev->pI2CBus = pBus;
    
    if (!xf86I2CDevInit(pDev)) {
	xf86DestroyI2CDevRec(pDev, TRUE);
	return NULL;
    }

    if (!xf86I2CReadByte(pDev, 0x1B, &buf)) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Unable to read from %s Slave %d.\n",
		   pBus->BusName, Address);
	xf86DestroyI2CDevRec(pDev, TRUE);
	return NULL;
    }
    
    switch (buf) {
    case 0x02:
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, 
		   "Detected VIA Technologies VT1621 TV Encoder\n");
	pBIOSInfo->TVEncoder = VIA_VT1621;
	pDev->DevName = "VT1621";
	break;
    case 0x03:
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, 
		   "Detected VIA Technologies VT1622 TV Encoder\n");
	pBIOSInfo->TVEncoder = VIA_VT1622;
	pDev->DevName = "VT1622";
	break;
    case 0x10:
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, 
		   "Detected VIA Technologies VT1622A/VT1623 TV Encoder\n");
	pBIOSInfo->TVEncoder = VIA_VT1623;
	pDev->DevName = "VT1623";
	break;
    case 0x50:
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, 
		   "Detected VIA Technologies VT1625 TV Encoder\n");
#if 0
	pBIOSInfo->TVEncoder = VIA_VT1625;
	pDev->DevName = "VT1625";
#else 
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "VT1625 is not supported yet.\n");
	xf86DestroyI2CDevRec(pDev, TRUE);
	pDev = NULL;
#endif
	break;
    default:
	pBIOSInfo->TVEncoder = VIA_NONETV;
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Unknown TV Encoder found at %s %X.\n", pBus->BusName, Address);
	xf86DestroyI2CDevRec(pDev,TRUE);
	pDev = NULL;
	break;
    }

    return pDev;
}

/*
 *
 */
static void
VT162xSave(ScrnInfoPtr pScrn)
{
    int i;
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT162xSave\n"));

    for (i = 0; i < pBIOSInfo->TVNumRegs; i++)
      xf86I2CReadByte(pBIOSInfo->TVI2CDev, i, &(pBIOSInfo->TVRegs[i]));

}

/*
 *
 */
static void
VT162xRestore(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    int i;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT162xRestore\n"));

    for (i = 0; i < pBIOSInfo->TVNumRegs; i++)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, i, pBIOSInfo->TVRegs[i]);
}

/*
 * the same for VT1621 as for VT1622/VT1622A/VT1623, result is different though
 * still needs testing on vt1621 of course.
 */
static CARD8
VT162xDACSenseI2C(I2CDevPtr pDev)
{
    CARD8  save, sense;

    xf86I2CReadByte(pDev, 0x0E, &save);
    xf86I2CWriteByte(pDev, 0x0E, 0x00);
    xf86I2CWriteByte(pDev, 0x0E, 0x80);
    xf86I2CWriteByte(pDev, 0x0E, 0x00);
    xf86I2CReadByte(pDev, 0x0F, &sense);
    xf86I2CWriteByte(pDev, 0x0E, save);
    
    return (sense & 0x0F);
}

/*
 * VT1621 only knows composite and s-video
 */
static Bool
VT1621DACSense(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    CARD8  sense;
    
    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1621DACSense\n"));
    
    sense = VT162xDACSenseI2C(pBIOSInfo->TVI2CDev);
    switch (sense) {
    case 0x00:
	pBIOSInfo->TVOutput = TVOUTPUT_SC;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT1621: S-Video & Composite connected.\n");
	return TRUE;
    case 0x01:
	pBIOSInfo->TVOutput = TVOUTPUT_COMPOSITE;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT1621: Composite connected.\n");
	return TRUE;
    case 0x02:
	pBIOSInfo->TVOutput = TVOUTPUT_SVIDEO;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT1621: S-Video connected.\n");
	return TRUE;
    case 0x03:
	pBIOSInfo->TVOutput = TVOUTPUT_NONE;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT1621: Nothing connected.\n");
	return FALSE;
    default:
	pBIOSInfo->TVOutput = TVOUTPUT_NONE;
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "VT1621: Unknown cable combination: 0x0%2X.\n",
		   sense);
	return FALSE;
    }
}


/*
 * VT1622, VT1622A and VT1623 know composite, s-video, RGB and YCBCR
 */
static Bool
VT1622DACSense(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    CARD8  sense;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1622DACSense\n"));

    sense = VT162xDACSenseI2C(pBIOSInfo->TVI2CDev);
    switch (sense) {
    case 0x00: /* DAC A,B,C,D */
	pBIOSInfo->TVOutput = TVOUTPUT_RGB;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT162x: RGB connected.\n");
	return TRUE;
    case 0x01: /* DAC A,B,C */
	pBIOSInfo->TVOutput = TVOUTPUT_SC;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT162x: S-Video & Composite connected.\n");
	return TRUE;
    case 0x07: /* DAC A */
	pBIOSInfo->TVOutput = TVOUTPUT_COMPOSITE;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT162x: Composite connected.\n");
	return TRUE;
    case 0x08: /* DAC B,C,D */
	pBIOSInfo->TVOutput = TVOUTPUT_YCBCR;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT162x: YcBcR connected.\n");
	return TRUE;
    case 0x09: /* DAC B,C */
	pBIOSInfo->TVOutput = TVOUTPUT_SVIDEO;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT162x: S-Video connected.\n");
	return TRUE;
    case 0x0F:
	pBIOSInfo->TVOutput = TVOUTPUT_NONE;
	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VT162x: Nothing connected.\n");
	return FALSE;
    default:
	pBIOSInfo->TVOutput = TVOUTPUT_NONE;
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "VT162x: Unknown cable combination: 0x0%2X.\n",
		   sense);
	return FALSE;
    }
}

/*
 *
 */
static CARD8
VT1621ModeIndex(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    int i;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1621ModeIndex\n"));    

    for (i = 0; VT1621Table[i].Width; i++) {
	if ((VT1621Table[i].Width == mode->CrtcHDisplay) &&
	    (VT1621Table[i].Height == mode->CrtcVDisplay) &&
	    (VT1621Table[i].Standard == pBIOSInfo->TVType) &&
	    !(strcmp(VT1621Table[i].name, mode->name)))
	    return i;
    }
    xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "VT1622ModeIndex:"
	       " Mode \"%s\" not found in Table\n", mode->name);
    return 0xFF;
}

/*
 *
 */
static ModeStatus
VT1621ModeValid(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1621ModeValid\n"));

    if ((mode->PrivSize != sizeof(struct VT162xModePrivate)) ||
	((mode->Private != (void *) &VT162xModePrivateNTSC) &&
	 (mode->Private != (void *) &VT162xModePrivatePAL))) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Not a mode defined by the TV Encoder.\n");
	return MODE_BAD;
    }

    if ((pBIOSInfo->TVType == TVTYPE_NTSC) &&
	(mode->Private != (void *) &VT162xModePrivateNTSC)) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "TV standard is NTSC. This is a PAL mode.\n");
	return MODE_BAD;
    } else if ((pBIOSInfo->TVType == TVTYPE_PAL) &&
	       (mode->Private != (void *) &VT162xModePrivatePAL)) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "TV standard is PAL. This is a NTSC mode.\n");
	return MODE_BAD;
    }

    if (VT1621ModeIndex(pScrn, mode) != 0xFF)
	return MODE_OK;
    return MODE_BAD;
}

/*
 *
 */
static CARD8
VT1622ModeIndex(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    struct VT162XTableRec *Table;
    int i;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1622ModeIndex\n"));    

    if (pBIOSInfo->TVEncoder == VIA_VT1622)
	Table = VT1622Table;
    else
	Table = VT1623Table;

    for (i = 0; Table[i].Width; i++) {
	if ((Table[i].Width == mode->CrtcHDisplay) &&
	    (Table[i].Height == mode->CrtcVDisplay) &&
	    (Table[i].Standard == pBIOSInfo->TVType) &&
	    !strcmp(Table[i].name, mode->name))
	    return i;
    }
    xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "VT1622ModeIndex:"
	       " Mode \"%s\" not found in Table\n", mode->name);
    return 0xFF;
}

/*
 *
 */
static ModeStatus
VT1622ModeValid(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1622ModeValid\n"));

    if ((mode->PrivSize != sizeof(struct VT162xModePrivate)) ||
	((mode->Private != (void *) &VT162xModePrivateNTSC) &&
	 (mode->Private != (void *) &VT162xModePrivatePAL))) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Not a mode defined by the TV Encoder.\n");
	return MODE_BAD;
    }

    if ((pBIOSInfo->TVType == TVTYPE_NTSC) &&
	(mode->Private != (void *) &VT162xModePrivateNTSC)) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "TV standard is NTSC. This is a PAL mode.\n");
	return MODE_BAD;
    } else if ((pBIOSInfo->TVType == TVTYPE_PAL) &&
	       (mode->Private != (void *) &VT162xModePrivatePAL)) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "TV standard is PAL. This is a NTSC mode.\n");
	return MODE_BAD;
    }

    if (VT1622ModeIndex(pScrn, mode) != 0xFF)
	return MODE_OK;
    return MODE_BAD;
}

/*
 *
 */
static void
VT162xSetSubCarrier(I2CDevPtr pDev, CARD32 SubCarrier)
{
    xf86I2CWriteByte(pDev, 0x16, SubCarrier & 0xFF);
    xf86I2CWriteByte(pDev, 0x17, (SubCarrier >> 8) & 0xFF);
    xf86I2CWriteByte(pDev, 0x18, (SubCarrier >> 16) & 0xFF);
    xf86I2CWriteByte(pDev, 0x19, (SubCarrier >> 24) & 0xFF);
}

/*
 *
 */
static void
VT1621ModeI2C(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    struct VT1621TableRec Table = VT1621Table[VT1621ModeIndex(pScrn, mode)];
    CARD8 i;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1621ModeI2C\n"));

    for (i = 0; i < 0x16; i++)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, i, Table.TV[i]);

    VT162xSetSubCarrier(pBIOSInfo->TVI2CDev, Table.SubCarrier);

    /* skip reserved (1A) and version id (1B). */

    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x1C, Table.TV[0x1C]);

    /* skip software reset (1D) */

    for (i = 0x1E; i < 0x24; i++)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, i, Table.TV[i]);

    /* write some zeroes? */
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x24, 0x00);
    for (i = 0; i < 0x08; i++)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x4A + i, 0x00);

    if (pBIOSInfo->TVOutput == TVOUTPUT_COMPOSITE)
	for (i = 0; i < 0x10; i++)
	    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x52 + i, Table.TVC[i]);
    else
	for (i = 0; i < 0x10; i++)
	    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x52 + i, Table.TVS[i]);

    /* Turn on all Composite and S-Video output */
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x0E, 0x00);

    if (pBIOSInfo->TVDotCrawl) {
	if (Table.DotCrawlSubCarrier) {
	    xf86I2CReadByte(pBIOSInfo->TVI2CDev, 0x11, &i);
	    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x11, i | 0x08);
	    
	    VT162xSetSubCarrier(pBIOSInfo->TVI2CDev, Table.DotCrawlSubCarrier);
	} else
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "This mode does not currently "
		       "support DotCrawl suppression.\n");  
    }
}

/*
 *
 */
static void
VT1621ModeCrtc(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia =  VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;
    struct VT1621TableRec Table = VT1621Table[VT1621ModeIndex(pScrn, mode)];

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1621ModeCrtc\n"));

    if (pVia->IsSecondary) {
	hwp->writeCrtc(hwp, 0x6A, 0x80);
	hwp->writeCrtc(hwp, 0x6B, 0x20);
	hwp->writeCrtc(hwp, 0x6C, 0x80);

	/* Disable LCD Scaling */
    	if (!pVia->SAMM || pVia->FirstInit)
	    hwp->writeCrtc(hwp, 0x79, 0x00);

    } else {
	hwp->writeCrtc(hwp, 0x6A, 0x00);
	hwp->writeCrtc(hwp, 0x6B, 0x80);
	hwp->writeCrtc(hwp, 0x6C, Table.PrimaryCR6C);
    }
    if ((pVia->Chipset != VIA_K8M800) && (pVia->Chipset != VIA_PM800))
	pBIOSInfo->ClockExternal = TRUE;
    ViaCrtcMask(hwp, 0x6A, 0x40, 0x40);
    ViaCrtcMask(hwp, 0x6C, 0x01, 0x01);
}

/*
 * also suited for VT1622A, VT1623
 */
static void
VT1622ModeI2C(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;
    struct VT162XTableRec Table;
    CARD8 save, i;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1622ModeI2C\n"));

    if (pBIOSInfo->TVEncoder == VIA_VT1622)
	Table = VT1622Table[VT1622ModeIndex(pScrn, mode)];
    else /* VT1622A/VT1623 */
	Table = VT1623Table[VT1622ModeIndex(pScrn, mode)];

    /* TV Reset */
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x1D, 0x00);
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x1D, 0x80);

    for (i = 0; i < 0x16; i++)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, i, Table.TV1[i]);

    VT162xSetSubCarrier(pBIOSInfo->TVI2CDev, Table.SubCarrier);

    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x1A, Table.TV1[0x1A]);
    
    /* skip version id */

    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x1C, Table.TV1[0x1C]);

    /* skip software reset */

    for (i = 0x1E; i < 0x30; i++)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, i, Table.TV1[i]);

    for (i = 0; i < 0x1B; i++)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x4A + i, Table.TV2[i]);

    /* Turn on all Composite and S-Video output */
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x0E, 0x00);

    if (pBIOSInfo->TVDotCrawl) {
	if (Table.DotCrawlSubCarrier) {
	    xf86I2CReadByte(pBIOSInfo->TVI2CDev, 0x11, &save);
	    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x11, save | 0x08);
	    
	    VT162xSetSubCarrier(pBIOSInfo->TVI2CDev, Table.DotCrawlSubCarrier);
	} else
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "This mode does not currently "
		       "support DotCrawl suppression.\n");  
    }

    if (pBIOSInfo->TVOutput == TVOUTPUT_RGB) {
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x02, 0x2A);
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x65, Table.RGB[0]);
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x66, Table.RGB[1]);
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x67, Table.RGB[2]);
	if (Table.RGB[3])
	    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x27, Table.RGB[3]);
	if (Table.RGB[4])
	    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x2B, Table.RGB[4]);
	if (Table.RGB[5])
	    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x2C, Table.RGB[5]);
    } else if (pBIOSInfo->TVOutput == TVOUTPUT_YCBCR) {
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x02, 0x03);
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x65, Table.YCbCr[0]);
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x66, Table.YCbCr[1]);
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x67, Table.YCbCr[2]);
    }

    /* Configure flicker filter */
    xf86I2CReadByte(pBIOSInfo->TVI2CDev, 0x03, &save);
    save &= 0xFC;
    if (pBIOSInfo->TVDeflicker == 1)
	save |= 0x01;
    else if (pBIOSInfo->TVDeflicker == 2)
        save |= 0x02;
    xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x03, save);
}

/*
 * Also suited for VT1622A, VT1623
 */
static void
VT1622ModeCrtc(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    vgaHWPtr hwp = VGAHWPTR(pScrn);
    VIAPtr pVia = VIAPTR(pScrn);
    VIABIOSInfoPtr pBIOSInfo = pVia->pBIOSInfo;
    struct VT162XTableRec Table;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1622ModeCrtc\n"));

    if (pBIOSInfo->TVEncoder == VIA_VT1622)
	Table = VT1622Table[VT1622ModeIndex(pScrn, mode)];
    else /* VT1622A/VT1623 */
	Table = VT1623Table[VT1622ModeIndex(pScrn, mode)];

    hwp->writeCrtc(hwp, 0x6A, 0x00);
    hwp->writeCrtc(hwp, 0x6B, 0x00);
    hwp->writeCrtc(hwp, 0x6C, 0x00);    

    if (pVia->IsSecondary) {
	hwp->writeCrtc(hwp, 0x6C, Table.SecondaryCR6C);

	ViaCrtcMask(hwp, 0x6A, 0x80, 0x80);
	ViaCrtcMask(hwp, 0x6C, 0x80, 0x80);

	/* CLE266Ax use 2x XCLK */
	if ((pVia->Chipset == VIA_CLE266) && CLE266_REV_IS_AX(pVia->ChipRev)) {
	    ViaCrtcMask(hwp, 0x6B, 0x20, 0x20);
	    
	    /* Fix TV clock Polarity for CLE266A2 */
	    if (pVia->ChipRev == 0x02)
		ViaCrtcMask(hwp, 0x6C, 0x1C, 0x1C);
	}

	/* Disable LCD Scaling */
	if (!pVia->SAMM || pVia->FirstInit)
	    hwp->writeCrtc(hwp, 0x79, 0x00);

    } else {
        if ((pVia->Chipset == VIA_CLE266) && CLE266_REV_IS_AX(pVia->ChipRev)) {
	    ViaCrtcMask(hwp, 0x6B, 0x80, 0x80);

	    /* Fix TV clock Polarity for CLE266A2 */
            if (pVia->ChipRev == 0x02)
		hwp->writeCrtc(hwp, 0x6C, Table.PrimaryCR6C);
        }
    }
    if ((pVia->Chipset != VIA_K8M800) && (pVia->Chipset != VIA_PM800))
	pBIOSInfo->ClockExternal = TRUE;
    ViaCrtcMask(hwp, 0x6A, 0x40, 0x40);
    ViaCrtcMask(hwp, 0x6C, 0x01, 0x01);
    ViaSeqMask(hwp, 0x1E, 0xF0, 0xF0); /* Enable DI0/DVP0 */
}

/*
 *
 */
static void
VT1621Power(ScrnInfoPtr pScrn, Bool On)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1621Power\n"));
    
    if (On)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x0E, 0x00);
    else
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x0E, 0x03);
}

/*
 *
 */
static void
VT1622Power(ScrnInfoPtr pScrn, Bool On)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1622Power\n"));
    
    if (On)
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x0E, 0x00);
    else
	xf86I2CWriteByte(pBIOSInfo->TVI2CDev, 0x0E, 0x0F);
}

/*
 *
 */
void
ViaVT162xInit(ScrnInfoPtr pScrn)
{
    VIABIOSInfoPtr pBIOSInfo = VIAPTR(pScrn)->pBIOSInfo;

    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ViaVT162xInit\n"));

    switch (pBIOSInfo->TVEncoder) {
    case VIA_VT1621:
	pBIOSInfo->TVSave = VT162xSave;
	pBIOSInfo->TVRestore = VT162xRestore;
	pBIOSInfo->TVDACSense = VT1621DACSense;
	pBIOSInfo->TVModeValid = VT1621ModeValid;
	pBIOSInfo->TVModeI2C = VT1621ModeI2C;
	pBIOSInfo->TVModeCrtc = VT1621ModeCrtc;
	pBIOSInfo->TVPower = VT1621Power;
	pBIOSInfo->TVModes = VT1621Modes;
	pBIOSInfo->TVPrintRegs = VT162xPrintRegs;
	pBIOSInfo->TVNumRegs = 0x68;
	break;
    case VIA_VT1622:
	pBIOSInfo->TVSave = VT162xSave;
	pBIOSInfo->TVRestore = VT162xRestore;
	pBIOSInfo->TVDACSense = VT1622DACSense;
	pBIOSInfo->TVModeValid = VT1622ModeValid;
	pBIOSInfo->TVModeI2C = VT1622ModeI2C;
	pBIOSInfo->TVModeCrtc = VT1622ModeCrtc;
	pBIOSInfo->TVPower = VT1622Power;
	pBIOSInfo->TVModes = VT1622Modes;
	pBIOSInfo->TVPrintRegs = VT162xPrintRegs;
	pBIOSInfo->TVNumRegs = 0x68;
	break;
    case VIA_VT1623:
	pBIOSInfo->TVSave = VT162xSave;
	pBIOSInfo->TVRestore = VT162xRestore;
	pBIOSInfo->TVDACSense = VT1622DACSense;
	pBIOSInfo->TVModeValid = VT1622ModeValid;
	pBIOSInfo->TVModeI2C = VT1622ModeI2C;
	pBIOSInfo->TVModeCrtc = VT1622ModeCrtc;
	pBIOSInfo->TVPower = VT1622Power;
	pBIOSInfo->TVModes = VT1623Modes;
	pBIOSInfo->TVPrintRegs = VT162xPrintRegs;
	pBIOSInfo->TVNumRegs = 0x6C;
	break;
    default:
	break;
    }
}
