/*
 * Copyright (c) 2003 NVIDIA, Corporation
 *
 * 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, sublicense, 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 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 NONINFRINGEMENT.
 * 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 "xf86Priv.h"
#include "xf86Privstr.h"
#include "nv_include.h"

#ifdef HAVE_DEV_WSCONS_WSCONSIO_H
#include <sys/time.h>
#include <sys/ioctl.h>
#include <dev/wscons/wsconsio.h>
#endif

/*
 * Override VGA I/O routines.
 */
static void NVWriteCrtc(vgaHWPtr pVga, CARD8 index, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PCIO, pVga->IOBase + VGA_CRTC_INDEX_OFFSET, index);
    VGA_WR08(pNv->PCIO, pVga->IOBase + VGA_CRTC_DATA_OFFSET,  value);
}
static CARD8 NVReadCrtc(vgaHWPtr pVga, CARD8 index)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PCIO, pVga->IOBase + VGA_CRTC_INDEX_OFFSET, index);
    return (VGA_RD08(pNv->PCIO, pVga->IOBase + VGA_CRTC_DATA_OFFSET));
}
static void NVWriteGr(vgaHWPtr pVga, CARD8 index, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PVIO, VGA_GRAPH_INDEX, index);
    VGA_WR08(pNv->PVIO, VGA_GRAPH_DATA,  value);
}
static CARD8 NVReadGr(vgaHWPtr pVga, CARD8 index)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PVIO, VGA_GRAPH_INDEX, index);
    return (VGA_RD08(pNv->PVIO, VGA_GRAPH_DATA));
}
static void NVWriteSeq(vgaHWPtr pVga, CARD8 index, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PVIO, VGA_SEQ_INDEX, index);
    VGA_WR08(pNv->PVIO, VGA_SEQ_DATA,  value);
}
static CARD8 NVReadSeq(vgaHWPtr pVga, CARD8 index)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PVIO, VGA_SEQ_INDEX, index);
    return (VGA_RD08(pNv->PVIO, VGA_SEQ_DATA));
}
static void NVWriteAttr(vgaHWPtr pVga, CARD8 index, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    volatile CARD8 tmp;

    tmp = VGA_RD08(pNv->PCIO, pVga->IOBase + VGA_IN_STAT_1_OFFSET);
    if (pVga->paletteEnabled)
        index &= ~0x20;
    else
        index |= 0x20;
    VGA_WR08(pNv->PCIO, VGA_ATTR_INDEX,  index);
    VGA_WR08(pNv->PCIO, VGA_ATTR_DATA_W, value);
}
static CARD8 NVReadAttr(vgaHWPtr pVga, CARD8 index)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    volatile CARD8 tmp;

    tmp = VGA_RD08(pNv->PCIO, pVga->IOBase + VGA_IN_STAT_1_OFFSET);
    if (pVga->paletteEnabled)
        index &= ~0x20;
    else
        index |= 0x20;
    VGA_WR08(pNv->PCIO, VGA_ATTR_INDEX, index);
    return (VGA_RD08(pNv->PCIO, VGA_ATTR_DATA_R));
}
static void NVWriteMiscOut(vgaHWPtr pVga, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PVIO, VGA_MISC_OUT_W, value);
}
static CARD8 NVReadMiscOut(vgaHWPtr pVga)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    return (VGA_RD08(pNv->PVIO, VGA_MISC_OUT_R));
}
static void NVEnablePalette(vgaHWPtr pVga)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    volatile CARD8 tmp;

    tmp = VGA_RD08(pNv->PCIO, pVga->IOBase + VGA_IN_STAT_1_OFFSET);
    VGA_WR08(pNv->PCIO, VGA_ATTR_INDEX, 0x00);
    pVga->paletteEnabled = TRUE;
}
static void NVDisablePalette(vgaHWPtr pVga)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    volatile CARD8 tmp;

    tmp = VGA_RD08(pNv->PCIO, pVga->IOBase + VGA_IN_STAT_1_OFFSET);
    VGA_WR08(pNv->PCIO, VGA_ATTR_INDEX, 0x20);
    pVga->paletteEnabled = FALSE;
}
static void NVWriteDacMask(vgaHWPtr pVga, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PDIO, VGA_DAC_MASK, value);
}
static CARD8 NVReadDacMask(vgaHWPtr pVga)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    return (VGA_RD08(pNv->PDIO, VGA_DAC_MASK));
}
static void NVWriteDacReadAddr(vgaHWPtr pVga, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PDIO, VGA_DAC_READ_ADDR, value);
}
static void NVWriteDacWriteAddr(vgaHWPtr pVga, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PDIO, VGA_DAC_WRITE_ADDR, value);
}
static void NVWriteDacData(vgaHWPtr pVga, CARD8 value)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    VGA_WR08(pNv->PDIO, VGA_DAC_DATA, value);
}
static CARD8 NVReadDacData(vgaHWPtr pVga)
{
    NVPtr pNv = (NVPtr)pVga->MMIOBase;
    return (VGA_RD08(pNv->PDIO, VGA_DAC_DATA));
}

