/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/ati/radeon_bios.c,v 1.0 Exp $ */
/*
 * Copyright 2004 ATI Technologies Inc., Markham, Ontario
 *
 * 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 on 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 (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 ATI, VA LINUX SYSTEMS AND/OR
 * THEIR SUPPLIERS 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 "xf86.h"
#include "xf86_OSproc.h"

#include "radeon.h"
#include "radeon_reg.h"
#include "radeon_macros.h"
#include "radeon_probe.h"
#include "vbe.h"

/* Read the Video BIOS block and the FP registers (if applicable). */
Bool RADEONGetBIOSInfo(ScrnInfoPtr pScrn, xf86Int10InfoPtr  pInt10)
{
    RADEONInfoPtr info     = RADEONPTR(pScrn);
    int tmp;

    if (!(info->VBIOS = xalloc(RADEON_VBIOS_SIZE))) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "Cannot allocate space for hold Video BIOS!\n");
	return FALSE;
    } else {
	if (pInt10) {
	    info->BIOSAddr = pInt10->BIOSseg << 4;
	    (void)memcpy(info->VBIOS, xf86int10Addr(pInt10, info->BIOSAddr),
			 RADEON_VBIOS_SIZE);
	} else {
	    xf86ReadPciBIOS(0, info->PciTag, 0, info->VBIOS, RADEON_VBIOS_SIZE);
	    if (info->VBIOS[0] != 0x55 || info->VBIOS[1] != 0xaa) {
		xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
			   "Video BIOS not detected in PCI space!\n");
		xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
			   "Attempting to read Video BIOS from "
			   "legacy ISA space!\n");
		info->BIOSAddr = 0x000c0000;
		xf86ReadDomainMemory(info->PciTag, info->BIOSAddr,
				     RADEON_VBIOS_SIZE, info->VBIOS);
	    }
	}
    }

    if (info->VBIOS[0] != 0x55 || info->VBIOS[1] != 0xaa) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Unrecognized BIOS signature, BIOS data will not be used\n");
	xfree (info->VBIOS);
	info->VBIOS = NULL;
	return FALSE;
    } 

    if (info->VBIOS) info->ROMHeaderStart = RADEON_BIOS16(0x48);

    if(!info->ROMHeaderStart) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Invalid ROM pointer, BIOS data will not be used\n");
	xfree (info->VBIOS);
	info->VBIOS = NULL;
	return FALSE;
    }
 
    tmp = info->ROMHeaderStart + 4;
    if ((RADEON_BIOS8(tmp)   == 'A' &&
	 RADEON_BIOS8(tmp+1) == 'T' &&
	 RADEON_BIOS8(tmp+2) == 'O' &&
	 RADEON_BIOS8(tmp+3) == 'M') ||
	(RADEON_BIOS8(tmp)   == 'M' &&
	 RADEON_BIOS8(tmp+1) == 'O' &&
	 RADEON_BIOS8(tmp+2) == 'T' &&
	 RADEON_BIOS8(tmp+3) == 'A'))
	info->IsAtomBios = TRUE;
    else
	info->IsAtomBios = FALSE;

    if (info->IsAtomBios) 
	info->MasterDataStart = RADEON_BIOS16 (info->ROMHeaderStart + 32);

    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "%s BIOS detected\n",
	       info->IsAtomBios ? "ATOM":"Legacy");

    return TRUE;
}

