/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/via/via_gpioi2c.c,v 1.2tsi Exp $ */
/*
 * Copyright 1998-2003 VIA Technologies, Inc. All Rights Reserved.
 * Copyright 2001-2003 S3 Graphics, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * VIA, S3 GRAPHICS, AND/OR ITS 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.
 */

#include "via_driver.h"
#include "via_gpioi2c.h"

static void delayIn_usec(int usec);
static void HWGPIOI2C_SetSCL(VIABIOSInfoPtr pBIOSInfo, CARD8 flag);
static void HWGPIOI2C_SetSDA(VIABIOSInfoPtr pBIOSInfo, CARD8 flag);
static CARD8  HWGPIOI2C_GetSDA(VIABIOSInfoPtr pBIOSInfo);
static void GPIOI2C_START(VIABIOSInfoPtr pBIOSInfo);
static void GPIOI2C_STOP(VIABIOSInfoPtr pBIOSInfo);
static Bool GPIOI2C_ACKNOWLEDGE(VIABIOSInfoPtr pBIOSInfo);
static Bool GPIOI2C_SENDACKNOWLEDGE(VIABIOSInfoPtr pBIOSInfo);
static Bool GPIOI2C_SENDNACKNOWLEDGE(VIABIOSInfoPtr pBIOSInfo);
static Bool GPIOI2C_ReadBit(VIABIOSInfoPtr pBIOSInfo, int *psda, int timeout);
static CARD8 GPIOI2C_ReadData(VIABIOSInfoPtr pBIOSInfo);
static Bool GPIOI2C_WriteBit(VIABIOSInfoPtr pBIOSInfo, int sda, int timeout);
static Bool GPIOI2C_WriteData(VIABIOSInfoPtr pBIOSInfo, CARD8 Data);
static void I2C_RW_Control(VIABIOSInfoPtr pBIOSInfo, CARD8 Command, CARD8 Data);

/* I2C Functions */

Bool VIAGPIOI2C_Initial(VIABIOSInfoPtr pBIOSInfo, CARD8 SlaveDevice)
{
    DEBUG(xf86DrvMsg(pBIOSInfo->scrnIndex, X_INFO, "GPIOI2C_Initial\n"));
    switch (pBIOSInfo->Chipset)
    {
        case VIA_KM400:
        case VIA_K8M800:
            GPIOPORT = 0x2C;
            break;
        default:
            GPIOPORT = 0;
            DEBUG(xf86DrvMsg(pBIOSInfo->scrnIndex, X_INFO,
                              "GPIOI2C initial failure!\n"));
            return FALSE;
    }
/*
    switch (pBIOSInfo->TVEncoder)
    {
        case VIA_VT1623:
            break;
        default:
            SLAVEADDR = 0;
            return FALSE;
    }
*/
    if (SlaveDevice == 0xA0 || SlaveDevice == 0xA2) {
        I2C_WAIT_TIME = 40;
        STARTTIMEOUT = 550;
        BYTETIMEOUT = 2200;
        HOLDTIME = 5;
        BITTIMEOUT = 5;
    }
    else {
        I2C_WAIT_TIME = 5;
        STARTTIMEOUT = 5;
        BYTETIMEOUT = 5;
        HOLDTIME = 5;
        BITTIMEOUT = 5;
    }

    SLAVEADDR = SlaveDevice;

    return TRUE;
}

