/*
 * Copyright 2007, 2008  Luc Verhaegen <lverhaegen@novell.com>
 * Copyright 2007, 2008  Matthias Hopf <mhopf@novell.com>
 * Copyright 2007, 2008  Egbert Eich   <eich@novell.com>
 * Copyright 2007, 2008  Advanced Micro Devices, Inc.
 *
 * 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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.
 */
#define _PARSE_EDID_

#include "common.h"
#include "rhd.h"

#include "edid.h"

#include "xf86DDC.h"

#include "rhd_connector.h"
#include "rhd_modes.h"
#include "rhd_monitor.h"
#ifdef ATOM_BIOS
# include "rhd_atombios.h"
#endif

/* From rhd_edid.c */
void RHDMonitorEDIDSet(struct rhdMonitor *Monitor, xf86MonPtr EDID);


/*
 *
 */

void
RHDMonitorPrint(struct rhdMonitor *Monitor)
{
    int i;

    xf86Msg(X_NONE, "    Bandwidth: %dMHz\n", Monitor->Bandwidth / 1000);
    xf86Msg(X_NONE, "    Horizontal timing:\n");
    for (i = 0; i < Monitor->numHSync; i++)
	xf86Msg(X_NONE, "        %3.1f - %3.1fkHz\n",  Monitor->HSync[i].lo,
		Monitor->HSync[i].hi);
    xf86Msg(X_NONE, "    Vertical timing:\n");
    for (i = 0; i < Monitor->numVRefresh; i++)
	xf86Msg(X_NONE, "        %3.1f - %3.1fHz\n",  Monitor->VRefresh[i].lo,
		Monitor->VRefresh[i].hi);
    xf86Msg(X_NONE, "    DPI: %dx%d\n", Monitor->xDpi, Monitor->yDpi);
    if (Monitor->ReducedAllowed)
	xf86Msg(X_NONE, "    Allows reduced blanking.\n");
    if (Monitor->UseFixedModes)
	xf86Msg(X_NONE, "    Uses Fixed Modes.\n");

    if (!Monitor->Modes)
	xf86Msg(X_NONE, "    No modes are provided.\n");
    else {
      DisplayModePtr Mode;

	xf86Msg(X_NONE, "    Attached modes:\n");
	for (Mode = Monitor->Modes; Mode; Mode = Mode->next) {
	    xf86Msg(X_NONE, "        ");
        RHDPrintModeline(Mode);
      }
    }
}


#if 0

/*
 *
 */
static struct rhdMonitor *
rhdMonitorFromConfig(int scrnIndex, MonPtr Config)
{
    struct rhdMonitor *Monitor;
    DisplayModePtr Mode;
    int i;

    Monitor = xnfcalloc(sizeof(struct rhdMonitor), 1);

    Monitor->Name = xnfstrdup(Config->id);
    Monitor->scrnIndex = scrnIndex;

    if (Config->nHsync) {
        Monitor->numHSync = Config->nHsync;
        for (i = 0; i < Config->nHsync; i++) {
            Monitor->HSync[i].lo = Config->hsync[i].lo;
            Monitor->HSync[i].hi = Config->hsync[i].hi;
        }
    } else if (!Monitor->numHSync) {
        Monitor->numHSync = 3;
        Monitor->HSync[0].lo = 31.5;
        Monitor->HSync[0].hi = 31.5;
        Monitor->HSync[1].lo = 35.15;
        Monitor->HSync[1].hi = 35.15;
        Monitor->HSync[2].lo = 35.5;
        Monitor->HSync[2].hi = 35.5;
    }

    if (Config->nVrefresh) {
        Monitor->numVRefresh = Config->nVrefresh;
        for (i = 0; i < Config->nVrefresh; i++) {
            Monitor->VRefresh[i].lo = Config->vrefresh[i].lo;
            Monitor->VRefresh[i].hi = Config->vrefresh[i].hi;
        }
    } else if (!Monitor->numVRefresh) {
        Monitor->numVRefresh = 1;
        Monitor->VRefresh[0].lo = 50;
        Monitor->VRefresh[0].hi = 61;
    }

#ifdef MONREC_HAS_REDUCED
    if (Config->reducedblanking)
        Monitor->ReducedAllowed = TRUE;
#endif

#ifdef MONREC_HAS_BANDWIDTH
    if (Config->maxPixClock)
        Monitor->Bandwidth = Config->maxPixClock;
#endif

    for (Mode = Config->Modes; Mode; Mode = Mode->next)
	Monitor->Modes = RHDModesAdd(Monitor->Modes, RHDModeCopy(Mode));

    return Monitor;
}
#endif

/*
 *
 */
static struct rhdMonitor *
rhdMonitorFromDefault(RHDPtr rhdPtr)
{
    struct rhdMonitor *Monitor;
    DisplayModePtr Mode;

    Monitor = xnfcalloc(sizeof(struct rhdMonitor), 1);

    Monitor->Name = strdup("Default (SVGA)");
    Monitor->scrnIndex = rhdPtr->scrnIndex;

    /* timing for pathetic 14" svga monitors */
    Monitor->numHSync = 3;
    Monitor->HSync[0].lo = 31.5;
    Monitor->HSync[0].hi = 31.5;
    Monitor->HSync[1].lo = 35.15;
    Monitor->HSync[1].hi = 35.15;
    Monitor->HSync[2].lo = 35.5;
    Monitor->HSync[2].hi = 35.5;

    Monitor->numVRefresh = 1;
    Monitor->VRefresh[0].lo = 50;
    Monitor->VRefresh[0].hi = 61;

    return Monitor;
}

/*
 * This function tries to handle a configured monitor correctly.
 *
 * This either can be forced through the option, or is used when
 * no monitors are autodetected.
 */