Bool RADEONGetConnectorInfoFromBIOS (ScrnInfoPtr pScrn)
{
    RADEONInfoPtr info = RADEONPTR (pScrn);
    RADEONEntPtr pRADEONEnt = RADEONEntPriv(pScrn);
    int i = 0, j, tmp, tmp0=0, tmp1=0;

    if(!info->VBIOS) return FALSE;

    if (info->IsAtomBios) {
	if((tmp = RADEON_BIOS16 (info->MasterDataStart + 22))) {
	    int crtc = 0, id[2];
	    tmp1 = RADEON_BIOS16 (tmp + 4);
	    for (i=0; i<8; i++) {
		if(tmp1 & (1<<i)) {
		    CARD16 portinfo = RADEON_BIOS16(tmp+6+i*2);
		    if (crtc < 2) {
			if ((i==2) || (i==6)) continue; /* ignore TV here */

			if (crtc == 1) {
			    /* sharing same port with id[0] */
			    if (((portinfo>>8) & 0xf) == id[0]) {
				if (i == 3) 
				    pRADEONEnt->PortInfo[0].TMDSType = TMDS_INT;
				else if (i == 7)
				    pRADEONEnt->PortInfo[0].TMDSType = TMDS_EXT;

				if (pRADEONEnt->PortInfo[0].DACType == DAC_UNKNOWN)
				    pRADEONEnt->PortInfo[0].DACType = (portinfo & 0xf) - 1;
				continue;
			    }
			}

			id[crtc] = (portinfo>>8) & 0xf; 
			pRADEONEnt->PortInfo[crtc].DACType = (portinfo & 0xf) - 1;
			pRADEONEnt->PortInfo[crtc].ConnectorType = (portinfo>>4) & 0xf;
			if (i == 3) 
			    pRADEONEnt->PortInfo[crtc].TMDSType = TMDS_INT;
			else if (i == 7)
			    pRADEONEnt->PortInfo[crtc].TMDSType = TMDS_EXT;
			
			if((tmp0 = RADEON_BIOS16 (info->MasterDataStart + 24)) && id[crtc]) {
			    switch (RADEON_BIOS16 (tmp0 + 4 + 27 * id[crtc]) * 4) 
			    {
			    case RADEON_GPIO_MONID:
				pRADEONEnt->PortInfo[crtc].DDCType = DDC_MONID;
				break;
			    case RADEON_GPIO_DVI_DDC:
				pRADEONEnt->PortInfo[crtc].DDCType = DDC_DVI;
				break;
			    case RADEON_GPIO_VGA_DDC:
				pRADEONEnt->PortInfo[crtc].DDCType = DDC_VGA;
				break;
			    case RADEON_GPIO_CRT2_DDC:
				pRADEONEnt->PortInfo[crtc].DDCType = DDC_CRT2;
				break;
			    default:
				pRADEONEnt->PortInfo[crtc].DDCType = DDC_NONE;
				break;
			    }

			} else {
			    pRADEONEnt->PortInfo[crtc].DDCType = DDC_NONE;
			}
			crtc++;
		    } else {
			/* we have already had two CRTCs assigned. the rest may share the same
			 * port with the existing connector, fill in them accordingly.
			 */
			for (j=0; j<2; j++) {
			    if (((portinfo>>8) & 0xf) == id[j]) {
				if (i == 3) 
				    pRADEONEnt->PortInfo[j].TMDSType = TMDS_INT;
				else if (i == 7)
				    pRADEONEnt->PortInfo[j].TMDSType = TMDS_EXT;

				if (pRADEONEnt->PortInfo[j].DACType == DAC_UNKNOWN)
				    pRADEONEnt->PortInfo[j].DACType = (portinfo & 0xf) - 1;
			    }
			}
		    }
		}
	    }

	    for (i=0; i<2; i++) {
		xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Port%d: DDCType-%d, DACType-%d, TMDSType-%d, ConnectorType-%d\n",
			   i, pRADEONEnt->PortInfo[i].DDCType, pRADEONEnt->PortInfo[i].DACType,
			   pRADEONEnt->PortInfo[i].TMDSType, pRADEONEnt->PortInfo[i].ConnectorType);
	    }	    
	} else {
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "No Device Info Table found!\n");
	    return FALSE;
	}
    } else {
	/* Some laptops only have one connector (VGA) listed in the connector table, 
	 * we need to add LVDS in as a non-DDC display. 
	 * Note, we can't assume the listed VGA will be filled in PortInfo[0],
	 * when walking through connector table. connector_found has following meaning: 
	 * 0 -- nothing found, 
	 * 1 -- only PortInfo[0] filled, 
	 * 2 -- only PortInfo[1] filled,
	 * 3 -- both are filled.
	 */
	int connector_found = 0;

	if ((tmp = RADEON_BIOS16(info->ROMHeaderStart + 0x50))) {
	    for (i = 1; i < 4; i++) {

		if (!RADEON_BIOS16(tmp + i*2))
			break; /* end of table */
		
		tmp0 = RADEON_BIOS16(tmp + i*2);
		if (((tmp0 >> 12) & 0x0f) == 0) continue;     /* no connector */
		if (connector_found > 0) {
		    if (pRADEONEnt->PortInfo[tmp1].DDCType == ((tmp0 >> 8) & 0x0f))
			continue;                             /* same connector */
		}

		/* internal DDC_DVI port will get assigned to PortInfo[0], or if there is no DDC_DVI (like in some IGPs). */
		tmp1 = ((((tmp0 >> 8) & 0xf) == DDC_DVI) || (tmp1 == 1)) ? 0 : 1; /* determine port info index */
		
		pRADEONEnt->PortInfo[tmp1].DDCType        = (tmp0 >> 8) & 0x0f;
		if (pRADEONEnt->PortInfo[tmp1].DDCType > DDC_CRT2) pRADEONEnt->PortInfo[tmp1].DDCType = DDC_NONE_DETECTED;
		pRADEONEnt->PortInfo[tmp1].DACType        = (tmp0 & 0x01) ? DAC_TVDAC : DAC_PRIMARY;
		pRADEONEnt->PortInfo[tmp1].ConnectorType  = (tmp0 >> 12) & 0x0f;
		if (pRADEONEnt->PortInfo[tmp1].ConnectorType > CONNECTOR_UNSUPPORTED) pRADEONEnt->PortInfo[tmp1].ConnectorType = CONNECTOR_UNSUPPORTED;
		pRADEONEnt->PortInfo[tmp1].TMDSType       = ((tmp0 >> 4) & 0x01) ? TMDS_EXT : TMDS_INT;

		/* some sanity checks */
		if (((pRADEONEnt->PortInfo[tmp1].ConnectorType != CONNECTOR_DVI_D) &&
		     (pRADEONEnt->PortInfo[tmp1].ConnectorType != CONNECTOR_DVI_I)) &&
		    pRADEONEnt->PortInfo[tmp1].TMDSType == TMDS_INT)
		    pRADEONEnt->PortInfo[tmp1].TMDSType = TMDS_UNKNOWN;
		
		connector_found += (tmp1 + 1);
	    }
	} else {
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "No Connector Info Table found!\n");
	    return FALSE;
	}

	if (info->IsMobility) {
	    /* For the cases where only one VGA connector is found, 
	       we assume LVDS is not listed in the connector table, 
	       add it in here as the first port.
	    */
	    if ((connector_found < 3) && (pRADEONEnt->PortInfo[tmp1].ConnectorType == CONNECTOR_CRT)) {
		if (connector_found == 1) {
		    memcpy (&pRADEONEnt->PortInfo[1], &pRADEONEnt->PortInfo[0], 
			    sizeof (pRADEONEnt->PortInfo[0]));
		}
		pRADEONEnt->PortInfo[0].DACType = DAC_TVDAC;
		pRADEONEnt->PortInfo[0].TMDSType = TMDS_UNKNOWN;
		pRADEONEnt->PortInfo[0].DDCType = DDC_NONE_DETECTED;
		pRADEONEnt->PortInfo[0].ConnectorType = CONNECTOR_PROPRIETARY;

		xf86DrvMsg(pScrn->scrnIndex, X_INFO, "LVDS port is not in connector table, added in.\n");
		if (connector_found == 0) connector_found = 1;
		else connector_found = 3;
	    }

	    if ((tmp = RADEON_BIOS16(info->ROMHeaderStart + 0x42))) {
	        if ((tmp0 = RADEON_BIOS16(tmp + 0x15))) {
		    if ((tmp1 = RADEON_BIOS8(tmp0+2) & 0x07)) {	    
			pRADEONEnt->PortInfo[0].DDCType	= tmp1;      
			xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "LCD DDC Info Table found!\n");
		    }
		}
	    } 
	} else if (connector_found == 2) {
	    memcpy (&pRADEONEnt->PortInfo[0], &pRADEONEnt->PortInfo[1], 
		    sizeof (pRADEONEnt->PortInfo[0]));	
	    pRADEONEnt->PortInfo[1].DACType = DAC_UNKNOWN;
	    pRADEONEnt->PortInfo[1].TMDSType = TMDS_UNKNOWN;
	    pRADEONEnt->PortInfo[1].DDCType = DDC_NONE_DETECTED;
	    pRADEONEnt->PortInfo[1].ConnectorType = CONNECTOR_NONE;
	    connector_found = 1;
	}

	if (connector_found == 0) {
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "No connector found in Connector Info Table.\n");
	} else {
	    xf86DrvMsg(0, X_INFO, "Connector0: DDCType-%d, DACType-%d, TMDSType-%d, ConnectorType-%d\n",
		       pRADEONEnt->PortInfo[0].DDCType, pRADEONEnt->PortInfo[0].DACType,
		       pRADEONEnt->PortInfo[0].TMDSType, pRADEONEnt->PortInfo[0].ConnectorType);
	}
	if (connector_found == 3) {
	    xf86DrvMsg(0, X_INFO, "Connector1: DDCType-%d, DACType-%d, TMDSType-%d, ConnectorType-%d\n",
		       pRADEONEnt->PortInfo[1].DDCType, pRADEONEnt->PortInfo[1].DACType,
		       pRADEONEnt->PortInfo[1].TMDSType, pRADEONEnt->PortInfo[1].ConnectorType);
	}

