/* Copyright (c) 2005 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
 * AUTHORS OR COPYRIGHT HOLDERS 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.
 *
 * Neither the name of the Advanced Micro Devices, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 * */

/* 
 * This file contains routines to write to and read from the I2C bus using
 * the GPIO pins of the CS5530.
 * */

/* STATIC VARIABLES TO STORE WHAT GPIO PINS TO USE */

int gpio_clock = 0;
int gpio_data = 0;

static int g_initialized = 0;

#define	I2CWRITE		0x00   /* Write address         */
#define	I2CREAD			0x01   /* Read address          */

#define	I2CACK			0x00   /* Ack value             */
#define	I2CNACK			0x01   /* Not - ack value       */

#define		CS5530_ID	(0x80000000 | (0x00<<16) | (0x12<<11) | (0<<8) | 0x00)
#define		CS5530_GPIO	(0x80000000 | (0x00<<16) | (0x12<<11) | (0<<8) | 0x90)
#define		SDA		0x0800
#define		SCL		0x0400
#define		SDADIR	0x0008
#define		SCLDIR	0x0004

int I2C_init(void);
void I2C_cleanup(void);

int I2C_Read(unsigned char address, unsigned int reg, unsigned long *p_value,
    unsigned int bytes);
int I2C_Write(unsigned char address, unsigned int reg, unsigned long value,
    unsigned int bytes);
int I2CAL_init(void);
void I2CAL_cleanup(void);

void I2CAL_output_clock(int state);
void I2CAL_output_data(int state);
unsigned char I2CAL_input_data(void);

void I2CAL_set_data_for_input(void);
void I2CAL_set_data_for_output(void);

void SendI2CStart(void);
void SendI2CData(unsigned char inData);

unsigned char ReceiveI2CAck(void);
void SendI2CStop(void);
void SendI2CNack(void);
void SendI2CAck(void);
unsigned char ReceiveI2CData(void);

/* ### ADD ### ANY LOCAL ROUTINE DEFINITIONS SPECIFIC TO GPIO */

/*---------------------------------------------------------------------------
 * gfx_i2c_reset
 *
 * This routine resets the I2C bus.
 *---------------------------------------------------------------------------
 */

#if GFX_I2C_DYNAMIC
int
gpio_i2c_reset(unsigned char busnum, short adr, char freq)
#else
int
gfx_i2c_reset(unsigned char busnum, short adr, char freq)
#endif
{
    /* ### ADD ### Any code needed to reset the state of the GPIOs. */
    return GFX_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * gfx_i2c_select_gpio
 *
 * This routine selects which GPIO pins to use.
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
gpio_i2c_select_gpio(int clock, int data)
#else
int
gfx_i2c_select_gpio(int clock, int data)
#endif
{
    gpio_clock = clock;
    gpio_data = data;
    return (0);
}

/*---------------------------------------------------------------------------
 * gfx_i2c_write
 *
 * This routine writes data to the specified I2C address.
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
gpio_i2c_write(unsigned char busnum, unsigned char address, unsigned char reg,
    unsigned char bytes, unsigned char *value)
#else
int
gfx_i2c_write(unsigned char busnum, unsigned char address, unsigned char reg,
    unsigned char bytes, unsigned char *value)
#endif
{
    /* ### ADD ### CODE TO WRITE BYTE TO I2B BUS */

    int restart_count = 0;

    while (restart_count++ < 5) {
        /* set the access pointer register. */
        /* The address is shifted left by one to make room for Read/Write 
         * bit */
        SendI2CStart();
        SendI2CData((char)((address << 1) | I2CWRITE));
        if (!ReceiveI2CAck()) {
            SendI2CStop();
            gfx_delay_milliseconds(10);
            continue;
        }
        SendI2CData((unsigned char)reg);
        if (!ReceiveI2CAck()) {
            SendI2CStop();
            gfx_delay_milliseconds(10);
            continue;
        }

        /* write the first byte */
        SendI2CData(*value);
        if (!ReceiveI2CAck()) {
            SendI2CStop();
            gfx_delay_milliseconds(10);
            continue;
        }

        /* write the second byte. */
        if (bytes == 2) {
            SendI2CData(*(value + 1));
            if (!ReceiveI2CAck()) {
                SendI2CStop();
                gfx_delay_milliseconds(10);
                continue;
            }
        }

        /* done. */
        SendI2CStop();

        return 0;
    }

    return (0);

}