void
RHDConfigMonitorSet(RHDPtr rhdPtr, Bool UseConfig)
{
  int i;

  for (i = 0; i < RHD_CONNECTORS_MAX; i++)
    if (rhdPtr->Connector[i] && rhdPtr->Connector[i]->Monitor)
	    break;

  if (i == RHD_CONNECTORS_MAX)
	xf86DrvMsg(scrnIndex, X_INFO, "No monitors autodetected; "
		   "attempting to work around this.\n");

  if (i == RHD_CONNECTORS_MAX)
  {
    rhdPtr->ConfigMonitor = rhdMonitorFromDefault(rhdPtr);

    DBG(dbgprintf("Created monitor from default: \"%s\":\n",
               rhdPtr->ConfigMonitor->Name));

    RHDMonitorPrint(rhdPtr->ConfigMonitor);
  };
}

/*
 * Make sure that we keep only a single mode in our list. This mode should
 * hopefully match our panel at native resolution correctly.
 */
static void
rhdPanelEDIDModesFilter(struct rhdMonitor *Monitor)
{
  DisplayModeRec *Best = Monitor->Modes, *Mode, *Temp;

  RHDFUNC(Monitor);

  if (!Best || !Best->next)
    return; /* don't bother */

    /* don't go for preferred, just take the biggest */
    for (Mode = Best->next; Mode; Mode = Mode->next) {
    if (((Best->HDisplay <= Mode->HDisplay) &&
         (Best->VDisplay < Mode->VDisplay)) ||
        ((Best->HDisplay < Mode->HDisplay) &&
         (Best->VDisplay <= Mode->VDisplay)))
      Best = Mode;
  }

    xf86DrvMsg(Monitor->scrnIndex, X_INFO, "Monitor \"%s\": Using Mode \"%s\""
	       " for native resolution.\n", Monitor->Name, Best->name);

    /* kill all other modes */
  Mode = Monitor->Modes;
    while (Mode) {
    Temp = Mode->next;

	if (Mode != Best) {
	    RHDDebug(Monitor->scrnIndex, "Monitor \"%s\": Discarding Mode \"%s\"\n",
		     Monitor->Name, Mode->name);

	    xfree(Mode->name);
	    xfree(Mode);
    }
    Mode = Temp;
  }

  Best->next = NULL;
  Best->prev = NULL;
  Best->type |= M_T_PREFERRED;
    Monitor->NativeMode = Best;
    Monitor->Modes = Monitor->NativeMode;
  Monitor->numHSync = 1;
  Monitor->HSync[0].lo = Best->HSync;
  Monitor->HSync[0].hi = Best->HSync;
  Monitor->numVRefresh = 1;
  Monitor->VRefresh[0].lo = Best->VRefresh;
  Monitor->VRefresh[0].hi = Best->VRefresh;
  Monitor->Bandwidth = Best->Clock;
}

/*
 *
 */
void
rhdMonitorPrintEDID(struct rhdMonitor *Monitor, xf86MonPtr EDID)
{
    xf86DrvMsg(EDID->scrnIndex, X_INFO, "EDID data for %s\n",
	       Monitor->Name);
  xf86PrintEDID(EDID);
}

/*
 * Panels are the most complicated case we need to handle here.
 * Information can come from several places, and we need to make sure
 * that we end up with only the native resolution in our table.
 */
static struct rhdMonitor *
rhdMonitorPanel(struct rhdConnector *Connector)
{
    struct rhdMonitor *Monitor;
    DisplayModeRec *Mode = NULL;
    xf86MonPtr EDID = NULL;

    RHDFUNC(Connector);

    /* has priority over AtomBIOS EDID */
    if (Connector->DDC)
    EDID = xf86DoEDID_DDC2(Connector->scrnIndex, Connector->DDC);

#ifdef ATOM_BIOS
    {
  RHDPtr rhdPtr = (RHDPtr)Connector->scrnIndex;
	AtomBiosArgRec data;
	AtomBiosResult Result;

  Result = RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
				 ATOMBIOS_GET_PANEL_MODE, &data);
	if (Result == ATOM_SUCCESS) {
	    Mode = data.mode;
	    Mode->type |= M_T_PREFERRED;
	}
	if (!EDID) {
      Result = RHDAtomBiosFunc(rhdPtr,rhdPtr->atomBIOS,
				     ATOMBIOS_GET_PANEL_EDID, &data);
	    if (Result == ATOM_SUCCESS)
    EDID = xf86InterpretEDID(rhdPtr, data.EDIDBlock);
	}
    }
#endif

    Monitor = xnfcalloc(sizeof(struct rhdMonitor), 1);

    Monitor->scrnIndex = Connector->scrnIndex;
    Monitor->EDID      = EDID;

    if (Mode) {
	Monitor->Name = xstrdup("LVDS Panel");
    Monitor->Modes = RHDModesAdd(Monitor->Modes, Mode);
	Monitor->NativeMode = Mode;
    Monitor->numHSync = 1;
    Monitor->HSync[0].lo = Mode->HSync;
    Monitor->HSync[0].hi = Mode->HSync;
    Monitor->numVRefresh = 1;
    Monitor->VRefresh[0].lo = Mode->VRefresh;
    Monitor->VRefresh[0].hi = Mode->VRefresh;
    Monitor->Bandwidth = Mode->SynthClock;

	/* Clueless atombios does give us a mode, but doesn't give us a
	 * DPI or a size. It is just perfect, right? */
	if (EDID) {
      if (EDID->features.hsize)
        Monitor->xDpi = (Mode->HDisplay * 2.54) / ((float) EDID->features.hsize) + 0.5;
      if (EDID->features.vsize)
        Monitor->yDpi = (Mode->VDisplay * 2.54) / ((float) EDID->features.vsize) + 0.5;
    }
    } else if (EDID) {
      RHDMonitorEDIDSet(Monitor, EDID);
      rhdPanelEDIDModesFilter(Monitor);
    } else {
	xf86DrvMsg(Connector->scrnIndex, X_ERROR,
		   "%s: No panel mode information found.\n", __func__);
	xfree(Monitor);
      return NULL;
    }

    /* panel should be driven at native resolution only. */
  Monitor->UseFixedModes = TRUE;
  Monitor->ReducedAllowed = TRUE;

  if (EDID)
    rhdMonitorPrintEDID(Monitor, EDID);

  return Monitor;
}