#if 0
/* External TMDS Table, not used now */
        if ((tmp0 = RADEON_BIOS16(info->ROMHeaderStart + 0x58))) {

            //pRADEONEnt->PortInfo[1].DDCType = (RADEON_BIOS8(tmp0 + 7) & 0x07);
            //pRADEONEnt->PortInfo[1].ConnectorType  = CONNECTOR_DVI_I;
            //pRADEONEnt->PortInfo[1].TMDSType = TMDS_EXT;
            xf86DrvMsg(pScrn->scrnIndex, X_INFO, "External TMDS found.\n");

        } else {
            xf86DrvMsg(pScrn->scrnIndex, X_INFO, "NO External TMDS Info found\n");

        }
#endif

    }
    return TRUE;
}

/* Read PLL parameters from BIOS block.  Default to typical values if there
   is no BIOS. */
Bool RADEONGetClockInfoFromBIOS (ScrnInfoPtr pScrn)
{
    RADEONInfoPtr info = RADEONPTR (pScrn);
    RADEONPLLPtr pll = &info->pll;
    CARD16 pll_info_block;

    if (!info->VBIOS) {
	return FALSE;
    } else {
	if (info->IsAtomBios) {
	    pll_info_block = RADEON_BIOS16 (info->MasterDataStart + 12);

	    pll->reference_freq = RADEON_BIOS16 (pll_info_block + 82);
	    pll->reference_div = 0; /* Need to derive from existing setting
					or use a new algorithm to calculate
					from min_input and max_input
				     */
	    pll->min_pll_freq = RADEON_BIOS16 (pll_info_block + 78);
	    pll->max_pll_freq = RADEON_BIOS32 (pll_info_block + 32);
	    pll->xclk = RADEON_BIOS16 (pll_info_block + 72);

	    info->sclk = RADEON_BIOS32(pll_info_block + 8) / 100.0;
	    info->mclk = RADEON_BIOS32(pll_info_block + 12) / 100.0;
	    if (info->sclk == 0) info->sclk = 200;
	    if (info->mclk == 0) info->mclk = 200;
		
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "ref_freq: %d, min_pll: %ld, max_pll: %ld, xclk: %d, sclk: %f, mclk: %f\n",
		       pll->reference_freq, pll->min_pll_freq, pll->max_pll_freq, pll->xclk, info->sclk, info->mclk);

	} else {
	    pll_info_block = RADEON_BIOS16 (info->ROMHeaderStart + 0x30);

	    pll->reference_freq = RADEON_BIOS16 (pll_info_block + 0x0e);
	    pll->reference_div = RADEON_BIOS16 (pll_info_block + 0x10);
	    pll->min_pll_freq = RADEON_BIOS32 (pll_info_block + 0x12);
	    pll->max_pll_freq = RADEON_BIOS32 (pll_info_block + 0x16);
	    pll->xclk = RADEON_BIOS16 (pll_info_block + 0x08);

	    info->sclk = RADEON_BIOS16(pll_info_block + 8) / 100.0;
	    info->mclk = RADEON_BIOS16(pll_info_block + 10) / 100.0;
	}
    }

    return TRUE;
}