static Bool
NVIsConnected (ScrnInfoPtr pScrn, int output)
{
    NVPtr pNv = NVPTR(pScrn);
    volatile U032 *PRAMDAC = pNv->PRAMDAC0;
    CARD32 reg52C, reg608, dac0_reg608 = 0;
    Bool present;

    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
               "Probing for analog device on output %s...\n",
                output ? "B" : "A");

    if(output) {
        dac0_reg608 = PRAMDAC[0x0608/4];
        PRAMDAC += 0x800;
    }

    reg52C = PRAMDAC[0x052C/4];
    reg608 = PRAMDAC[0x0608/4];

    PRAMDAC[0x0608/4] = reg608 & ~0x00010000;

    PRAMDAC[0x052C/4] = reg52C & 0x0000FEEE;
    usleep(1000);
    PRAMDAC[0x052C/4] |= 1;

    pNv->PRAMDAC0[0x0610/4] = 0x94050140;
    pNv->PRAMDAC0[0x0608/4] |= 0x00001000;

    usleep(1000);

    present = (PRAMDAC[0x0608/4] & (1 << 28)) ? TRUE : FALSE;

    if(present)
       xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "  ...found one\n");
    else
       xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "  ...can't find one\n");

    if(output)
        pNv->PRAMDAC0[0x0608/4] = dac0_reg608;

    PRAMDAC[0x052C/4] = reg52C;
    PRAMDAC[0x0608/4] = reg608;

    return present;
}

static void
NVSelectHeadRegisters(ScrnInfoPtr pScrn, int head)
{
    NVPtr pNv = NVPTR(pScrn);

    if(head) {
       pNv->PCIO = pNv->PCIO0 + 0x2000;
       pNv->PCRTC = pNv->PCRTC0 + 0x800;
       pNv->PRAMDAC = pNv->PRAMDAC0 + 0x800;
       pNv->PDIO = pNv->PDIO0 + 0x2000;
    } else {
       pNv->PCIO = pNv->PCIO0;
       pNv->PCRTC = pNv->PCRTC0;
       pNv->PRAMDAC = pNv->PRAMDAC0;
       pNv->PDIO = pNv->PDIO0;
    }
}

static xf86MonPtr
NVProbeDDC (ScrnInfoPtr pScrn, int bus)
{
    NVPtr pNv = NVPTR(pScrn);
    xf86MonPtr MonInfo = NULL;

    if(!pNv->I2C) return NULL;

    pNv->DDCBase = bus ? 0x36 : 0x3e;

    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
               "Probing for EDID on I2C bus %s...\n", bus ? "B" : "A");

#ifdef EDID_COMPLETE_RAWDATA
    MonInfo = xf86DoEEDID(XF86_SCRN_ARG(pScrn), pNv->I2C, TRUE);