/*---------------------------------------------------------------------------
 * gfx_i2c_read
 *
 * This routine reads data from the specified I2C address.
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
gpio_i2c_read(unsigned char busnum, unsigned char address, unsigned char reg,
    unsigned char bytes, unsigned char *p_value)
#else
int
gfx_i2c_read(unsigned char busnum, unsigned char address, unsigned char reg,
    unsigned char bytes, unsigned char *p_value)
#endif
{
    /* ### ADD ### CODE TO WRITE BYTE TO I2B BUS */
    /* For now return clock and data pins */

    int restart_count = 0;

    if (!p_value)
        return (1);

    while (restart_count++ < 5) {
        /* set the access pointer register. */
        /* The address is shifted left by one to make room for Read/Write 
         * bit  */
        SendI2CStart();
        SendI2CData((char)((address << 1) | I2CWRITE));
        if (!ReceiveI2CAck()) {
            SendI2CStop();
            gfx_delay_milliseconds(10);
            continue;
        }
        SendI2CData((unsigned char)(reg & 0xFF));
        SendI2CNack();

        /* read the first data byte. */
        SendI2CStart();
        SendI2CData((char)((address << 1) | I2CREAD));
        if (!ReceiveI2CAck()) {
            SendI2CStop();
            gfx_delay_milliseconds(10);
            continue;
        }
        *p_value = ReceiveI2CData();

        /* read the second byte. */
        if (bytes == 2) {
            SendI2CAck();
            *(p_value + 1) = ReceiveI2CData();
        }

        /* done. */
        SendI2CNack();
        SendI2CStop();

        return 0;
    }

    return (1);
}

/* Added i2c/gpio code to test fs451 chip. */

/*
 * ----------------------------------------------------------------------
 *
 *	void SendI2CStart(void)
 *	
 *	Sends an I2C start signal on the bus.
 *	
 *----------------------------------------------------------------------
 */
void
SendI2CStart(void)
{
    I2CAL_output_data(1);
    I2CAL_output_clock(1);
    I2CAL_output_data(0);
    I2CAL_output_clock(0);
}

/*
 *----------------------------------------------------------------------
 *
 *	void SendI2CStop(void)
 *	
 *	Sends an I2C stop signal on the bus.
 *	
 *----------------------------------------------------------------------
 */
void
SendI2CStop(void)
{
    I2CAL_output_data(0);
    I2CAL_output_clock(1);
    I2CAL_output_data(1);
}

/*
 *----------------------------------------------------------------------
 *
 *	void SendI2CAck(void)
 *	
 *	Sends the Ack signal on the I2C bus.
 *	
 *----------------------------------------------------------------------
 */
void
SendI2CAck(void)
{
    I2CAL_output_data(0);
    I2CAL_output_clock(1);
    I2CAL_output_clock(0);
}

/*
 *----------------------------------------------------------------------
 *
 *	void SendI2CNack(void)
 *	
 *	Sends the Nt-Ack signal on the I2C bus.
 *	
 *----------------------------------------------------------------------
 */
void
SendI2CNack(void)
{
    I2CAL_output_data(1);
    I2CAL_output_clock(1);
    I2CAL_output_clock(0);
}

/*
 *----------------------------------------------------------------------
 *
 *	UInt8 SendI2CData( UInt8 inData )
 *	
 *	Sends a byte of data on the I2C bus and returns the TRUE if the slave 
 *	ACK'dthe data.
 *	
 *	Input:	inData			- the byte of data to send          
 *	Output:	(return)		- TRUE (1) if ACK was received, FALSE (0) if not
 *	
 *----------------------------------------------------------------------
 */
void
SendI2CData(unsigned char inData)
{
    unsigned char bit;

    /* Send all 8 bits of data byte, MSB to LSB */
    for (bit = 0x80; bit != 0; bit >>= 1) {
        if (inData & bit)
            I2CAL_output_data(1);
        else
            I2CAL_output_data(0);

        I2CAL_output_clock(1);
        I2CAL_output_clock(0);
    }
}

/*
 *----------------------------------------------------------------------
 *
 *	UInt8 ReceiveI2CAck(  )
 *	
 *	Receives the Ack (or Nack) from the slave. 
 *	
 *	Output:	(return) - TRUE (1) if ACK was received, FALSE (0) if not
 *
 *----------------------------------------------------------------------
 */
unsigned char
ReceiveI2CAck(void)
{
    unsigned char bit;

    /* Test for Ack/Nack */
    I2CAL_set_data_for_input();
    I2CAL_output_data(1);
    I2CAL_output_clock(1);
    bit = I2CAL_input_data();
    I2CAL_output_clock(0);
    I2CAL_set_data_for_output();
    return !bit;
}

/*	
 *----------------------------------------------------------------------
 *
 *	unsigned char ReceiveI2CData(void)
 *
 *	Receives a byte of data from the I2C bus.
 *
 *	Output:	(return) - The data byte recehved from the bus
 *
 *----------------------------------------------------------------------
 */
unsigned char
ReceiveI2CData(void)
{
    unsigned char data = 0;
    unsigned char x;

    /* make sure the data line is released */
    I2CAL_set_data_for_input();
    I2CAL_output_data(1);

    /* shift in the data */
    for (x = 0; x < 8; x++) {
        /* shift the data left   */
        I2CAL_output_clock(1);
        data <<= 1;
        data |= I2CAL_input_data();
        I2CAL_output_clock(0);
    }

    I2CAL_set_data_for_output();
    I2CAL_output_data(1);
    return data;
}