Bool RADEONGetLVDSInfoFromBIOS (ScrnInfoPtr pScrn)
{
    RADEONInfoPtr info     = RADEONPTR(pScrn);
    unsigned long tmp, i;

    if (!info->VBIOS) return FALSE;

    if (info->IsAtomBios) {
	if((tmp = RADEON_BIOS16 (info->MasterDataStart + 16))) {

	    info->PanelXRes = RADEON_BIOS16(tmp+6);
	    info->PanelYRes = RADEON_BIOS16(tmp+10);
	    info->DotClock   = RADEON_BIOS16(tmp+4)*10;
	    info->HBlank     = RADEON_BIOS16(tmp+8);
	    info->HOverPlus  = RADEON_BIOS16(tmp+14);
	    info->HSyncWidth = RADEON_BIOS16(tmp+16);
	    info->VBlank     = RADEON_BIOS16(tmp+12);
	    info->VOverPlus  = RADEON_BIOS16(tmp+18);
	    info->VSyncWidth = RADEON_BIOS16(tmp+20);
	    info->PanelPwrDly = RADEON_BIOS16(tmp+40);

	    info->Flags = 0;
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING, 
		       "LVDS Info:\n"
		       "XRes: %d, YRes: %d, DotClock: %d\n"
		       "HBlank: %d, HOverPlus: %d, HSyncWidth: %d\n"
		       "VBlank: %d, VOverPlus: %d, VSyncWidth: %d\n",
		       info->PanelXRes, info->PanelYRes, info->DotClock,
		       info->HBlank,info->HOverPlus, info->HSyncWidth,
		       info->VBlank, info->VOverPlus, info->VSyncWidth);
	} else {
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		       "No LVDS Info Table found in BIOS!\n");
	    return FALSE;
	}
    } else {

	tmp = RADEON_BIOS16(info->ROMHeaderStart + 0x40);

	if (!tmp) {
	    xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		       "No Panel Info Table found in BIOS!\n");
	    return FALSE;
	} else {
	    char  stmp[30];
	    int   tmp0;

	    for (i = 0; i < 24; i++)
	    stmp[i] = RADEON_BIOS8(tmp+i+1);
	    stmp[24] = 0;

	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "Panel ID string: %s\n", stmp);

	    info->PanelXRes = RADEON_BIOS16(tmp+25);
	    info->PanelYRes = RADEON_BIOS16(tmp+27);
	    xf86DrvMsg(0, X_INFO, "Panel Size from BIOS: %dx%d\n",
		       info->PanelXRes, info->PanelYRes);
	
	    info->PanelPwrDly = RADEON_BIOS16(tmp+44);
	    if (info->PanelPwrDly > 2000 || info->PanelPwrDly < 0)
		info->PanelPwrDly = 2000;

	    /* some panels only work well with certain divider combinations.
	     */
	    info->RefDivider = RADEON_BIOS16(tmp+46);
	    info->PostDivider = RADEON_BIOS8(tmp+48);
	    info->FeedbackDivider = RADEON_BIOS16(tmp+49);
	    if ((info->RefDivider != 0) &&
		(info->FeedbackDivider > 3)) {
		info->UseBiosDividers = TRUE;
		xf86DrvMsg(pScrn->scrnIndex, X_INFO,
			   "BIOS provided dividers will be used.\n");
	    }

	    /* We don't use a while loop here just in case we have a corrupted BIOS image.
	       The max number of table entries is 23 at present, but may grow in future.
	       To ensure it works with future revisions we loop it to 32.
	    */
	    for (i = 0; i < 32; i++) {
		tmp0 = RADEON_BIOS16(tmp+64+i*2);
		if (tmp0 == 0) break;
		if ((RADEON_BIOS16(tmp0) == info->PanelXRes) &&
		    (RADEON_BIOS16(tmp0+2) == info->PanelYRes)) {
		    info->HBlank     = (RADEON_BIOS16(tmp0+17) -
					RADEON_BIOS16(tmp0+19)) * 8;
		    info->HOverPlus  = (RADEON_BIOS16(tmp0+21) -
					RADEON_BIOS16(tmp0+19) - 1) * 8;
		    info->HSyncWidth = RADEON_BIOS8(tmp0+23) * 8;
		    info->VBlank     = (RADEON_BIOS16(tmp0+24) -
					RADEON_BIOS16(tmp0+26));
		    info->VOverPlus  = ((RADEON_BIOS16(tmp0+28) & 0x7ff) -
					RADEON_BIOS16(tmp0+26));
		    info->VSyncWidth = ((RADEON_BIOS16(tmp0+28) & 0xf800) >> 11);
		    info->DotClock   = RADEON_BIOS16(tmp0+9) * 10;
		    info->Flags = 0;
		}
	    }
	}
    }
    return TRUE;
}