/* I2C R/W Control */
static void I2C_RW_Control(VIABIOSInfoPtr pBIOSInfo, CARD8 Command, CARD8 Data)
{
    VIABIOSInfoPtr  pVia = pBIOSInfo;
    CARD8   value;

    switch(Command) {
        case I2C_RELEASE:
            VGAOUT8(0x3c4, GPIOPORT);
            value = VGAIN8(0x3c5) & (CARD8)(~GPIOI2C_MASKD);
            VGAOUT8(0x3c5, value);
            break;
        case I2C_WRITE_SCL:
            VGAOUT8(0x3c4, GPIOPORT);
            value = VGAIN8(0x3c5) & (CARD8)(~GPIOI2C_SCL_MASK);
            value |= GPIOI2C_SCL_WRITE;
            if (Data) {
                VGAOUT8(0x3c5, (value | I2C_OUTPUT_CLOCK));
            }
            else {
                VGAOUT8(0x3c5, (value & (CARD8)(~I2C_OUTPUT_CLOCK)));
            }
            break;
        case I2C_READ_SCL:
            VGAOUT8(0x3c4, GPIOPORT);
            value = VGAIN8(0x3c5) & (CARD8)(~GPIOI2C_SCL_MASK);
            VGAOUT8(0x3c5, (value | GPIOI2C_SCL_READ));
            break;
        case I2C_WRITE_SDA:
            VGAOUT8(0x3c4, GPIOPORT);
            value = VGAIN8(0x3c5) & (CARD8)(~GPIOI2C_SDA_MASK);
            value |= GPIOI2C_SDA_WRITE;
            if (Data) {
                VGAOUT8(0x3c5, (value | I2C_OUTPUT_DATA));
            }
            else {
                VGAOUT8(0x3c5, (value & (CARD8)(~I2C_OUTPUT_DATA)));
            }
            break;
        case I2C_READ_SDA:
            VGAOUT8(0x3c4, GPIOPORT);
            value = VGAIN8(0x3c5) & (CARD8)(~GPIOI2C_SDA_MASK);
            VGAOUT8(0x3c5, (value | GPIOI2C_SCL_READ));
            break;
        default:
            break;
    }
}

static void delayIn_usec(int usec)
{
  long b_secs, b_usecs;
  long a_secs, a_usecs;
  long d_secs, d_usecs;
  long diff;

  if (usec > 0) {
    xf86getsecs(&b_secs, &b_usecs);
    do {
      /* It would be nice to use {xf86}usleep,
       * but usleep (1) takes >10000 usec !
       */
      xf86getsecs(&a_secs, &a_usecs);
      d_secs  = (a_secs - b_secs);
      d_usecs = (a_usecs - b_usecs);
      diff = d_secs*1000000 + d_usecs;
    } while (diff>0 && diff< (usec + 1));
  }
}

/* Set SCL */
static void HWGPIOI2C_SetSCL(VIABIOSInfoPtr pBIOSInfo, CARD8 flag)
{
    I2C_RW_Control(pBIOSInfo, I2C_WRITE_SCL, flag);
    if (flag) delayIn_usec(I2C_WAIT_TIME);
    delayIn_usec(I2C_WAIT_TIME);
}

/* Set SDA */
static void HWGPIOI2C_SetSDA(VIABIOSInfoPtr pBIOSInfo, CARD8 flag)
{
    I2C_RW_Control(pBIOSInfo, I2C_WRITE_SDA, flag);
    delayIn_usec(I2C_WAIT_TIME);
}

/* Get SDA */
static CARD8  HWGPIOI2C_GetSDA(VIABIOSInfoPtr pBIOSInfo)
{
    VIABIOSInfoPtr  pVia = pBIOSInfo;
    CARD8   value;

    VGAOUT8(0x3c4, GPIOPORT);
    value = VGAIN8(0x3c5);
    if (value & I2C_INPUT_DATA)
        return 1;
    else
        return 0;
}

/* START Condition */
static void GPIOI2C_START(VIABIOSInfoPtr pBIOSInfo)
{
    HWGPIOI2C_SetSDA(pBIOSInfo, 1);
    HWGPIOI2C_SetSCL(pBIOSInfo, 1);
    delayIn_usec(STARTTIMEOUT);
    HWGPIOI2C_SetSDA(pBIOSInfo, 0);
    HWGPIOI2C_SetSCL(pBIOSInfo, 0);
}

/* STOP Condition */
static void GPIOI2C_STOP(VIABIOSInfoPtr pBIOSInfo)
{
    HWGPIOI2C_SetSDA(pBIOSInfo, 0);
    HWGPIOI2C_SetSCL(pBIOSInfo, 1);
    HWGPIOI2C_SetSDA(pBIOSInfo, 1);
    I2C_RW_Control(pBIOSInfo, I2C_RELEASE, 0);
    /* to make the differentiation of next START condition */
    delayIn_usec(I2C_WAIT_TIME);
}

/* Check ACK */
static Bool GPIOI2C_ACKNOWLEDGE(VIABIOSInfoPtr pBIOSInfo)
{
    CARD8   Status;

    I2C_RW_Control(pBIOSInfo, I2C_READ_SDA, 0);
    delayIn_usec(I2C_WAIT_TIME);
    HWGPIOI2C_SetSCL(pBIOSInfo, 1);
    Status = HWGPIOI2C_GetSDA(pBIOSInfo);
    I2C_RW_Control(pBIOSInfo, I2C_WRITE_SDA, Status);
    HWGPIOI2C_SetSCL(pBIOSInfo, 0);
    delayIn_usec(I2C_WAIT_TIME);

    if(Status) return FALSE;
    else return TRUE;
}