/*
 * rhdMonitorTV(): get TV modes. Currently we can only get this from AtomBIOS.
 */
static struct rhdMonitor *
rhdMonitorTV(struct rhdConnector *Connector)
{
    struct rhdMonitor *Monitor = NULL;
#ifdef ATOM_BIOS
    RHDPtr rhdPtr = RHDPTRI(Connector);
    DisplayModeRec *Mode = NULL;
    AtomBiosArgRec arg;

    RHDFUNC(Connector);

    arg.tvMode = rhdPtr->tvMode;
    if (RHDAtomBiosFunc(Connector->scrnIndex, rhdPtr->atomBIOS,
			ATOM_ANALOG_TV_MODE, &arg)
	!= ATOM_SUCCESS)
	return NULL;

    Mode = arg.mode;
    Mode->type |= M_T_PREFERRED;

    Monitor = xnfcalloc(sizeof(struct rhdMonitor), 1);

    Monitor->scrnIndex = Connector->scrnIndex;
    Monitor->EDID      = NULL;

    Monitor->Name      = xstrdup("TV");
    Monitor->Modes     = RHDModesAdd(Monitor->Modes, Mode);
    Monitor->NativeMode= Mode;
    Monitor->numHSync  = 1;
    Monitor->HSync[0].lo = Mode->HSync;
    Monitor->HSync[0].hi = Mode->HSync;
    Monitor->numVRefresh = 1;
    Monitor->VRefresh[0].lo = Mode->VRefresh;
    Monitor->VRefresh[0].hi = Mode->VRefresh;
    Monitor->Bandwidth = Mode->SynthClock;

    /* TV should be driven at native resolution only. */
    Monitor->UseFixedModes = TRUE;
    Monitor->ReducedAllowed = FALSE;
    /*
     *  hack: the TV encoder takes care of that.
     *  The mode that goes in isn't what comes out.
     */
    Mode->Flags &= ~(V_INTERLACE);
#endif
    return Monitor;
}

/*
 *
 */
struct rhdMonitor *
RHDMonitorInit(struct rhdConnector *Connector)
{
  struct rhdMonitor *Monitor = NULL;

  RHDFUNC(Connector);

  if (Connector->Type == RHD_CONNECTOR_PANEL)
    Monitor = rhdMonitorPanel(Connector);
    else if (Connector->Type == RHD_CONNECTOR_TV)
	Monitor = rhdMonitorTV(Connector);
    else if (Connector->DDC) {
    xf86MonPtr EDID = xf86DoEDID_DDC2(Connector->scrnIndex, Connector->DDC);
	if (EDID) {
	    Monitor = xnfcalloc(sizeof(struct rhdMonitor), 1);
        Monitor->scrnIndex = Connector->scrnIndex;
        Monitor->EDID   = EDID;
	    Monitor->NativeMode = NULL;

        RHDMonitorEDIDSet(Monitor, EDID);
        rhdMonitorPrintEDID(Monitor, EDID);
      }
    }
  return Monitor;
}

/*
 *
 */
void
RHDMonitorDestroy(struct rhdMonitor *Monitor)
{
    DisplayModePtr Mode, Next;

    for (Mode = Monitor->Modes; Mode;) {
	Next = Mode->next;

	xfree(Mode->name);
	xfree(Mode);

	Mode = Next;
    }

    if (Monitor->EDID)
	xfree(Monitor->EDID->rawData);
    xfree(Monitor->EDID);
    xfree(Monitor->Name);
    xfree(Monitor);
}


static unsigned char * VDIFRead(RHDPtr rhdPtr, I2CBusPtr pBus, int start);

#define RETRIES 4

static xf86VdifLimitsPtr* get_limits(CARD8 *c);
static xf86VdifGammaPtr* get_gamma(CARD8 *c);
static xf86VdifTimingPtr* get_timings(CARD8 *c);

xf86vdifPtr xf86InterpretVdif(CARD8 *c)
{
  xf86VdifPtr p = (xf86VdifPtr)c;
  xf86vdifPtr vdif;
  int i;

  unsigned long l = 0;

  if (c == NULL) return NULL;
  if (p->VDIFId[0] != 'V' || p->VDIFId[1] != 'D' || p->VDIFId[2] != 'I'
      || p->VDIFId[3] != 'F') return NULL;
  for ( i = 12; i < p->FileLength; i++)
    l += c[i];
  if ( l != p->Checksum) return NULL;
  vdif = malloc(sizeof(xf86vdif));
  vdif->vdif = p;
  vdif->limits = get_limits(c);
  vdif->timings = get_timings(c);
  vdif->gamma = get_gamma(c);
  vdif->strings = VDIF_STRING(((xf86VdifPtr)c),0);
  free(c);
  return vdif;
}

static xf86VdifLimitsPtr*
get_limits(CARD8 *c)
{
    int num, i, j;
    xf86VdifLimitsPtr *pp;
    xf86VdifLimitsPtr p;

    num = ((xf86VdifPtr)c)->NumberOperationalLimits;
    pp = malloc(sizeof(xf86VdifLimitsPtr) * (num+1));
    p = VDIF_OPERATIONAL_LIMITS(((xf86VdifPtr)c));
    j = 0;
    for ( i = 0; i<num; i++) {
	if (p->Header.ScnTag == VDIF_OPERATIONAL_LIMITS_TAG)
	    pp[j++] = p;
	VDIF_NEXT_OPERATIONAL_LIMITS(p);
    }
    pp[j] = NULL;
    return pp;
}