/*
 *----------------------------------------------------------------------
 *
 *	void I2C_init(void)
 *
 *	This routine initializes the I2C interface. Clients of the I2C.c
 *	will call this routine before calling any other routine in the I2C.c
 *
 *----------------------------------------------------------------------
 */

#if GFX_I2C_DYNAMIC
int
gpio_i2c_init(void)
#else
int
gfx_i2c_init(void)
#endif
{
    int errc;

    /* init I2CAL */
    errc = I2CAL_init();
    if (errc)
        return errc;

    /* set the clock and data lines to the proper states */
    I2CAL_output_clock(1);
    I2CAL_output_data(1);
    I2CAL_set_data_for_output();

    SendI2CStart();
    SendI2CStop();
    SendI2CStop();

    g_initialized = 1;

    return 0;
}

/*
 *----------------------------------------------------------------------
 *	
 *	void I2C_cleanup(void)
 *
 *	This routine disables the I2C interface. Clients of the I2C.c will not
 *	call any other I2C routine after calling this routine.
 *	
 *----------------------------------------------------------------------
 */

#if GFX_I2C_DYNAMIC
void
gpio_i2c_cleanup(void)
#else
void
gfx_i2c_cleanup(void)
#endif
{
    if (g_initialized) {

        /* set the clock and data lines to a harmless state */
        I2CAL_output_clock(1);
        I2CAL_output_data(1);

        g_initialized = 0;
    }

    I2CAL_cleanup();
}

int
I2CAL_init(void)
{
    unsigned long l_reg;
    unsigned short reg;

    /* initialize the i2c port. */
    l_reg = gfx_pci_config_read(CS5530_GPIO);

    if (l_reg != 0x01001078)
        return 1;

    l_reg = gfx_pci_config_read(CS5530_GPIO);
    reg = (unsigned short)l_reg;

    /* both outputs, both high. */
    reg |= (SDADIR | SCLDIR | SDA | SCL);
    l_reg = reg;
    gfx_pci_config_write(CS5530_GPIO, l_reg);

    g_initialized = 1;

    return 0;
}

void
I2CAL_cleanup(void)
{
    if (g_initialized) {

        g_initialized = 0;
    }
}

/*
 *----------------------------------------------------------------------------
 *
 *	set the I2C clock line state
 *
 *----------------------------------------------------------------------------
 */
void
I2CAL_output_clock(int inState)
{
    unsigned short reg;
    unsigned long value;

    value = gfx_pci_config_read(CS5530_GPIO);
    reg = (unsigned short)value;

    if (inState) {                     /* write a 1. */
        reg |= SCL;
    } else {                           /* write a 0. */
        reg &= ~SCL;
    }

    value = reg;
    gfx_pci_config_write(CS5530_GPIO, value);

    /* hold it for a minimum of 4.7us */
    gfx_delay_microseconds(5);
}

/*
 *----------------------------------------------------------------------------
 *
 *	set the I2C data line state
 *
 *----------------------------------------------------------------------------
 */
void
I2CAL_output_data(int inState)
{
    unsigned short reg;
    unsigned long value;

    value = gfx_pci_config_read(CS5530_GPIO);
    reg = (unsigned short)value;

    if (inState) {                     /* write a 1. */
        reg |= SDA;
    } else {
        /* write a 0. */
        reg &= ~SDA;
    }
    value = reg;
    gfx_pci_config_write(CS5530_GPIO, value);

    /* 250 ns setup time */
    gfx_delay_microseconds(1);
}

/*
 *----------------------------------------------------------------------------
 *
 *	read the state of the data line
 *
 *----------------------------------------------------------------------------
 */
unsigned char
I2CAL_input_data(void)
{
    unsigned short reg;
    unsigned long value;

    value = gfx_pci_config_read(CS5530_GPIO);
    reg = (unsigned short)value;

    if (reg & SDA)
        return 1;
    else
        return 0;
}

/*
 *----------------------------------------------------------------------------
 *
 *	set the I2C data for input mode
 *
 *----------------------------------------------------------------------------
 */
void
I2CAL_set_data_for_input(void)
{
    unsigned short reg;
    unsigned long value;

    value = gfx_pci_config_read(CS5530_GPIO);
    reg = (unsigned short)value;

    reg &= ~SDADIR;

    value = reg;

    gfx_pci_config_write(CS5530_GPIO, value);
}

/*
 *----------------------------------------------------------------------------
 *
 *	set the I2C data for output mode
 *
 *----------------------------------------------------------------------------
 */
void
I2CAL_set_data_for_output(void)
{
    unsigned short reg;
    unsigned long value;

    value = gfx_pci_config_read(CS5530_GPIO);
    reg = (unsigned short)value;
    reg |= SDADIR;
    value = reg;

    gfx_pci_config_write(CS5530_GPIO, value);

}

/* END OF FILE */