#else
    MonInfo = xf86DoEDID_DDC2(XF86_SCRN_ARG(pScrn), pNv->I2C);
#endif
#ifdef WSDISPLAYIO_GET_EDID
    if (!MonInfo) {
	/* ask wsdisplay */
	struct wsdisplayio_edid_info ei;
	char *buffer;
	xf86MonPtr tmp;

	buffer = malloc(1024);
	ei.edid_data = buffer;
	ei.buffer_size = 1024;
	if (ioctl(xf86Info.consoleFd, WSDISPLAYIO_GET_EDID, &ei) != -1) {
	    xf86Msg(X_INFO, "got %d bytes worth of EDID from wsdisplay\n",
	    	ei.data_size);
	    tmp = xf86InterpretEEDID(pScrn->scrnIndex, buffer);
	    tmp->flags |= MONITOR_EDID_COMPLETE_RAWDATA;
	    MonInfo = tmp;
	}
	free(buffer);
    }
#endif
    if (MonInfo) {
       xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                  "DDC detected a %s:\n", MonInfo->features.input_type ?
                  "DFP" : "CRT");
       xf86PrintEDID( MonInfo );
    } else {
       xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                  "  ... none found\n");
    }

    return MonInfo;
}

static void nv4GetConfig (NVPtr pNv)
{
    if (pNv->PFB[0x0000/4] & 0x00000100) {
        pNv->RamAmountKBytes = ((pNv->PFB[0x0000/4] >> 12) & 0x0F) * 1024 * 2
                              + 1024 * 2;
    } else {
        switch (pNv->PFB[0x0000/4] & 0x00000003) {
        case 0:
            pNv->RamAmountKBytes = 1024 * 32;
            break;
        case 1:
            pNv->RamAmountKBytes = 1024 * 4;
            break;
        case 2:
            pNv->RamAmountKBytes = 1024 * 8;
            break;
        case 3:
        default:
            pNv->RamAmountKBytes = 1024 * 16;
            break;
        }
    }
    pNv->CrystalFreqKHz = (pNv->PEXTDEV[0x0000/4] & 0x00000040) ? 14318 : 13500;
    pNv->CURSOR         = &(pNv->PRAMIN[0x1E00]);
    pNv->MinVClockFreqKHz = 12000;
    pNv->MaxVClockFreqKHz = 350000;
}

static void nv10GetConfig (NVPtr pNv)
{
    CARD32 implementation = pNv->Chipset & 0x0ff0;

#if X_BYTE_ORDER == X_BIG_ENDIAN
    /* turn on big endian register access */
    if(!(pNv->PMC[0x0004/4] & 0x01000001)) {
       pNv->PMC[0x0004/4] = 0x01000001;
       mem_barrier();
    }
#endif

#ifdef XSERVER_LIBPCIACCESS
    {
    /* [AGP]: I don't know if this is correct */
    struct pci_device *dev = pci_device_find_by_slot(0, 0, 0, 1);

    if(implementation == 0x01a0) {
        uint32_t amt;
        pci_device_cfg_read_u32(dev, &amt, 0x7C);
        pNv->RamAmountKBytes = (((amt >> 6) & 31) + 1) * 1024;
    } else if(implementation == 0x01f0) {
        uint32_t amt;
        pci_device_cfg_read_u32(dev, &amt, 0x84);
        pNv->RamAmountKBytes = (((amt >> 4) & 127) + 1) * 1024;
    } else {
        pNv->RamAmountKBytes = (pNv->PFB[0x020C/4] & 0xFFF00000) >> 10;
    }
    }
#else
    if(implementation == 0x01a0) {
        int amt = pciReadLong(pciTag(0, 0, 1), 0x7C);
        pNv->RamAmountKBytes = (((amt >> 6) & 31) + 1) * 1024;
    } else if(implementation == 0x01f0) {
        int amt = pciReadLong(pciTag(0, 0, 1), 0x84);
        pNv->RamAmountKBytes = (((amt >> 4) & 127) + 1) * 1024;
    } else {
        pNv->RamAmountKBytes = (pNv->PFB[0x020C/4] & 0xFFF00000) >> 10;
    }
#endif

    if(pNv->RamAmountKBytes > 256*1024)
        pNv->RamAmountKBytes = 256*1024;

    pNv->CrystalFreqKHz = (pNv->PEXTDEV[0x0000/4] & (1 << 6)) ? 14318 : 13500;

    if(pNv->twoHeads && (implementation != 0x0110))
    {
       if(pNv->PEXTDEV[0x0000/4] & (1 << 22))
           pNv->CrystalFreqKHz = 27000;
    }

    pNv->CURSOR           = NULL;  /* can't set this here */
    pNv->MinVClockFreqKHz = 12000;
    pNv->MaxVClockFreqKHz = pNv->twoStagePLL ? 400000 : 350000;
}


