/*
 * Copyright 2007, 2008  Luc Verhaegen <libv@exsuse.de>
 * 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.
 */

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

#include "xf86.h"
#include "xf86i2c.h"
#include "edid.h"

/* for usleep */
#if HAVE_XF86_ANSIC_H
# include "xf86_ansic.h"
#else
# include <unistd.h>
# include <string.h>
# include <stdio.h>
#endif

#include "rhd.h"
#ifdef ATOM_BIOS
# include "rhd_atombios.h"
#endif
#include "rhd_connector.h"
#include "rhd_output.h"
#include "rhd_regs.h"
#include "rhd_monitor.h"
#include "rhd_card.h"
#include "rhd_i2c.h"

/*
 *
 */
struct rhdHPD {
    Bool Stored;
    CARD32 StoreMask;
    CARD32 StoreEnable;
};

/*
 *
 */
void
RHDHPDSave(RHDPtr rhdPtr)
{
    struct rhdHPD *hpd = rhdPtr->HPD;

    RHDFUNC(rhdPtr);

    hpd->StoreMask = RHDRegRead(rhdPtr, DC_GPIO_HPD_MASK);
    hpd->StoreEnable = RHDRegRead(rhdPtr, DC_GPIO_HPD_EN);

    hpd->Stored = TRUE;
}

/*
 *
 */
void
RHDHPDRestore(RHDPtr rhdPtr)
{
    struct rhdHPD *hpd = rhdPtr->HPD;

    RHDFUNC(rhdPtr);

    if (hpd->Stored) {
	RHDRegWrite(rhdPtr, DC_GPIO_HPD_MASK, hpd->StoreMask);
	RHDRegWrite(rhdPtr, DC_GPIO_HPD_EN, hpd->StoreEnable);
    } else
	xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR,
		   "%s: no registers stored.\n", __func__);
}

/*
 *
 */
static void
RHDHPDSet(RHDPtr rhdPtr)
{
    RHDFUNC(rhdPtr);

    /* give the hw full control */
    RHDRegWrite(rhdPtr, DC_GPIO_HPD_MASK, 0);
    RHDRegWrite(rhdPtr, DC_GPIO_HPD_EN, 0);

    usleep(1);
}

/*
 *
 */
static Bool
RHDHPDCheck(struct rhdConnector *Connector)
{
    Bool ret;

    RHDFUNC(Connector);

    ret = RHDRegRead(Connector, DC_GPIO_HPD_Y);
    RHDDebug(Connector->scrnIndex, "%s returned: %x mask: %x\n",
	     __func__,ret, Connector->HPDMask);

    return (ret & Connector->HPDMask);
}

struct rhdCsState {
    int vga_cnt;
    int dvi_cnt;
};

/*
 *
 */
static char *
rhdConnectorSynthName(struct rhdConnectorInfo *ConnectorInfo,
		      struct rhdCsState **state)
{
    char *str = NULL;
    char *TypeName;
    char *str1, *str2;
    int cnt;

    ASSERT(state != NULL);

    if (!*state) {
	if (!(*state = xcalloc(sizeof(struct rhdCsState), 1)))
	    return NULL;
    }
    switch (ConnectorInfo->Type) {
	case RHD_CONNECTOR_NONE:
	    return NULL;
	case RHD_CONNECTOR_DVI:
	case RHD_CONNECTOR_DVI_SINGLE:
	    if (ConnectorInfo->Output[0] && ConnectorInfo->Output[1]) {
		TypeName = "DVI-I";
		cnt = ++(*state)->dvi_cnt;
	    } else if (ConnectorInfo->Output[0] == RHD_OUTPUT_DACA
		     || ConnectorInfo->Output[0] == RHD_OUTPUT_DACB
		     || ConnectorInfo->Output[1] == RHD_OUTPUT_DACA
		     || ConnectorInfo->Output[1] == RHD_OUTPUT_DACB
		) {
		if (ConnectorInfo->HPD == RHD_HPD_NONE) {
		    TypeName = "VGA";
		    cnt = ++(*state)->vga_cnt;
		} else {
		    TypeName = "DVI-A";
		    cnt = ++(*state)->dvi_cnt;
		}
	    } else {
		TypeName = "DVI-D";
		cnt = ++(*state)->dvi_cnt;
	    }
	    str = xalloc(12);
	    snprintf(str, 11, "%s %i",TypeName, cnt);
	    return str;

	case RHD_CONNECTOR_VGA:
	    str = xalloc(10);
	    snprintf(str, 9, "VGA %i",++(*state)->vga_cnt);
	    return str;

	case RHD_CONNECTOR_PANEL:
	    str = xalloc(10);
	    snprintf(str, 9, "PANEL");
	    return str;

	case RHD_CONNECTOR_TV:
	    str1 = xstrdup(ConnectorInfo->Name);
	    str = xalloc(20);
	    str2 = strchr(str1, ' ');
	    if (str2) *(str2) = '\0';
	    snprintf(str, 20, "TV %s",str1);
	    xfree(str1);
	    return str;

	case RHD_CONNECTOR_PCIE: /* should never get here */
	    return NULL;
    }
    return NULL;
}