Bool RADEONGetHardCodedEDIDFromBIOS (ScrnInfoPtr pScrn)
{
    RADEONInfoPtr info     = RADEONPTR(pScrn);
    unsigned long tmp;
    char EDID[256];

    if (!info->VBIOS) return FALSE;

    if (info->IsAtomBios) {
	/* Not yet */
	return FALSE;
    } else {
	if (!(tmp = RADEON_BIOS16(info->ROMHeaderStart + 0x4c))) {
	    return FALSE;
	}

	memcpy(EDID, (char*)(info->VBIOS + tmp), 256);

	info->DotClock = (*(CARD16*)(EDID+54)) * 10;
	info->PanelXRes = (*(CARD8*)(EDID+56)) + ((*(CARD8*)(EDID+58))>>4)*256;
	info->HBlank = (*(CARD8*)(EDID+57)) + ((*(CARD8*)(EDID+58)) & 0xf)*256;
	info->HOverPlus = (*(CARD8*)(EDID+62)) + ((*(CARD8*)(EDID+65)>>6)*256);
	info->HSyncWidth = (*(CARD8*)(EDID+63)) + (((*(CARD8*)(EDID+65)>>4) & 3)*256);
	info->PanelYRes = (*(CARD8*)(EDID+59)) + ((*(CARD8*)(EDID+61))>>4)*256;
	info->VBlank = ((*(CARD8*)(EDID+60)) + ((*(CARD8*)(EDID+61)) & 0xf)*256);
	info->VOverPlus = (((*(CARD8*)(EDID+64))>>4) + (((*(CARD8*)(EDID+65)>>2) & 3)*16));
	info->VSyncWidth = (((*(CARD8*)(EDID+64)) & 0xf) + ((*(CARD8*)(EDID+65)) & 3)*256);
	info->Flags      = V_NHSYNC | V_NVSYNC; /**(CARD8*)(EDID+71);*/
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Hardcoded EDID data will be used for TMDS panel\n");
    }
    return TRUE;
}