static xf86VdifGammaPtr*
get_gamma(CARD8 *c)
{
    int num, i, j;
    xf86VdifGammaPtr *pp;
    xf86VdifGammaPtr p;

    num = ((xf86VdifPtr)c)->NumberOptions;
    pp = malloc(sizeof(xf86VdifGammaPtr) * (num+1));
    p = (xf86VdifGammaPtr)VDIF_OPTIONS(((xf86VdifPtr)c));
    j = 0;
    for ( i = 0; i<num; i++)
    {
	if (p->Header.ScnTag == VDIF_GAMMA_TABLE_TAG)
	    pp[j++] = p;
	VDIF_NEXT_OPTIONS(p);
    }
    pp[j] = NULL;
    return pp;
}

static xf86VdifTimingPtr*
get_timings(CARD8 *c)
{
    int num, num_limits;
    int i,j,k;
    xf86VdifLimitsPtr lp;
    xf86VdifTimingPtr *pp;
    xf86VdifTimingPtr p;

    num = ((xf86VdifPtr)c)->NumberOperationalLimits;
    lp = VDIF_OPERATIONAL_LIMITS(((xf86VdifPtr)c));
    num_limits = 0;
    for (i = 0; i < num; i++) {
	if (lp->Header.ScnTag == VDIF_OPERATIONAL_LIMITS_TAG)
	    num_limits += lp->NumberPreadjustedTimings;
	VDIF_NEXT_OPERATIONAL_LIMITS(lp);
    }
    pp = malloc(sizeof(xf86VdifTimingPtr)
				      * (num_limits+1));
    j = 0;
    lp = VDIF_OPERATIONAL_LIMITS(((xf86VdifPtr) c));
    for (i = 0; i < num; i++) {
	p = VDIF_PREADJUSTED_TIMING(lp);
	for (k = 0; k < lp->NumberPreadjustedTimings; k++) {
	    if (p->Header.ScnTag == VDIF_PREADJUSTED_TIMING_TAG)
		pp[j++] = p;
	    VDIF_NEXT_PREADJUSTED_TIMING(p);
	}
	VDIF_NEXT_OPERATIONAL_LIMITS(lp);
    }
    pp[j] = NULL;
    return pp;
}

int DDC_checksum(unsigned char *block, int len)
{
  int i, result = 0;
  int not_null = 0;

  for (i=0;i<len;i++)
  {
    not_null |= block[i];
    result += block[i];
  }

  if (result & 0xFF) DBG(dbgprintf("DDC checksum not correct\n"));
  if (!not_null) DBG(dbgprintf("DDC read all Null\n"));

    /* catch the trivial case where all bytes are 0 */
  if (!not_null) return 1;

  return (result&0xFF);
}

static unsigned char *
DDCRead_DDC2(RHDPtr rhdPtr, I2CBusPtr pBus, int start, int len)
{
  I2CDevPtr dev;
  unsigned char W_Buffer[2];
  int w_bytes;
  unsigned char *R_Buffer;
  int i;

  RHDFUNC(rhdPtr);

//    xf86LoaderReqSymLists(i2cSymbols, NULL);

  if (!(dev = xf86I2CFindDev(pBus, 0x00A0)))
  {
    dev = xf86CreateI2CDevRec();
    dev->DevName = "ddc2";
    dev->SlaveAddr = 0xA0;
    dev->ByteTimeout = 2200; /* VESA DDC spec 3 p. 43 (+10 %) */
    dev->StartTimeout = 550;
    dev->BitTimeout = 40;
    dev->ByteTimeout = 40;
    dev->AcknTimeout = 40;

    dev->pI2CBus = pBus;
    if (!xf86I2CDevInit(dev))
    {
      DBG(dbgprintf("No DDC2 device\n"));
	    return NULL;
    }
  }
  if (start < 0x100)
  {
    w_bytes = 1;
    W_Buffer[0] = start;
  }
  else
  {
    w_bytes = 2;
    W_Buffer[0] = start & 0xFF;
    W_Buffer[1] = (start & 0xFF00) >> 8;
  }

  R_Buffer = calloc(1,sizeof(unsigned char)* (len));

  if( !R_Buffer)
  {
    DBG(dbgprintf("R_Buffer = NULL\n"));
    return NULL;
  };

  for (i=0; i<RETRIES; i++)
  {
    if (xf86I2CWriteRead(dev, W_Buffer,w_bytes, R_Buffer,len))
    {
	    if (!DDC_checksum(R_Buffer,len))
        return R_Buffer;
      else
        DBG(dbgprintf("Checksum error in EDID block\n"));
    }
    else
      DBG(dbgprintf("Error reading EDID block\n"));
  }
  xf86DestroyI2CDevRec(dev,TRUE);
  free(R_Buffer);
  return NULL;
}

static unsigned char*
EDID1Read_DDC2(RHDPtr rhdPtr, I2CBusPtr pBus)
{
    return  DDCRead_DDC2(rhdPtr, pBus, 0, EDID1_LEN);
}

xf86MonPtr
xf86DoEDID_DDC2(RHDPtr rhdPtr, I2CBusPtr pBus)
{
  unsigned char *EDID_block = NULL;
  unsigned char *VDIF_Block = NULL;
  xf86MonPtr tmp = NULL;

  RHDFUNC(rhdPtr);

  EDID_block = EDID1Read_DDC2(rhdPtr,pBus);

  if (EDID_block)
  {
    tmp = xf86InterpretEDID(rhdPtr,EDID_block);
  }
  else
  {
    DBG(dbgprintf("No EDID block returned\n"));
    return NULL;
  }

  if (!tmp)
  {
    DBG(dbgprintf("Cannot interpret EDID block\n"));
    return tmp;
  }
  DBG(dbgprintf("Sections to follow: %d\n",tmp->no_sections));

  VDIF_Block =
  VDIFRead(rhdPtr, pBus, EDID1_LEN * (tmp->no_sections + 1));
  tmp->vdif = xf86InterpretVdif(VDIF_Block);

  return tmp;
}