void
NVCommonSetup(ScrnInfoPtr pScrn)
{
    NVPtr pNv = NVPTR(pScrn);
    vgaHWPtr pVga = VGAHWPTR(pScrn);
    CARD16 implementation = pNv->Chipset & 0x0ff0;
    xf86MonPtr monitorA, monitorB;
    Bool mobile = FALSE;
    Bool tvA = FALSE;
    Bool tvB = FALSE;
    int FlatPanel = -1;   /* really means the CRTC is slaved */
    Bool Television = FALSE;
    void *tmp;
#ifdef XSERVER_LIBPCIACCESS
    int err;
#endif

    /*
     * Override VGA I/O routines.
     */
    pVga->writeCrtc         = NVWriteCrtc;
    pVga->readCrtc          = NVReadCrtc;
    pVga->writeGr           = NVWriteGr;
    pVga->readGr            = NVReadGr;
    pVga->writeAttr         = NVWriteAttr;
    pVga->readAttr          = NVReadAttr;
    pVga->writeSeq          = NVWriteSeq;
    pVga->readSeq           = NVReadSeq;
    pVga->writeMiscOut      = NVWriteMiscOut;
    pVga->readMiscOut       = NVReadMiscOut;
    pVga->enablePalette     = NVEnablePalette;
    pVga->disablePalette    = NVDisablePalette;
    pVga->writeDacMask      = NVWriteDacMask;
    pVga->readDacMask       = NVReadDacMask;
    pVga->writeDacWriteAddr = NVWriteDacWriteAddr;
    pVga->writeDacReadAddr  = NVWriteDacReadAddr;
    pVga->writeDacData      = NVWriteDacData;
    pVga->readDacData       = NVReadDacData;
    /*
     * Note: There are different pointers to the CRTC/AR and GR/SEQ registers.
     * Bastardize the intended uses of these to make it work.
     */
    pVga->MMIOBase   = (CARD8 *)pNv;
    pVga->MMIOOffset = 0;

#ifdef XSERVER_LIBPCIACCESS
    err = pci_device_map_range(pNv->PciInfo, pNv->IOAddress, 0x01000000,
			       PCI_DEV_MAP_FLAG_WRITABLE, &tmp);
    if (err != 0) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "pci_device_map_range failed: %s\n", strerror(err));
    }
#else
    tmp = xf86MapPciMem(pScrn->scrnIndex, VIDMEM_MMIO | VIDMEM_READSIDEEFFECT,
                        pNv->PciTag, pNv->IOAddress, 0x01000000);