/*
 *
 */
Bool
RHDConnectorsInit(RHDPtr rhdPtr, struct rhdCard *Card)
{
    struct rhdConnectorInfo *ConnectorInfo;
    struct rhdConnector *Connector;
    struct rhdOutput *Output;
    struct rhdCsState *csstate = NULL;
    int i, j, k, l, hpd;
    Bool InfoAllocated = FALSE;

    RHDFUNC(rhdPtr);

    /* Card->ConnectorInfo is there to work around quirks, so check it first */
    if (Card && (Card->ConnectorInfo[0].Type != RHD_CONNECTOR_NONE)) {
	ConnectorInfo = Card->ConnectorInfo;
	xf86DrvMsg(rhdPtr->scrnIndex, X_INFO,
		   "ConnectorInfo from quirk table:\n");
	RhdPrintConnectorInfo (rhdPtr->scrnIndex, ConnectorInfo);
    } else {
#ifdef ATOM_BIOS
	/* common case */
	AtomBiosArgRec data;
	AtomBiosResult result;

	data.chipset = rhdPtr->ChipSet;
	result = RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS,
				 ATOM_GET_CONNECTORS, &data);
	if (result == ATOM_SUCCESS) {
	    ConnectorInfo = data.ConnectorInfo;
	    InfoAllocated = TRUE;
	} else
#endif
        {
	    xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR, "%s: Failed to retrieve "
		       "Connector information.\n", __func__);
	    return FALSE;
	}
    }
    /* Init HPD */
    rhdPtr->HPD = xnfcalloc(sizeof(struct rhdHPD), 1);
    RHDHPDSave(rhdPtr);
    RHDHPDSet(rhdPtr);

    for (i = 0, j = 0; i < RHD_CONNECTORS_MAX; i++) {
	if (ConnectorInfo[i].Type == RHD_CONNECTOR_NONE)
	    continue;

	RHDDebug(rhdPtr->scrnIndex, "%s: %d (%s) type %d, ddc %d, hpd %d\n",
		 __func__, i, ConnectorInfo[i].Name, ConnectorInfo[i].Type,
		 ConnectorInfo[i].DDC, ConnectorInfo[i].HPD);

	Connector = xnfcalloc(sizeof(struct rhdConnector), 1);

	Connector->scrnIndex = rhdPtr->scrnIndex;

	Connector->Type = ConnectorInfo[i].Type;
	Connector->Name = rhdConnectorSynthName(&ConnectorInfo[i], &csstate);

	/* Get the DDC bus of this connector */
	if (ConnectorInfo[i].DDC != RHD_DDC_NONE) {
	    RHDI2CDataArg data;
	    int ret;

	    data.i = ConnectorInfo[i].DDC;
	    ret = RHDI2CFunc(rhdPtr->scrnIndex,
			     rhdPtr->I2C, RHD_I2C_GETBUS, &data);
	    if (ret == RHD_I2C_SUCCESS)
		Connector->DDC = data.i2cBusPtr;
	}

	/* attach HPD */
	hpd = ConnectorInfo[i].HPD;
	switch (rhdPtr->hpdUsage) {
	case RHD_HPD_USAGE_OFF:
	case RHD_HPD_USAGE_AUTO_OFF:
	    hpd = RHD_HPD_NONE;
	    break;
	case RHD_HPD_USAGE_SWAP:
	case RHD_HPD_USAGE_AUTO_SWAP:
	    switch (hpd) {
	    case RHD_HPD_0:
		hpd = RHD_HPD_1;
		break;
	    case RHD_HPD_1:
		hpd = RHD_HPD_0;
		break;
	    }
	    break;
	default:
	    break;
	}
	switch(hpd) {
	case RHD_HPD_0:
	    Connector->HPDMask = 0x00000001;
	    Connector->HPDCheck = RHDHPDCheck;
	    break;
	case RHD_HPD_1:
	    Connector->HPDMask = 0x00000100;
	    Connector->HPDCheck = RHDHPDCheck;
	    break;
	case RHD_HPD_2:
	    Connector->HPDMask = 0x00010000;
	    Connector->HPDCheck = RHDHPDCheck;
	    break;
	case RHD_HPD_3:
	    Connector->HPDMask = 0x01000000;
	    Connector->HPDCheck = RHDHPDCheck;
	    break;
	default:
	    Connector->HPDCheck = NULL;
	    break;
	}

	/* create Outputs */
	for (k = 0; k < 2; k++) {
	    if (ConnectorInfo[i].Output[k] == RHD_OUTPUT_NONE)
		continue;

	    /* Check whether the output exists already */
	    for (Output = rhdPtr->Outputs; Output; Output = Output->Next)
		if (Output->Id == ConnectorInfo[i].Output[k])
		    break;

	    if (!Output) {
		if (!RHDUseAtom(rhdPtr, NULL, atomUsageOutput)) {
		    switch (ConnectorInfo[i].Output[k]) {
		    case RHD_OUTPUT_DACA:
			Output = RHDDACAInit(rhdPtr);
			RHDOutputAdd(rhdPtr, Output);
			break;
		    case RHD_OUTPUT_DACB:
			Output = RHDDACBInit(rhdPtr);
			RHDOutputAdd(rhdPtr, Output);
			break;
		    case RHD_OUTPUT_TMDSA:
			Output = RHDTMDSAInit(rhdPtr);
			RHDOutputAdd(rhdPtr, Output);
			break;
		    case RHD_OUTPUT_LVTMA:
			Output = RHDLVTMAInit(rhdPtr, ConnectorInfo[i].Type);
			RHDOutputAdd(rhdPtr, Output);
			break;
		    case RHD_OUTPUT_DVO:
			Output = RHDDDIAInit(rhdPtr);
			if (Output)
			    RHDOutputAdd(rhdPtr, Output);
			break;
		    case RHD_OUTPUT_KLDSKP_LVTMA:
		    case RHD_OUTPUT_UNIPHYA:
		    case RHD_OUTPUT_UNIPHYB:
			Output = RHDDIGInit(rhdPtr, ConnectorInfo[i].Output[k], ConnectorInfo[i].Type);
			RHDOutputAdd(rhdPtr, Output);
			break;
		    default:
			xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR,
				   "%s: unhandled output id: %d. Trying fallback to AtomBIOS\n", __func__,
				   ConnectorInfo[i].Output[k]);
			break;
		    }
		}
#ifdef ATOM_BIOS
		if (!Output) {
		    Output = RHDAtomOutputInit(rhdPtr, ConnectorInfo[i].Type,
					       ConnectorInfo[i].Output[k]);
		    if (Output)
			RHDOutputAdd(rhdPtr, Output);
		}
#endif
	    }

	    if (Output) {
		xf86DrvMsg(rhdPtr->scrnIndex, X_PROBED,
			   "Attaching Output %s to Connector %s\n",
			   Output->Name, Connector->Name);
		for (l = 0; l < 2; l++)
		    if (!Connector->Output[l]) {
			Connector->Output[l] = Output;
			break;
		    }
	    }
	}

	rhdPtr->Connector[j] = Connector;
	j++;
    }
    if (csstate)
	xfree(csstate);

    /* Deallocate what atombios code allocated */
    if (ConnectorInfo && InfoAllocated) {
	for (i = 0; i < RHD_CONNECTORS_MAX; i++)
	    if (ConnectorInfo[i].Type != RHD_CONNECTOR_NONE)
		xfree(ConnectorInfo[i].Name);
	/* Don't free the Privates as they are hooked into the rhdConnector structures !!! */
	xfree(ConnectorInfo);
    }

    RHDHPDRestore(rhdPtr);

    return (j && 1);
}