static unsigned char*
VDIFRead(RHDPtr rhdPtr, I2CBusPtr pBus, int start)
{
  unsigned char * Buffer, *v_buffer = NULL, *v_bufferp = NULL;
  int i, num = 0;

    /* read VDIF length in 64 byte blocks */
  Buffer = DDCRead_DDC2(rhdPtr, pBus,start,64);
  if (Buffer == NULL)
    return NULL;

  DBG(dbgprintf("number of 64 bit blocks: %i\n",Buffer[0]));

  if ((num = Buffer[0]) > 0)
    v_buffer = v_bufferp = malloc(sizeof(unsigned char) * 64 * num);

  for (i = 0; i < num; i++)
  {
    Buffer = DDCRead_DDC2(rhdPtr, pBus,start,64);
    if (Buffer == NULL)
    {
      free (v_buffer);
	    return NULL;
    }
    memcpy(v_bufferp,Buffer,63); /* 64th byte is checksum */
    free(Buffer);
    v_bufferp += 63;
  }
  return v_buffer;
}



static void print_vendor(RHDPtr rhdPtr, struct vendor *);
static void print_version(RHDPtr rhdPtr, struct edid_version *);
static void print_display(RHDPtr rhdPtr, struct disp_features *,
			  struct edid_version *);
static void print_established_timings(RHDPtr rhdPtr,
				      struct established_timings *);
static void print_std_timings(RHDPtr rhdPtr, struct std_timings *);
static void print_detailed_monitor_section(RHDPtr rhdPtr,
					   struct detailed_monitor_section *);
static void print_detailed_timings(RHDPtr rhdPtr, struct detailed_timings *);

static void print_input_features(RHDPtr rhdPtr, struct disp_features *);
static void print_dpms_features(RHDPtr rhdPtr, struct disp_features *,
				struct edid_version *v);
static void print_whitepoint(RHDPtr rhdPtr, struct disp_features *);
static void print_number_sections(RHDPtr rhdPtr, int);

xf86MonPtr
xf86PrintEDID(xf86MonPtr m)
{
    if (!(m)) return NULL;
    print_vendor(m->rhdPtr,&m->vendor);
    print_version(m->rhdPtr,&m->ver);
    print_display(m->rhdPtr,&m->features, &m->ver);
    print_established_timings(m->rhdPtr,&m->timings1);
    print_std_timings(m->rhdPtr,m->timings2);
    print_detailed_monitor_section(m->rhdPtr,m->det_mon);
    print_number_sections(m->rhdPtr,m->no_sections);

    return m;
}

static void
print_vendor(RHDPtr rhdPtr, struct vendor *c)
{
  DBG(dbgprintf("Manufacturer: %s  Model: %x  Serial#: %u\n",
            (char *)&c->name, c->prod_id, c->serial));
  DBG(dbgprintf("Year: %u  Week: %u\n", c->year, c->week));
}

static void
print_version(RHDPtr rhdPtr, struct edid_version *c)
{
  DBG(dbgprintf("EDID Version: %u.%u\n",c->version,c->revision));
}

static void
print_display(RHDPtr rhdPtr, struct disp_features *disp,
	      struct edid_version *version)
{
    print_input_features(rhdPtr,disp);
    DBG(dbgprintf("Max H-Image Size [cm]: "));
    if (disp->hsize)
      DBG(dbgprintf("horiz.: %i  ",disp->hsize));
    else
      DBG(dbgprintf("H-Size may change,  "));
    if (disp->vsize)
      DBG(dbgprintf("vert.: %i\n",disp->vsize));
    else
      DBG(dbgprintf("V-Size may change\n"));
    DBG(dbgprintf("Gamma: %.2f\n", (double)disp->gamma));
    print_dpms_features(rhdPtr,disp,version);
    print_whitepoint(rhdPtr,disp);
}

static void
print_input_features(RHDPtr rhdPtr, struct disp_features *c)
{
    if (DIGITAL(c->input_type))
    {
      DBG(dbgprintf("Digital Display Input\n"));
      if (DFP1(c->input_dfp))
        DBG(dbgprintf("DFP 1.x compatible TMDS\n"));
    }
    else
    {
      DBG(dbgprintf("Analog Display Input,  "));
      DBG(dbgprintf("Input Voltage Level: "));
      switch (c->input_voltage)
      {
        case V070:
          DBG(dbgprintf("0.700/0.300 V\n"));
          break;
        case V071:
          DBG(dbgprintf("0.714/0.286 V\n"));
          break;
        case V100:
          DBG(dbgprintf("1.000/0.400 V\n"));
          break;
        case V007:
          DBG(dbgprintf("0.700/0.700 V\n"));
          break;
        default:
          DBG(dbgprintf("undefined\n"));
      }
      if (SIG_SETUP(c->input_setup))
        DBG(dbgprintf("Signal levels configurable\n"));
      DBG(dbgprintf("Sync:"));
      if (SEP_SYNC(c->input_sync))
        DBG(dbgprintf("  Separate"));
      if (COMP_SYNC(c->input_sync))
        DBG(dbgprintf("  Composite"));
      if (SYNC_O_GREEN(c->input_sync))
        DBG(dbgprintf("  SyncOnGreen"));
      if (SYNC_SERR(c->input_sync))
        DBG(dbgprintf("Serration on. "
         "V.Sync Pulse req. if CompSync or SyncOnGreen\n"));
      else
        DBG(dbgprintf("\n"));
    }
}