#endif
    pNv->REGS = tmp;

    pNv->PRAMIN   = pNv->REGS + (0x00710000/4);
    pNv->PCRTC0   = pNv->REGS + (0x00600000/4);
    pNv->PRAMDAC0 = pNv->REGS + (0x00680000/4);
    pNv->PFB      = pNv->REGS + (0x00100000/4);
    pNv->PFIFO    = pNv->REGS + (0x00002000/4);
    pNv->PGRAPH   = pNv->REGS + (0x00400000/4);
    pNv->PEXTDEV  = pNv->REGS + (0x00101000/4);
    pNv->PTIMER   = pNv->REGS + (0x00009000/4);
    pNv->PMC      = pNv->REGS + (0x00000000/4);
    pNv->FIFO     = pNv->REGS + (0x00800000/4);

    /* 8 bit registers */
    pNv->PCIO0    = (U008*)pNv->REGS + 0x00601000;
    pNv->PDIO0    = (U008*)pNv->REGS + 0x00681000;
    pNv->PVIO     = (U008*)pNv->REGS + 0x000C0000;

    pNv->twoHeads =  (pNv->Architecture >= NV_ARCH_10) &&
                     (implementation != 0x0100) &&
                     (implementation != 0x0150) &&
                     (implementation != 0x01A0) &&
                     (implementation != 0x0200);

    pNv->fpScaler = (pNv->FpScale && pNv->twoHeads && (implementation!=0x0110));

    pNv->twoStagePLL = (implementation == 0x0310) ||
                       (implementation == 0x0340) ||
                       (pNv->Architecture >= NV_ARCH_40);

    pNv->WaitVSyncPossible = (pNv->Architecture >= NV_ARCH_10) &&
                             (implementation != 0x0100);

    pNv->BlendingPossible = ((pNv->Chipset & 0xffff) != 0x0020);

    /* look for known laptop chips */
    switch(pNv->Chipset & 0xffff) {
    case 0x0112:
    case 0x0174:
    case 0x0175:
    case 0x0176:
    case 0x0177:
    case 0x0179:
    case 0x017C:
    case 0x017D:
    case 0x0186:
    case 0x0187:
    case 0x018D:
    case 0x0228:
    case 0x0286:
    case 0x028C:
    case 0x0316:
    case 0x0317:
    case 0x031A:
    case 0x031B:
    case 0x031C:
    case 0x031D:
    case 0x031E:
    case 0x031F:
    case 0x0324:
    case 0x0325:
    case 0x0328:
    case 0x0329:
    case 0x032C:
    case 0x032D:
    case 0x0347:
    case 0x0348:
    case 0x0349:
    case 0x034B:
    case 0x034C:
    case 0x0160:
    case 0x0166:
    case 0x0169:
    case 0x016B:
    case 0x016C:
    case 0x016D:
    case 0x00C8:
    case 0x00CC:
    case 0x0144:
    case 0x0146:
    case 0x0148:
    case 0x0098:
    case 0x0099:
        mobile = TRUE;
        break;
    default:
        break;
    }

    if(pNv->Architecture == NV_ARCH_04)
        nv4GetConfig(pNv);
    else
        nv10GetConfig(pNv);

    NVSelectHeadRegisters(pScrn, 0);

    NVLockUnlock(pNv, 0);

    NVI2CInit(pScrn);

    pNv->Television = FALSE;

    vgaHWGetIOBase(pVga);

    if(!pNv->twoHeads) {
       pNv->CRTCnumber = 0;
       if((monitorA = NVProbeDDC(pScrn, 0))) {
           FlatPanel = monitorA->features.input_type ? 1 : 0;

           /* NV4 doesn't support FlatPanels */
           if((pNv->Chipset & 0x0fff) <= 0x0020)
              FlatPanel = 0;
       } else {
           VGA_WR08(pNv->PCIO, 0x03D4, 0x28);
           if(VGA_RD08(pNv->PCIO, 0x03D5) & 0x80) {
              VGA_WR08(pNv->PCIO, 0x03D4, 0x33);
              if(!(VGA_RD08(pNv->PCIO, 0x03D5) & 0x01))
                 Television = TRUE;
              FlatPanel = 1;
           } else {
              FlatPanel = 0;
           }
           xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                         "HW is currently programmed for %s\n",
                          FlatPanel ? (Television ? "TV" : "DFP") : "CRT");
       }

       if(pNv->FlatPanel == -1) {
           pNv->FlatPanel = FlatPanel;
           pNv->Television = Television;
       } else {
           xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                      "Forcing display type to %s as specified\n",
                       pNv->FlatPanel ? "DFP" : "CRT");
       }
    } else {
       CARD8 outputAfromCRTC, outputBfromCRTC;
       int CRTCnumber = -1;
       CARD8 slaved_on_A, slaved_on_B;
       Bool analog_on_A, analog_on_B;
       CARD32 oldhead;
       CARD8 cr44;

       if(implementation != 0x0110) {
           if(pNv->PRAMDAC0[0x0000052C/4] & 0x100)
               outputAfromCRTC = 1;
           else
               outputAfromCRTC = 0;
           if(pNv->PRAMDAC0[0x0000252C/4] & 0x100)
               outputBfromCRTC = 1;
           else
               outputBfromCRTC = 0;
          analog_on_A = NVIsConnected(pScrn, 0);
          analog_on_B = NVIsConnected(pScrn, 1);
       } else {
          outputAfromCRTC = 0;
          outputBfromCRTC = 1;
          analog_on_A = FALSE;
          analog_on_B = FALSE;
       }

       VGA_WR08(pNv->PCIO, 0x03D4, 0x44);
       cr44 = VGA_RD08(pNv->PCIO, 0x03D5);

       VGA_WR08(pNv->PCIO, 0x03D5, 3);
       NVSelectHeadRegisters(pScrn, 1);
       NVLockUnlock(pNv, 0);

       VGA_WR08(pNv->PCIO, 0x03D4, 0x28);
       slaved_on_B = VGA_RD08(pNv->PCIO, 0x03D5) & 0x80;
       if(slaved_on_B) {
           VGA_WR08(pNv->PCIO, 0x03D4, 0x33);
           tvB = !(VGA_RD08(pNv->PCIO, 0x03D5) & 0x01);
       }

       VGA_WR08(pNv->PCIO, 0x03D4, 0x44);
       VGA_WR08(pNv->PCIO, 0x03D5, 0);
       NVSelectHeadRegisters(pScrn, 0);
       NVLockUnlock(pNv, 0);

       VGA_WR08(pNv->PCIO, 0x03D4, 0x28);
       slaved_on_A = VGA_RD08(pNv->PCIO, 0x03D5) & 0x80;
       if(slaved_on_A) {
           VGA_WR08(pNv->PCIO, 0x03D4, 0x33);
           tvA = !(VGA_RD08(pNv->PCIO, 0x03D5) & 0x01);
       }

       oldhead = pNv->PCRTC0[0x00000860/4];
       pNv->PCRTC0[0x00000860/4] = oldhead | 0x00000010;

       monitorA = NVProbeDDC(pScrn, 0);
       monitorB = NVProbeDDC(pScrn, 1);

       if(slaved_on_A && !tvA) {
          CRTCnumber = 0;
          FlatPanel = 1;
          xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                    "CRTC 0 is currently programmed for DFP\n");
       } else
       if(slaved_on_B && !tvB) {
          CRTCnumber = 1;
          FlatPanel = 1;
          xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                    "CRTC 1 is currently programmed for DFP\n");
       } else
       if(analog_on_A) {
          CRTCnumber = outputAfromCRTC;
          FlatPanel = 0;
          xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                    "CRTC %i appears to have a CRT attached\n", CRTCnumber);
       } else
       if(analog_on_B) {
           CRTCnumber = outputBfromCRTC;
           FlatPanel = 0;
           xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                    "CRTC %i appears to have a CRT attached\n", CRTCnumber);
       } else
       if(slaved_on_A) {
          CRTCnumber = 0;
          FlatPanel = 1;
          Television = 1;
          xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                    "CRTC 0 is currently programmed for TV\n");
       } else
       if(slaved_on_B) {
          CRTCnumber = 1;
          FlatPanel = 1;
          Television = 1;
          xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
                    "CRTC 1 is currently programmed for TV\n");
       } else
       if(monitorA) {
           FlatPanel = monitorA->features.input_type ? 1 : 0;
       } else
       if(monitorB) {
           FlatPanel = monitorB->features.input_type ? 1 : 0;
       }

       if(pNv->FlatPanel == -1) {
          if(FlatPanel != -1) {
             pNv->FlatPanel = FlatPanel;
             pNv->Television = Television;
          } else {
             xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                        "Unable to detect display type...\n");
             if(mobile) {
                 xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT,
                            "...On a laptop, assuming DFP\n");
                 pNv->FlatPanel = 1;
             } else {
                 xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT,
                            "...Using default of CRT\n");
                 pNv->FlatPanel = 0;
             }
          }
       } else {
           xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                      "Forcing display type to %s as specified\n",
                       pNv->FlatPanel ? "DFP" : "CRT");
       }

       if(pNv->CRTCnumber == -1) {
          if(CRTCnumber != -1) pNv->CRTCnumber = CRTCnumber;
          else {
             xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                        "Unable to detect which CRTCNumber...\n");
             if(pNv->FlatPanel) pNv->CRTCnumber = 1;
             else pNv->CRTCnumber = 0;
             xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT,
                        "...Defaulting to CRTCNumber %i\n", pNv->CRTCnumber);
          }
       } else {
           xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                      "Forcing CRTCNumber %i as specified\n", pNv->CRTCnumber);
       }

       if(monitorA) {
           if((monitorA->features.input_type && pNv->FlatPanel) ||
              (!monitorA->features.input_type && !pNv->FlatPanel))
           {
               if(monitorB) {
                  free(monitorB);
                  monitorB = NULL;
               }
           } else {
              free(monitorA);
              monitorA = NULL;
           }
       }

       if(monitorB) {
           if((monitorB->features.input_type && !pNv->FlatPanel) ||
              (!monitorB->features.input_type && pNv->FlatPanel))
           {
              free(monitorB);
           } else {
              monitorA = monitorB;
           }
           monitorB = NULL;
       }

       if(implementation == 0x0110)
           cr44 = pNv->CRTCnumber * 0x3;

       pNv->PCRTC0[0x00000860/4] = oldhead;

       VGA_WR08(pNv->PCIO, 0x03D4, 0x44);
       VGA_WR08(pNv->PCIO, 0x03D5, cr44);
       NVSelectHeadRegisters(pScrn, pNv->CRTCnumber);
    }

    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
              "Using %s on CRTC %i\n",
              pNv->FlatPanel ? (pNv->Television ? "TV" : "DFP") : "CRT",
              pNv->CRTCnumber);

    if(pNv->FlatPanel && !pNv->Television) {
       pNv->fpWidth = pNv->PRAMDAC[0x0820/4] + 1;
       pNv->fpHeight = pNv->PRAMDAC[0x0800/4] + 1;
       pNv->fpVTotal = pNv->PRAMDAC[0x804/4] + 1;
       pNv->fpSyncs = pNv->PRAMDAC[0x0848/4] & 0x30000033;
       xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "Panel size is %i x %i\n",
                  pNv->fpWidth, pNv->fpHeight);
       xf86DrvMsg(pScrn->scrnIndex, X_INFO, "NOTE: This driver cannot "
                  "reconfigure the BIOS-programmed size.\n");
       xf86DrvMsg(pScrn->scrnIndex, X_INFO, "These dimensions will be used as "
                  "the panel size for mode validation.\n");
    }

    if(monitorA)
      xf86SetDDCproperties(pScrn, monitorA);

    if(!pNv->FlatPanel || (pScrn->depth != 24) || !pNv->twoHeads)
        pNv->FPDither = FALSE;

    pNv->LVDS = FALSE;
    if(pNv->FlatPanel && pNv->twoHeads) {
        pNv->PRAMDAC0[0x08B0/4] = 0x00010004;
        if(pNv->PRAMDAC0[0x08B4/4] & 1)
           pNv->LVDS = TRUE;
        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Panel is %s\n",
                   pNv->LVDS ? "LVDS" : "TMDS");
    }
}