/* Send ACK */
static Bool GPIOI2C_SENDACKNOWLEDGE(VIABIOSInfoPtr pBIOSInfo)
{
    HWGPIOI2C_SetSDA(pBIOSInfo, 0);
    HWGPIOI2C_SetSCL(pBIOSInfo, 1);
    HWGPIOI2C_SetSCL(pBIOSInfo, 0);
    delayIn_usec(I2C_WAIT_TIME);

    return TRUE;
}

/* Send NACK */
static Bool GPIOI2C_SENDNACKNOWLEDGE(VIABIOSInfoPtr pBIOSInfo)
{
    HWGPIOI2C_SetSDA(pBIOSInfo, 1);
    HWGPIOI2C_SetSCL(pBIOSInfo, 1);
    HWGPIOI2C_SetSCL(pBIOSInfo, 0);
    delayIn_usec(I2C_WAIT_TIME);

    return TRUE;
}

/* Write Data(Bit by Bit) to I2C */
static Bool GPIOI2C_WriteData(VIABIOSInfoPtr pBIOSInfo, CARD8 Data)
{
    int     i;

    if (!GPIOI2C_WriteBit(pBIOSInfo, (Data >> 7) & 1, BYTETIMEOUT))
	    return FALSE;

    for (i = 6; i >= 0; i--)
    if (!GPIOI2C_WriteBit(pBIOSInfo, (Data >> i) & 1, BITTIMEOUT))
	    return FALSE;

    return GPIOI2C_ACKNOWLEDGE(pBIOSInfo);
}

static Bool GPIOI2C_WriteBit(VIABIOSInfoPtr pBIOSInfo, int sda, int timeout)
{
    Bool ret = TRUE;

    HWGPIOI2C_SetSDA(pBIOSInfo, sda);
    delayIn_usec(I2C_WAIT_TIME/5);
    HWGPIOI2C_SetSCL(pBIOSInfo, 1);
    delayIn_usec(HOLDTIME);
    delayIn_usec(timeout);
    HWGPIOI2C_SetSCL(pBIOSInfo, 0);
    delayIn_usec(I2C_WAIT_TIME/5);

    return ret;
}

static Bool GPIOI2C_ReadBit(VIABIOSInfoPtr pBIOSInfo, int *psda, int timeout)
{
    Bool ret = TRUE;

    I2C_RW_Control(pBIOSInfo, I2C_READ_SDA, 0);
    delayIn_usec(I2C_WAIT_TIME/5);
    HWGPIOI2C_SetSCL(pBIOSInfo, 1);
    delayIn_usec(HOLDTIME);
    delayIn_usec(timeout);
    *psda = HWGPIOI2C_GetSDA(pBIOSInfo);
    HWGPIOI2C_SetSCL(pBIOSInfo, 0);
    delayIn_usec(I2C_WAIT_TIME/5);

    return ret;
}

/* Read Data(Bit by Bit) from I2C */
static CARD8 GPIOI2C_ReadData(VIABIOSInfoPtr pBIOSInfo)
{
    int     i, sda;
    CARD8   data;

    if(!GPIOI2C_ReadBit(pBIOSInfo, &sda, BYTETIMEOUT))
	return 0;

    data = (sda > 0) << 7;
    for (i = 6; i >= 0; i--)
	if (!GPIOI2C_ReadBit(pBIOSInfo, &sda, BITTIMEOUT))
	    return 0;
	else
	    data |= (sda > 0) << i;

    return  data;
}