static void
print_dpms_features(RHDPtr rhdPtr, struct disp_features *c,
		    struct edid_version *v)
{
   if (c->dpms)
   {
     DBG(dbgprintf("DPMS capabilities:"));
     if (DPMS_STANDBY(c->dpms))
       DBG(dbgprintf(" StandBy"));
     if (DPMS_SUSPEND(c->dpms))
       DBG(dbgprintf(" Suspend"));
     if (DPMS_OFF(c->dpms))
       DBG(dbgprintf(" Off"));
   }
   else
     DBG(dbgprintf("No DPMS capabilities specified"));
   switch (c->display_type)
   {
     case DISP_MONO:
       DBG(dbgprintf("; Monochorome/GrayScale Display\n"));
       break;
     case DISP_RGB:
       DBG(dbgprintf("; RGB/Color Display\n"));
       break;
     case DISP_MULTCOLOR:
       DBG(dbgprintf("; Non RGB Multicolor Display\n"));
       break;
     default:
       DBG(dbgprintf("\n"));
       break;
   }
   if (STD_COLOR_SPACE(c->msc))
     DBG(dbgprintf("Default color space is primary color space\n"));
   if (PREFERRED_TIMING_MODE(c->msc))
     DBG(dbgprintf("First detailed timing is preferred mode\n"));
   else
     if (v->version == 1 && v->revision >= 3)
       DBG(dbgprintf("First detailed timing not preferred "
                 "mode in violation of standard!"));
   if (GFT_SUPPORTED(c->msc))
     DBG(dbgprintf("GTF timings supported\n"));
}

static void
print_whitepoint(RHDPtr rhdPtr, struct disp_features *disp)
{
    DBG(dbgprintf("redX: %.3f redY: %.3f   ",
         (double)disp->redx,(double)disp->redy));
    DBG(dbgprintf("greenX: %.3f greenY: %.3f\n",
         (double)disp->greenx,(double)disp->greeny));
    DBG(dbgprintf("blueX: %.3f blueY: %.3f   ",
         (double)disp->bluex,(double)disp->bluey));
    DBG(dbgprintf("whiteX: %.3f whiteY: %.3f\n",
         (double)disp->whitex,(double)disp->whitey));
}

static void
print_established_timings(RHDPtr rhdPtr, struct established_timings *t)
{
    unsigned char c;

    if (t->t1 || t->t2 || t->t_manu)
      DBG(dbgprintf("Supported VESA Video Modes:\n"));
    c=t->t1;
    if (c&0x80) DBG(dbgprintf("720x400@70Hz\n"));
    if (c&0x40) DBG(dbgprintf("720x400@88Hz\n"));
    if (c&0x20) DBG(dbgprintf("640x480@60Hz\n"));
    if (c&0x10) DBG(dbgprintf("640x480@67Hz\n"));
    if (c&0x08) DBG(dbgprintf("640x480@72Hz\n"));
    if (c&0x04) DBG(dbgprintf("640x480@75Hz\n"));
    if (c&0x02) DBG(dbgprintf("800x600@56Hz\n"));
    if (c&0x01) DBG(dbgprintf("800x600@60Hz\n"));
    c=t->t2;
    if (c&0x80) DBG(dbgprintf("800x600@72Hz\n"));
    if (c&0x40) DBG(dbgprintf("800x600@75Hz\n"));
    if (c&0x20) DBG(dbgprintf("832x624@75Hz\n"));
    if (c&0x10) DBG(dbgprintf("1024x768@87Hz (interlaced)\n"));
    if (c&0x08) DBG(dbgprintf("1024x768@60Hz\n"));
    if (c&0x04) DBG(dbgprintf("1024x768@70Hz\n"));
    if (c&0x02) DBG(dbgprintf("1024x768@75Hz\n"));
    if (c&0x01) DBG(dbgprintf("1280x1024@75Hz\n"));
    c=t->t_manu;
    if (c&0x80) DBG(dbgprintf("1152x870@75Hz\n"));
    DBG(dbgprintf("Manufacturer's mask: %X\n",c&0x7F));
}

static void
print_std_timings(RHDPtr rhdPtr, struct std_timings *t)
{
    int i;
    char done = 0;
    for (i=0;i<STD_TIMINGS;i++)
    {
      if (t[i].hsize > 256)   /* sanity check */
      {
        if (!done)
        {
          DBG(dbgprintf("Supported Future Video Modes:\n"));
          done = 1;
        }
        DBG(dbgprintf("#%d: hsize: %i  vsize %i  refresh: %i  vid: %i\n",
                      i, t[i].hsize, t[i].vsize, t[i].refresh, t[i].id));
      }
    }
}

static void
print_detailed_monitor_section(RHDPtr rhdPtr,
			       struct detailed_monitor_section *m)
{
  int i,j;

  for (i=0;i<DET_TIMINGS;i++)
  {
    switch (m[i].type)
    {
      case DT:
        print_detailed_timings(rhdPtr,&m[i].section.d_timings);
        break;
      case DS_SERIAL:
        DBG(dbgprintf("Serial No: %s\n",m[i].section.serial));
        break;
      case DS_ASCII_STR:
        DBG(dbgprintf(" %s\n",m[i].section.ascii_data));
        break;
      case DS_NAME:
        DBG(dbgprintf("Monitor name: %s\n",m[i].section.name));
        break;
      case DS_RANGES:
        DBG(dbgprintf("Ranges: V min: %i  V max: %i Hz, H min: %i  H max: %i kHz,",
                   m[i].section.ranges.min_v, m[i].section.ranges.max_v,
                   m[i].section.ranges.min_h, m[i].section.ranges.max_h));
        if (m[i].section.ranges.max_clock != 0)
          DBG(dbgprintf(" PixClock max %i MHz\n",m[i].section.ranges.max_clock));
        else
          DBG(dbgprintf("\n"));
        if (m[i].section.ranges.gtf_2nd_f > 0)
          DBG(dbgprintf(" 2nd GTF parameters: f: %i kHz "
                    "c: %i m: %i k %i j %i\n",
                    m[i].section.ranges.gtf_2nd_f,
                    m[i].section.ranges.gtf_2nd_c,
                    m[i].section.ranges.gtf_2nd_m,
                    m[i].section.ranges.gtf_2nd_k,
                    m[i].section.ranges.gtf_2nd_j));
          break;
      case DS_STD_TIMINGS:
        for (j = 0; j<5; j++)
          DBG(dbgprintf("#%i: hsize: %i  vsize %i  refresh: %i  "
                    "vid: %i\n",i,m[i].section.std_t[i].hsize,
                    m[i].section.std_t[j].vsize,m[i].section.std_t[j].refresh,
                    m[i].section.std_t[j].id));
          break;
      case DS_WHITE_P:
        for (j = 0; j<2; j++)
          if (m[i].section.wp[j].index != 0)
            DBG(dbgprintf("White point %i: whiteX: %f, whiteY: %f; gamma: %f\n",
                      m[i].section.wp[j].index,(double)m[i].section.wp[j].white_x,
                      (double)m[i].section.wp[j].white_y,
                      (double)m[i].section.wp[j].white_gamma));
            break;
      case DS_DUMMY:
      default:
        break;
    }
  }
}