/*
 *
 */
void
RHDConnectorsDestroy(RHDPtr rhdPtr)
{
    struct rhdConnector *Connector;
    int i;

    RHDFUNC(rhdPtr);

    for (i = 0; i < RHD_CONNECTORS_MAX; i++) {
	Connector = rhdPtr->Connector[i];
	if (Connector) {
	    if (Connector->Monitor)
		RHDMonitorDestroy(Connector->Monitor);
	    xfree(Connector->Name);
	    xfree(Connector);
	}
    }
}

/*
 *
 */
void
RhdPrintConnectorInfo(int scrnIndex, struct rhdConnectorInfo *cp)
{
    int n;
    ScrnInfoPtr pScrn = xf86Screens[scrnIndex];
    RHDPtr rhdPtr = RHDPTR(pScrn);

    const char *c_name[] =
	{ "RHD_CONNECTOR_NONE", "RHD_CONNECTOR_VGA", "RHD_CONNECTOR_DVI",
	  "RHD_CONNECTOR_DVI_SINGLE", "RHD_CONNECTOR_PANEL",
	  "RHD_CONNECTOR_TV", "RHD_CONNECTOR_PCIE" };

    const char *ddc_name[] =
	{ "RHD_DDC_0", "RHD_DDC_1", "RHD_DDC_2", "RHD_DDC_3", "RHD_DDC_4" };

    const char *hpd_name_normal[] =
	{ "RHD_HPD_NONE", "RHD_HPD_0", "RHD_HPD_1", "RHD_HPD_2", "RHD_HPD_3" };
    const char *hpd_name_off[] =
	{ "RHD_HPD_NONE", "RHD_HPD_NONE /*0*/", "RHD_HPD_NONE /*1*/", "RHD_HPD_NONE /*2*/", "RHD_HPD_NONE /*3*/" };
    const char *hpd_name_swapped[] =
	{ "RHD_HPD_NONE", "RHD_HPD_1 /*swapped*/", "RHD_HPD_0 /*swapped*/", "RHD_HPD_2", "RHD_HPD_3" };

    const char *output_name[] =
	{ "RHD_OUTPUT_NONE", "RHD_OUTPUT_DACA", "RHD_OUTPUT_DACB", "RHD_OUTPUT_TMDSA",
	  "RHD_OUTPUT_LVTMA", "RHD_OUTPUT_DVO", "RHD_OUTPUT_KLDSKP_LVTMA",
	  "RHD_OUTPUT_UNIPHYA", "RHD_OUTPUT_UNIPHYB", "RHD_OUTPUT_UNIPHYC", "RHD_OUTPUT_UNIPHYD",
	  "RHD_OUTPUT_UNIPHYE", "RHD_OUTPUT_UNIPHYF" };
    const char **hpd_name;

    switch (rhdPtr->hpdUsage) {
    case RHD_HPD_USAGE_OFF:
    case RHD_HPD_USAGE_AUTO_OFF:
	hpd_name = hpd_name_off;
	break;
    case RHD_HPD_USAGE_SWAP:
    case RHD_HPD_USAGE_AUTO_SWAP:
	hpd_name = hpd_name_swapped;
	break;
    default:
	hpd_name = hpd_name_normal;
	break;
    }

    for (n = 0; n < RHD_CONNECTORS_MAX; n++) {
	if (cp[n].Type == RHD_CONNECTOR_NONE)
	    break;
	xf86DrvMsg(scrnIndex, X_INFO, "Connector[%i] {%s, \"%s\", %s, %s, { %s, %s } }\n",
		   n, c_name[cp[n].Type], cp[n].Name,
		   cp[n].DDC == RHD_DDC_NONE ? "RHD_DDC_NONE" : ddc_name[cp[n].DDC],
		   hpd_name[cp[n].HPD], output_name[cp[n].Output[0]],
		   output_name[cp[n].Output[1]]);
    }
}

/*
 * Should we enable HDMI on this connector?
 */
Bool RHDConnectorEnableHDMI(struct rhdConnector *Connector)
{
    RHDPtr rhdPtr = RHDPTRI(Connector);
    RHDFUNC(rhdPtr);

    /* check if user forced HDMI on this connector */
    switch(RhdParseBooleanOption(&rhdPtr->hdmi, Connector->Name)) {
	case RHD_OPTION_ON:
	case RHD_OPTION_DEFAULT:
	    xf86DrvMsg(rhdPtr->scrnIndex, X_INFO, "Enabling HDMI on %s because of config option\n", Connector->Name);
	    return TRUE;
	case RHD_OPTION_OFF:
	    xf86DrvMsg(rhdPtr->scrnIndex, X_INFO, "Disabling HDMI on %s because of config option\n", Connector->Name);
	    return FALSE;
	case RHD_OPTION_NOT_SET:
	    /* ask connected monitor if it supports HDMI */
	    /* TODO: Not implemented yet! */
	    return FALSE;
    }

    return FALSE;
}