/* Write Data(one Byte) to Desired Device on I2C */
Bool VIAGPIOI2C_Write(VIABIOSInfoPtr pBIOSInfo, int SubAddress, CARD8 Data)
{
    int     Retry;
    Bool    Done = FALSE;

    for(Retry = 1; Retry <= WRITE_MAX_RETRIES; Retry++)
    {
        GPIOI2C_START(pBIOSInfo);

        if(!GPIOI2C_WriteData(pBIOSInfo, SLAVEADDR)) {

            GPIOI2C_STOP(pBIOSInfo);
            continue;
        }

        if(SubAddress & 0xF00) {            /* write 12-bit sub address */
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)((SubAddress/0x100)&0x0F))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)(SubAddress%0x100))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
        } else {                            /* write 8-bit sub address */
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)(SubAddress))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
        }

        if(!GPIOI2C_WriteData(pBIOSInfo, Data)) {

            GPIOI2C_STOP(pBIOSInfo);
            continue;
        }
        Done = TRUE;
        break;
    }

    GPIOI2C_STOP(pBIOSInfo);

    return Done;
}

/* Read Data from Desired Device on I2C */
Bool VIAGPIOI2C_Read(VIABIOSInfoPtr pBIOSInfo, int SubAddress, CARD8 *Buffer, int BufferLen)
{
    int     i, Retry;

    for(Retry = 1; Retry <= READ_MAX_RETRIES; Retry++)
    {
        GPIOI2C_START(pBIOSInfo);
        if(!GPIOI2C_WriteData(pBIOSInfo, SLAVEADDR&0xFE)) {

            GPIOI2C_STOP(pBIOSInfo);
            continue;
        }

        if(SubAddress & 0xF00) {        /* write 12-bit sub address */
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)((SubAddress/0x100)&0x0F))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)(SubAddress%0x100))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
        } else {                        /* write 8-bit sub address */
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)(SubAddress))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
        }

        break;
    }

    if (Retry > READ_MAX_RETRIES) return FALSE;

    for(Retry = 1; Retry <= READ_MAX_RETRIES; Retry++)
    {
        GPIOI2C_START(pBIOSInfo);
        if(!GPIOI2C_WriteData(pBIOSInfo, SLAVEADDR | 0x01)) {

            GPIOI2C_STOP(pBIOSInfo);
            continue;
        }
        for(i = 0; i < BufferLen; i++) {

            *Buffer = GPIOI2C_ReadData(pBIOSInfo);
            Buffer ++;
            if(BufferLen == 1)
                /*GPIOI2C_SENDACKNOWLEDGE(pBIOSInfo);*/ /* send ACK for normal operation */
                GPIOI2C_SENDNACKNOWLEDGE(pBIOSInfo);    /* send NACK for VT3191/VT3192 only */
            else if(i < BufferLen - 1)
                GPIOI2C_SENDACKNOWLEDGE(pBIOSInfo);     /* send ACK */
            else
                GPIOI2C_SENDNACKNOWLEDGE(pBIOSInfo);    /* send NACK */
        }
        GPIOI2C_STOP(pBIOSInfo);
        break;
    }

    if (Retry > READ_MAX_RETRIES)
        return FALSE;
    else
        return TRUE;
}

/* Read Data(one Byte) from Desired Device on I2C */
Bool VIAGPIOI2C_ReadByte(VIABIOSInfoPtr pBIOSInfo, int SubAddress, CARD8 *Buffer)
{
    int     Retry;

    for(Retry = 1; Retry <= READ_MAX_RETRIES; Retry++)
    {
        GPIOI2C_START(pBIOSInfo);
        if(!GPIOI2C_WriteData(pBIOSInfo, SLAVEADDR & 0xFE)) {

            GPIOI2C_STOP(pBIOSInfo);
            continue;
        }

        if(SubAddress & 0xF00) {        /* write 12-bit sub address */
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)((SubAddress/0x100)&0x0F))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)(SubAddress%0x100))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
        } else {                        /* write 8-bit sub address */
            if(!GPIOI2C_WriteData(pBIOSInfo, (CARD8)(SubAddress))) {

                GPIOI2C_STOP(pBIOSInfo);
                continue;
            }
        }

        break;
    }

    if (Retry > READ_MAX_RETRIES) return FALSE;

    for(Retry = 1; Retry <= READ_MAX_RETRIES; Retry++) {

        GPIOI2C_START(pBIOSInfo);

        if(!GPIOI2C_WriteData(pBIOSInfo, SLAVEADDR | 0x01)) {

            GPIOI2C_STOP(pBIOSInfo);
            continue;
        }

        *Buffer = GPIOI2C_ReadData(pBIOSInfo);

        GPIOI2C_STOP(pBIOSInfo);
        break;
    }

    if (Retry > READ_MAX_RETRIES)
        return FALSE;
    else
        return TRUE;
}