Bool RADEONGetTMDSInfoFromBIOS (ScrnInfoPtr pScrn)
{
    RADEONInfoPtr info     = RADEONPTR(pScrn);
    CARD32 tmp, maxfreq;
    int i, n;

    if (!info->VBIOS) return FALSE;

    if (info->IsAtomBios) {
	if((tmp = RADEON_BIOS16 (info->MasterDataStart + 18))) {

	    maxfreq = RADEON_BIOS16(tmp+4);
	    
	    for (i=0; i<4; i++) {
		info->tmds_pll[i].freq = RADEON_BIOS16(tmp+i*6+6);
		/* This assumes each field in TMDS_PLL has 6 bit as in R300/R420 */
		info->tmds_pll[i].value = ((RADEON_BIOS8(tmp+i*6+8) & 0x3f) |
					   ((RADEON_BIOS8(tmp+i*6+10) & 0x3f)<<6) |
					   ((RADEON_BIOS8(tmp+i*6+9) & 0xf)<<12) |
					   ((RADEON_BIOS8(tmp+i*6+11) & 0xf)<<16));
		xf86DrvMsg(pScrn->scrnIndex, X_INFO, 
			   "TMDS PLL from BIOS: %ld %lx\n", 
			   info->tmds_pll[i].freq, info->tmds_pll[i].value);
		       
		if (maxfreq == info->tmds_pll[i].freq) {
		    info->tmds_pll[i].freq = 0xffffffff;
		    break;
		}
	    }
	    return TRUE;
	}
    } else {

	tmp = RADEON_BIOS16(info->ROMHeaderStart + 0x34);
	if (tmp) {
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "DFP table revision: %d\n", RADEON_BIOS8(tmp));
	    if (RADEON_BIOS8(tmp) == 3) {
		n = RADEON_BIOS8(tmp + 5) + 1;
		if (n > 4) n = 4;
		for (i=0; i<n; i++) {
		    info->tmds_pll[i].value = RADEON_BIOS32(tmp+i*10+0x08);
		    info->tmds_pll[i].freq = RADEON_BIOS16(tmp+i*10+0x10);
		}
		return TRUE;
	    }

	    /* revision 4 has some problem as it appears in RV280, 
	       comment it off for now, use default instead */ 
	    /*    
		  else if (RADEON_BIOS8(tmp) == 4) {
		  int stride = 0;
		  n = RADEON_BIOS8(tmp + 5) + 1;
		  if (n > 4) n = 4;
		  for (i=0; i<n; i++) {
		  info->tmds_pll[i].value = RADEON_BIOS32(tmp+stride+0x08);
		  info->tmds_pll[i].freq = RADEON_BIOS16(tmp+stride+0x10);
		  if (i == 0) stride += 10;
		  else stride += 6;
		  }
		  return TRUE;
		  }
	    */  
	}
    }
    return FALSE;
}