static void
print_detailed_timings(RHDPtr rhdPtr, struct detailed_timings *t)
{

    if (t->clock > 15000000)  /* sanity check */
    {
      DBG(dbgprintf("Supported additional Video Mode:\n"));
      DBG(dbgprintf("clock: %.1f MHz   ",(double)t->clock/1000000.0));
      DBG(dbgprintf("Image Size:  %i x %i mm\n",t->h_size,t->v_size));
      DBG(dbgprintf("h_active: %i  h_sync: %i  h_sync_end %i h_blank_end %i ",
                    t->h_active, t->h_sync_off + t->h_active,
                    t->h_sync_off + t->h_sync_width + t->h_active,
                    t->h_active + t->h_blanking));
      DBG(dbgprintf("h_border: %i\n",t->h_border));
      DBG(dbgprintf("v_active: %i  v_sync: %i  v_sync_end %i v_blanking: %i ",
                    t->v_active, t->v_sync_off + t->v_active,
                    t->v_sync_off + t->v_sync_width + t->v_active,
                    t->v_active + t->v_blanking));
      DBG(dbgprintf("v_border: %i\n",t->v_border));
      if (IS_STEREO(t->stereo))
      {
        DBG(dbgprintf("Stereo: "));
        if (IS_RIGHT_STEREO(t->stereo))
        {
          if (!t->stereo_1)
            DBG(dbgprintf("right channel on sync\n"));
          else
            DBG(dbgprintf("left channel on sync\n"));
        }
        else
          if (IS_LEFT_STEREO(t->stereo))
          {
            if (!t->stereo_1)
              DBG(dbgprintf("right channel on even line\n"));
            else
              DBG(dbgprintf("left channel on evel line\n"));
          }
        if (IS_4WAY_STEREO(t->stereo))
        {
          if (!t->stereo_1)
            DBG(dbgprintf("4-way interleaved\n"));
          else
            DBG(dbgprintf("side-by-side interleaved"));
        }
      }
    }
}

static void
print_number_sections(RHDPtr rhdPtr, int num)
{
  if (num)
    DBG(dbgprintf("Number of EDID sections to follow: %i\n",num));
}



static void get_vendor_section(Uchar*, struct vendor *);
static void get_version_section(Uchar*, struct edid_version *);
static void get_display_section(Uchar*, struct disp_features *,
				struct edid_version *);
static void get_established_timing_section(Uchar*, struct established_timings *);
static void get_std_timing_section(Uchar*, struct std_timings *,
				   struct edid_version *);
static void get_dt_md_section(Uchar *, struct edid_version *,
			      struct detailed_monitor_section *det_mon);
static void copy_string(Uchar *, Uchar *);
static void get_dst_timing_section(Uchar *, struct std_timings *,
				   struct edid_version *);
static void get_monitor_ranges(Uchar *, struct monitor_ranges *);
static void get_whitepoint_section(Uchar *, struct whitePoints *);
static void get_detailed_timing_section(Uchar*, struct 	detailed_timings *);
static Bool validate_version(RHDPtr rhdPtr, struct edid_version *);


xf86MonPtr
xf86InterpretEDID(int scrnIndex, Uchar *block)
{
    xf86MonPtr m;
    RHDPtr rhdPtr = (RHDPtr)scrnIndex;

    if (!block) return NULL;
    if (! (m = calloc(sizeof(xf86Monitor),1))) return NULL;
    m->rhdPtr = rhdPtr;
    m->rawData = block;

    get_vendor_section(SECTION(VENDOR_SECTION,block),&m->vendor);
    get_version_section(SECTION(VERSION_SECTION,block),&m->ver);
    if (!validate_version(rhdPtr, &m->ver)) goto error;
    get_display_section(SECTION(DISPLAY_SECTION,block),&m->features,
			&m->ver);
    get_established_timing_section(SECTION(ESTABLISHED_TIMING_SECTION,block),
				   &m->timings1);
    get_std_timing_section(SECTION(STD_TIMING_SECTION,block),m->timings2,
			   &m->ver);
    get_dt_md_section(SECTION(DET_TIMING_SECTION,block),&m->ver, m->det_mon);
    m->no_sections = (int)*(char *)SECTION(NO_EDID,block);

    return (m);

 error:
    free(m);
    return NULL;
}

static void
get_vendor_section(Uchar *c, struct vendor *r)
{
    r->name[0] = L1;
    r->name[1] = L2;
    r->name[2] = L3;
    r->name[3] = '\0';

    r->prod_id = PROD_ID;
    r->serial  = SERIAL_NO;
    r->week    = WEEK;
    r->year    = YEAR;
}

static void
get_version_section(Uchar *c, struct edid_version *r)
{
    r->version  = VERSION;
    r->revision = REVISION;
}

static void
get_display_section(Uchar *c, struct disp_features *r,
		    struct edid_version *v)
{
    r->input_type = INPUT_TYPE;
    if (!DIGITAL(r->input_type))
    {
      r->input_voltage = INPUT_VOLTAGE;
      r->input_setup = SETUP;
      r->input_sync = SYNC;
    }
    else
      if (v->version > 1 || v->revision > 2)
        r->input_dfp = DFP;
    r->hsize = HSIZE_MAX;
    r->vsize = VSIZE_MAX;
    r->gamma = GAMMA;
    r->dpms =  DPMS;
    r->display_type = DISPLAY_TYPE;
    r->msc = MSC;
    r->redx = REDX;
    r->redy = REDY;
    r->greenx = GREENX;
    r->greeny = GREENY;
    r->bluex = BLUEX;
    r->bluey = BLUEY;
    r->whitex = WHITEX;
    r->whitey = WHITEY;
}

