/*
 * Copyright 2003 through 2004 by Marc Aurele La France (TSI @ UQV), tsi@xfree86.org
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of Marc Aurele La France not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  Marc Aurele La France makes no representations
 * about the suitability of this software for any purpose.  It is provided
 * "as-is" without express or implied warranty.
 *
 * MARC AURELE LA FRANCE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO
 * EVENT SHALL MARC AURELE LA FRANCE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

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

#include "atii2c.h"
#include "atimach64i2c.h"
#include "atistruct.h"

#include "xf86.h"

/* This is derived from GATOS code, with a liberal sprinkling of bug fixes */

/*
 * Some local macros for use by the mid-level I2C functions.
 */

#define ATII2CDelay                                            \
    (*pI2CBus->I2CUDelay)(pI2CBus, pI2CBus->HoldTime)


#define ATII2CSCLDirOff                                        \
    if (pATII2C->SCLDir != 0)                                  \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur & ~pATII2C->SCLDir)

#define ATII2CSCLDirOn                                         \
    if (pATII2C->SCLDir != 0)                                  \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur | pATII2C->SCLDir)

#define ATII2CSDADirOff                                        \
    if (pATII2C->SDADir != 0)                                  \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur & ~pATII2C->SDADir)

#define ATII2CSDADirOn                                         \
    if (pATII2C->SDADir != 0)                                  \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur | pATII2C->SDADir)


#define ATII2CSCLBitGet                                        \
    ((*pATII2C->I2CGetBits)(pATI) & pATII2C->SCLGet)

#define ATII2CSCLBitOff                                        \
    do                                                         \
    {                                                          \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur & ~pATII2C->SCLSet);               \
        ATII2CDelay;                                           \
    } while (0)

#define ATII2CSCLBitOn                                         \
    do                                                         \
    {                                                          \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur | pATII2C->SCLSet);                \
        do      /* Wait until all devices have released SCL */ \
        {                                                      \
            ATII2CDelay;                                       \
        } while (ATII2CSCLBitGet == 0);                        \
    } while (0)


#define ATII2CSDABitGet                                        \
    ((*pATII2C->I2CGetBits)(pATI) & pATII2C->SDAGet)

#define ATII2CSDABitOff                                        \
    do                                                         \
    {                                                          \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur & ~pATII2C->SDASet);               \
        ATII2CDelay;                                           \
    } while (0)

#define ATII2CSDABitOn                                         \
    do                                                         \
    {                                                          \
        (*pATII2C->I2CSetBits)(pATII2C, pATI,                  \
            pATII2C->I2CCur | pATII2C->SDASet);                \
        ATII2CDelay;                                           \
    } while (0)

#define ATII2CSDABitSet(_flag)                                 \
    do                                                         \
    {                                                          \
        if (_flag)                                             \
            ATII2CSDABitOn;                                    \
        else                                                   \
            ATII2CSDABitOff;                                   \
    } while (0)


/*
 * ATII2CStart --
 *
 * This function puts a start signal on the I2C bus.
 */
static Bool
ATII2CStart
(
    I2CBusPtr pI2CBus,
    int       timeout
)
{
    ATII2CPtr pATII2C = pI2CBus->DriverPrivate.ptr;
    ATIPtr    pATI    = pATII2C->pATI;

    (void)timeout;

    /*
     * Set I2C line directions to out-bound.  SCL will remain out-bound until
     * next I2C Stop.
     */
    ATII2CSCLDirOn;
    ATII2CSDADirOn;

    /*
     * Send Start bit.  This is a pull-down of the data line while the clock
     * line is pulled up.
     */
    ATII2CSDABitOn;
    ATII2CSCLBitOn;
    ATII2CSDABitOff;
    ATII2CSCLBitOff;

    return TRUE;
}

/*
 * ATII2CAddress --
 *
 * This function puts an 8-bit address on the I2C bus.
 */
static Bool
ATII2CAddress
(
    I2CDevPtr    pI2CDev,
    I2CSlaveAddr Address
)
{
    I2CBusPtr pI2CBus = pI2CDev->pI2CBus;

    /* Send low byte of device address */
    if ((*pI2CBus->I2CPutByte)(pI2CDev, (I2CByte)Address))
    {
        /* Send top byte of address, if appropriate */
        if (((Address & 0x00F8U) != 0x00F0U) &&
            ((Address & 0x00FEU) != 0x0000U))
            return TRUE;

        if ((*pI2CBus->I2CPutByte)(pI2CDev, (I2CByte)(Address >> 8)))
            return TRUE;
    }

    /* Kill I2C transaction on failure */
    (*pI2CBus->I2CStop)(pI2CDev);
    return FALSE;
}

/*
 * ATII2CStop --
 *
 * This function puts a stop signal on the I2C bus.
 */
static void
ATII2CStop
(
    I2CDevPtr pI2CDev
)
{
    I2CBusPtr pI2CBus = pI2CDev->pI2CBus;
    ATII2CPtr pATII2C = pI2CBus->DriverPrivate.ptr;
    ATIPtr    pATI    = pATII2C->pATI;

    ATII2CSDADirOn;             /* Set data line direction to out-bound */

    /*
     * Send Stop bit.  This is a pull-up of the data line while the clock line
     * is pulled up.
     */
    ATII2CSDABitOff;
    ATII2CSCLBitOn;
    ATII2CSDABitOn;
    ATII2CSCLBitOff;

    /* Reset I2C line directions to in-bound */
    ATII2CSCLDirOff;
    ATII2CSDADirOff;
}

