root/src/add-ons/accelerants/skeleton/engine/i2c.c
/*
 * i2c interface for the G400 MAVEN under BeOS
 *
 * Provides I2CR,I2CW - functions to parallel DACW,DACR
 * Bus should be run at max. 100kHz: see original Philips I2C specification
 *      
 * Much help was provided by observing the Linux i2c code, 
 * so thanks go to: Gerd Knorr
 *
 * Other authors:
 * Mark Watson 6/2000,
 * Rudolf Cornelissen 12/2002-12/2003
 */

#define MODULE_BIT 0x00004000

#include "std.h"

int i2c_set_lines(int clock, int data);
int i2c_get_data(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_high(void);
void i2c_low(void);
int i2c_get_ack(void);
void i2c_send_ack(void);
int i2c_sendbyte(unsigned char data);
unsigned char i2c_readbyte(int ack_required);

/*which device on the bus is the MAVEN?*/
#define MAVEN_WRITE (0x1B<<1)
#define MAVEN_READ ((0x1B<<1)|1)

#define I2C_CLOCK 0x20
#define I2C_DATA 0x10

/* NV-TVO I2C for G200, G400 */
#define I2C_CLOCK 0x20
#define I2C_DATA 0x10
/* primary head DDC for Mystique(?), G100, G200, G400 */
#define DDC1_CLK        0x08
#define DDC1_DATA       0x02
/* primary head DDC for Millennium, Millennium II */
#define DDC1B_CLK       0x10
#define DDC1B_DATA      0x04
/* secondary head DDC for G400, G450 and G550 */
#define DDC2_CLK        0x04
#define DDC2_DATA       0x01

status_t i2c_sec_tv_adapter()
{
        status_t result = B_ERROR;

        /* The secondary DDC channel only exist on dualhead cards */
        if (!si->ps.secondary_head) return result;

        /* make sure the output lines will be active-low when enabled
         * (they will be pulled 'passive-high' when disabled) */
//      DXIW(GENIODATA,0x00);
        /* send out B_STOP condition on secondary head DDC channel and use it to
         * check for 'shortcut', indicating the Matrox VGA->TV adapter is connected */

        /* make sure SDA is low */
//      DXIW(GENIOCTRL, (DXIR(GENIOCTRL) | DDC2_DATA));
        snooze(2);
        /* make sure SCL should be high */
//      DXIW(GENIOCTRL, (DXIR(GENIOCTRL) & ~DDC2_CLK));
        snooze(2);
        /* if SCL is low then the bus is blocked by a TV adapter */
//      if (!(DXIR(GENIODATA) & DDC2_CLK)) result = B_OK;
        snooze(5);
        /* set SDA while SCL should be set (generates actual bus-stop condition) */
//      DXIW(GENIOCTRL, (DXIR(GENIOCTRL) & ~DDC2_DATA));
        snooze(5);

        return result;
}

/*-----------------------------
 *low level hardware access
 */
#define I2C_DELAY 2
#define I2C_TIMEOUT 100
int i2c_set_lines(int clock,int data)
{
        int count=0;
        int program;
        int required;

        /*work out which bits to zero*/
        program = 
                (clock ? 0 : I2C_CLOCK)|
                (data ? 0 : I2C_DATA);

        /*what value do I require on data lines*/
        required = 
                (clock ? I2C_CLOCK : 0);

        /*set the bits to zero*/
//      DXIW(GENIOCTRL,program); /*drive these bits*/
//      DXIW(GENIODATA,0x00);    /*to zero*/

        /*wait a bit*/
        delay(I2C_DELAY);

        /*loop until the clock is as required*/
//      while ((DXIR(GENIODATA)&I2C_CLOCK)!=required)
        {
                delay(I2C_DELAY);
                count++;
                if (count>I2C_TIMEOUT)
                {
//                      LOG(8,("I2C: Timeout on set lines - clock:%d data:%d actual:%x\n",clock,data,DXIR(GENIODATA)));
                        return -1;
                }
        }

        return 0;
}

int i2c_get_data()
{
        int data = 0;
        int clock;
        int count=0;

        do
        {
                /*read the data and clock lines*/
//              data = DXIR(GENIODATA);
                clock = (data&I2C_CLOCK) ? 1 : 0;
                data = (data&I2C_DATA) ? 1 : 0;
        
                /*manage timeout*/
                count++;
                if (count>I2C_TIMEOUT)
                {
                        return -1;
                }

                /*wait a bit, so not hammering bus*/
                delay(I2C_DELAY);

        }while (!clock); /*wait for high clock*/

        return data;
}


/*----------------------- 
 *Standard I2C operations
 */
void i2c_start()
{
        int error=0;

        error+= i2c_set_lines(0,1);
        error+= i2c_set_lines(1,1);
        error+= i2c_set_lines(1,0);
        error+= i2c_set_lines(0,0);

        if (error)
        {
                LOG(8,("I2C: start - %d\n",error));
        }
}

void i2c_stop()
{
        int error=0;

        error+= i2c_set_lines(0,0);
        error+= i2c_set_lines(1,0);
        error+= i2c_set_lines(1,1);
        error+= i2c_set_lines(0,1);

        if (error)
        {
                LOG(8,("I2C: stop - %d\n",error));
        }
}

void i2c_high()
{
        int error=0;

        error+= i2c_set_lines(0,1);
        error+= i2c_set_lines(1,1);
        error+= i2c_set_lines(0,1);

        if (error)
        {
                LOG(8,("I2C: high - %d\n",error));
        }
}

void i2c_low()
{
        int error=0;

        error+= i2c_set_lines(0,0);
        error+= i2c_set_lines(1,0);
        error+= i2c_set_lines(0,0);

        if (error)
        {
                LOG(8,("I2C: low - %d\n",error));
        }
}

int i2c_get_ack()
{
        int error=0;
        int ack;
    
        error+= i2c_set_lines(0,1);
        error+= i2c_set_lines(1,1);
        ack = i2c_get_data();
        error+= i2c_set_lines(0,1);

        if (error)
        {
                LOG(8,("I2C: get_ack - %d value:%x\n",error,ack));
        }

        return ack;
}

void i2c_send_ack()
{
        int error=0;
    
        error+= i2c_set_lines(0,0);
        error+= i2c_set_lines(1,0);
        error+= i2c_set_lines(0,0);

        if (error)
        {
                LOG(8,("I2C: send_ack - %d\n",error));
        }
}

/*------------------------------
 *use above functions to send and receive bytes
 */

int i2c_sendbyte(unsigned char data)
{
        int i;

        for (i=7; i>=0; i--)
        {
                if (data&(1<<i)) 
                {
                        i2c_high();
                }
                else
                {
                        i2c_low();
                }
        }

        return i2c_get_ack();
}

unsigned char i2c_readbyte(int ack_required)
{
        int i;
        unsigned char data=0;
    
        /*read data*/
        i2c_set_lines(0,1);
        for (i=7; i>=0; i--) 
        {
                i2c_set_lines(1,1);
                if (i2c_get_data()==1)
                        data |= (1<<i);
                i2c_set_lines(0,1);
        }

        /*send acknowledge*/
        if (ack_required) i2c_send_ack();

        return data;
}

/*-------------------------------------------
 *PUBLIC functions
 */ 
int i2c_maven_read(unsigned char address)
{
        int error=0;
        int data;

        i2c_start();
        {
                error+=i2c_sendbyte(MAVEN_READ);
                error+=i2c_sendbyte(address);
                data = i2c_readbyte(0);
        }       
        i2c_stop();
        if (error>0) LOG(8,("I2C: MAVR ERROR - %x\n",error));
        return data;
}

void i2c_maven_write(unsigned char address, unsigned char data)
{
        int error=0;

        i2c_start();
        {
                error+=i2c_sendbyte(MAVEN_WRITE);
                error+=i2c_sendbyte(address);
                error+=i2c_sendbyte(data);
        }       
        i2c_stop();
        if (error>0) LOG(8,("I2C: MAVW ERROR - %x\n",error));
}

status_t i2c_init(void)
{
        /*init g400 i2c*/
//      DXIW(GENIODATA,0x00); /*to zero*/
//      DXIW(GENIOCTRL,0x30); /*drive clock and data*/
//      DXIW(GENIOCTRL,0x00); /*stop driving*/

        return B_OK;
}

status_t i2c_maven_probe(void)
{
        int ack;

        /*scan the bus for the MAVEN*/
        i2c_start();
        {
                ack = i2c_sendbyte(MAVEN_READ);
        }
        i2c_stop();
        if (ack==0) 
        {
                return B_OK;
        }
        else
        {
                return B_ERROR;
        }
}