static void
get_established_timing_section(Uchar *c, struct established_timings *r)
{
    r->t1 = T1;
    r->t2 = T2;
    r->t_manu = T_MANU;
}

static void
get_std_timing_section(Uchar *c, struct std_timings *r,
		       struct edid_version *v)
{
    int i;

    for (i=0;i<STD_TIMINGS;i++)
    {
      if (VALID_TIMING)
      {
        r[i].hsize = HSIZE1;
        VSIZE1(r[i].vsize);
        r[i].refresh = REFRESH_R;
        r[i].id = STD_TIMING_ID;
      }
      else
      {
        r[i].hsize = r[i].vsize = r[i].refresh = r[i].id = 0;
       }
       NEXT_STD_TIMING;
    }
}

static void
get_dt_md_section(Uchar *c, struct edid_version *ver,
		  struct detailed_monitor_section *det_mon)
{
  int i;

  for (i=0;i<DET_TIMINGS;i++) {
    if (ver->version == 1 && ver->revision >= 1 && IS_MONITOR_DESC) {

      switch (MONITOR_DESC_TYPE) {
      case SERIAL_NUMBER:
	det_mon[i].type = DS_SERIAL;
	copy_string(c,det_mon[i].section.serial);
	break;
      case ASCII_STR:
	det_mon[i].type = DS_ASCII_STR;
	copy_string(c,det_mon[i].section.ascii_data);
	break;
      case MONITOR_RANGES:
	det_mon[i].type = DS_RANGES;
	get_monitor_ranges(c,&det_mon[i].section.ranges);
	break;
      case MONITOR_NAME:
	det_mon[i].type = DS_NAME;
	copy_string(c,det_mon[i].section.name);
	break;
      case ADD_COLOR_POINT:
	det_mon[i].type = DS_WHITE_P;
	get_whitepoint_section(c,det_mon[i].section.wp);
	break;
      case ADD_STD_TIMINGS:
	det_mon[i].type = DS_STD_TIMINGS;
	get_dst_timing_section(c,det_mon[i].section.std_t, ver);
	break;
      case ADD_DUMMY:
	det_mon[i].type = DS_DUMMY;
        break;
      }
    } else {
      det_mon[i].type = DT;
      get_detailed_timing_section(c,&det_mon[i].section.d_timings);
    }
    NEXT_DT_MD_SECTION;
  }
}

static void
copy_string(Uchar *c, Uchar *s)
{
  int i;
  c = c + 5;
  for (i = 0; (i < 13 && *c != 0x0A); i++)
    *(s++) = *(c++);
  *s = 0;
  while (i-- && (*--s == 0x20)) *s = 0;
}

static void
get_dst_timing_section(Uchar *c, struct std_timings *t,
		       struct edid_version *v)
{
  int j;
    c = c + 5;
    for (j = 0; j < 5; j++) {
	t[j].hsize = HSIZE1;
	VSIZE1(t[j].vsize);
	t[j].refresh = REFRESH_R;
	t[j].id = STD_TIMING_ID;
	NEXT_STD_TIMING;
    }
}

static void
get_monitor_ranges(Uchar *c, struct monitor_ranges *r)
{
    r->min_v = MIN_V;
    r->max_v = MAX_V;
    r->min_h = MIN_H;
    r->max_h = MAX_H;
    r->max_clock = 0;
    if(MAX_CLOCK != 0xff) /* is specified? */
	r->max_clock = MAX_CLOCK * 10;
    if (HAVE_2ND_GTF) {
	r->gtf_2nd_f = F_2ND_GTF;
	r->gtf_2nd_c = C_2ND_GTF;
	r->gtf_2nd_m = M_2ND_GTF;
	r->gtf_2nd_k = K_2ND_GTF;
	r->gtf_2nd_j = J_2ND_GTF;
    } else
	r->gtf_2nd_f = 0;
}

static void
get_whitepoint_section(Uchar *c, struct whitePoints *wp)
{
    wp[1].white_x = WHITEX1;
    wp[1].white_y = WHITEY1;
    wp[2].white_x = WHITEX2;
    wp[2].white_y = WHITEY2;
    wp[1].index  = WHITE_INDEX1;
    wp[2].index  = WHITE_INDEX2;
    wp[1].white_gamma  = WHITE_GAMMA1;
    wp[2].white_gamma  = WHITE_GAMMA2;
}

static void
get_detailed_timing_section(Uchar *c, struct detailed_timings *r)
{
  r->clock = PIXEL_CLOCK;
  r->h_active = H_ACTIVE;
  r->h_blanking = H_BLANK;
  r->v_active = V_ACTIVE;
  r->v_blanking = V_BLANK;
  r->h_sync_off = H_SYNC_OFF;
  r->h_sync_width = H_SYNC_WIDTH;
  r->v_sync_off = V_SYNC_OFF;
  r->v_sync_width = V_SYNC_WIDTH;
  r->h_size = H_SIZE;
  r->v_size = V_SIZE;
  r->h_border = H_BORDER;
  r->v_border = V_BORDER;
  r->interlaced = INTERLACED;
  r->stereo = STEREO;
  r->stereo_1 = STEREO1;
  r->sync = SYNC_T;
  r->misc = MISC;
}


static Bool
validate_version(RHDPtr rhdPtr, struct edid_version *r)
{
    if (r->version != 1)
      return FALSE;
    if (r->revision > 3)
    {
      DBG(dbgprintf("EDID Version 1.%d not yet supported\n",r->revision));
      return FALSE;
    }
    return TRUE;
}