/*
 * ATII2CPutByte --
 *
 * This function puts an 8-bit value on the I2C bus, starting with its MSB.
 */
static Bool
ATII2CPutByte
(
    I2CDevPtr pI2CDev,
    I2CByte   Data
)
{
    I2CBusPtr pI2CBus = pI2CDev->pI2CBus;
    ATII2CPtr pATII2C = pI2CBus->DriverPrivate.ptr;
    ATIPtr    pATI    = pATII2C->pATI;
    int       i;
    Bool      Result;

    ATII2CSDADirOn;             /* Set data line direction to out-bound */

    /* Send data byte */
    for (i = 0;  i < 8;  i++)
    {
        ATII2CSDABitSet(Data & 0x80U);
        ATII2CSCLBitOn;
        ATII2CSCLBitOff;

        Data <<= 1;
    }

    ATII2CSDABitOn;             /* Release data line */

    ATII2CSDADirOff;            /* Set data line direction to in-bound */

    ATII2CSCLBitOn;             /* Start bit-read clock pulse */

    /* Get [N]ACK bit */
    if (ATII2CSDABitGet)
        Result = FALSE;
    else
        Result = TRUE;

    ATII2CSCLBitOff;            /* End clock pulse */

    return Result;
}

/*
 * ATII2CGetByte --
 *
 * This function retrieves an 8-bit value from the I2C bus.
 */
static Bool
ATII2CGetByte
(
    I2CDevPtr pI2CDev,
    I2CByte   *pData,
    Bool      Last
)
{
    I2CBusPtr     pI2CBus = pI2CDev->pI2CBus;
    ATII2CPtr     pATII2C = pI2CBus->DriverPrivate.ptr;
    ATIPtr        pATI    = pATII2C->pATI;
    unsigned long Value   = 1;

    do
    {
        ATII2CSCLBitOn;         /* Start bit-read clock pulse */

        /* Accumulate bit into byte value */
        Value <<= 1;
        if (ATII2CSDABitGet)
            Value++;

        ATII2CSCLBitOff;        /* End clock pulse */
    } while (Value <= (unsigned long)((I2CByte)(-1)));

    *pData = (I2CByte)Value;

    ATII2CSDADirOn;             /* Set data line direction to out-bound */

    /* Send [N]ACK bit */
    ATII2CSDABitSet(Last);
    ATII2CSCLBitOn;
    ATII2CSCLBitOff;

    if (!Last)
        ATII2CSDABitOn;         /* Release data line */

    ATII2CSDADirOff;            /* Set data line direction to in-bound */

    return TRUE;
}

/*
 * ATICreateI2CBusRec --
 *
 * This function is called to initialise an I2CBusRec.
 */
I2CBusPtr
ATICreateI2CBusRec
(
    int    iScreen,
    ATIPtr pATI,
    char   *BusName
)
{
    I2CBusPtr pI2CBus;
    ATII2CPtr pATII2C = xnfcalloc(1, SizeOf(ATII2CRec));

    if (!(pI2CBus = xf86CreateI2CBusRec()))
    {
        xf86DrvMsg(iScreen, X_WARNING, "Unable to allocate I2C Bus record.\n");
        free(pATII2C);
        return NULL;
    }

    /* Fill in generic structure fields */
    pI2CBus->BusName           = BusName;
    pI2CBus->scrnIndex         = iScreen;

    pI2CBus->I2CAddress        = ATII2CAddress;
    pI2CBus->I2CStart          = ATII2CStart;
    pI2CBus->I2CStop           = ATII2CStop;
    pI2CBus->I2CPutByte        = ATII2CPutByte;
    pI2CBus->I2CGetByte        = ATII2CGetByte;

    pI2CBus->DriverPrivate.ptr = pATII2C;

    pATII2C->pATI              = pATI;

    if (xf86I2CBusInit(pI2CBus))
        return pI2CBus;

    xf86DrvMsg(iScreen, X_WARNING,
        "I2C bus %s initialisation failure.\n", BusName);
    xf86DestroyI2CBusRec(pI2CBus, TRUE, TRUE);
    free(pATII2C);
    return NULL;
}

/*
 * ATII2CPreInit --
 *
 * This is called by ATIPreInit() to create I2C bus record(s) for the adapter.
 */
void
ATII2CPreInit
(
    ScrnInfoPtr pScreenInfo,
    ATIPtr      pATI
)
{
            if (!xf86LoadSubModule(pScreenInfo, "i2c"))
                return;

            ATIMach64I2CPreInit(pScreenInfo, pATI);
}

/*
 * ATII2CFreeScreen --
 *
 * This is called by ATIFreeScreen() to remove the driver's I2C interface.
 */
void
ATII2CFreeScreen
(
    int iScreen
)
{
    I2CBusPtr pI2CBus, *ppI2CBus;
    ATII2CPtr pATII2C;
    int nI2CBus;

    nI2CBus = xf86I2CGetScreenBuses(iScreen, &ppI2CBus);
    while (--nI2CBus >= 0)
    {
        pI2CBus = ppI2CBus[nI2CBus];
        pATII2C = pI2CBus->DriverPrivate.ptr;

        xf86DestroyI2CBusRec(pI2CBus, TRUE, TRUE);
        free(pATII2C);
    }

    free(ppI2CBus);
}
