/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nsc/gfx/i2c_acc.c,v 1.1 2002/12/10 15:12:26 alanh Exp $ */
/*
 * $Workfile: i2c_acc.c $
 *
 * This file contains routines to write to and read from the I2C bus using
 * the ACCESS.bus hardware in the SC1200. 
 *
 * NSC_LIC_ALTERNATIVE_PREAMBLE
 *
 * Revision 1.0
 *
 * National Semiconductor Alternative GPL-BSD License
 *
 * National Semiconductor Corporation licenses this software 
 * ("Software"):
 *
 *      Durango
 *
 * under one of the two following licenses, depending on how the 
 * Software is received by the Licensee.
 * 
 * If this Software is received as part of the Linux Framebuffer or
 * other GPL licensed software, then the GPL license designated 
 * NSC_LIC_GPL applies to this Software; in all other circumstances 
 * then the BSD-style license designated NSC_LIC_BSD shall apply.
 *
 * END_NSC_LIC_ALTERNATIVE_PREAMBLE */

/* NSC_LIC_BSD
 *
 * National Semiconductor Corporation Open Source License for Durango
 *
 * (BSD License with Export Notice)
 *
 * Copyright (c) 1999-2001
 * National Semiconductor Corporation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 *
 *   * Redistributions of source code must retain the above copyright 
 *     notice, this list of conditions and the following disclaimer. 
 *
 *   * Redistributions in binary form must reproduce the above 
 *     copyright notice, this list of conditions and the following 
 *     disclaimer in the documentation and/or other materials provided 
 *     with the distribution. 
 *
 *   * Neither the name of the National Semiconductor Corporation nor 
 *     the names of its contributors may be used to endorse or promote 
 *     products derived from this software without specific prior 
 *     written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * NATIONAL SEMICONDUCTOR CORPORATION OR CONTRIBUTORS BE LIABLE FOR ANY 
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE,
 * INTELLECTUAL PROPERTY INFRINGEMENT, OR OTHERWISE) ARISING IN ANY WAY 
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF 
 * YOUR JURISDICTION. It is licensee's responsibility to comply with 
 * any export regulations applicable in licensee's jurisdiction. Under 
 * CURRENT (2001) U.S. export regulations this software 
 * is eligible for export from the U.S. and can be downloaded by or 
 * otherwise exported or reexported worldwide EXCEPT to U.S. embargoed 
 * destinations which include Cuba, Iraq, Libya, North Korea, Iran, 
 * Syria, Sudan, Afghanistan and any other country to which the U.S. 
 * has embargoed goods and services. 
 *
 * END_NSC_LIC_BSD */

/* NSC_LIC_GPL
 *
 * National Semiconductor Corporation Gnu General Public License for Durango
 *
 * (GPL License with Export Notice)
 *
 * Copyright (c) 1999-2001
 * National Semiconductor Corporation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted under the terms of the GNU General 
 * Public License as published by the Free Software Foundation; either 
 * version 2 of the License, or (at your option) any later version  
 *
 * In addition to the terms of the GNU General Public License, neither 
 * the name of the National Semiconductor Corporation nor the names of 
 * its contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * NATIONAL SEMICONDUCTOR CORPORATION OR CONTRIBUTORS BE LIABLE FOR ANY 
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE, 
 * INTELLECTUAL PROPERTY INFRINGEMENT, OR OTHERWISE) ARISING IN ANY WAY 
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE. See the GNU General Public License for more details. 
 *
 * EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF 
 * YOUR JURISDICTION. It is licensee's responsibility to comply with 
 * any export regulations applicable in licensee's jurisdiction. Under 
 * CURRENT (2001) U.S. export regulations this software 
 * is eligible for export from the U.S. and can be downloaded by or 
 * otherwise exported or reexported worldwide EXCEPT to U.S. embargoed 
 * destinations which include Cuba, Iraq, Libya, North Korea, Iran, 
 * Syria, Sudan, Afghanistan and any other country to which the U.S. 
 * has embargoed goods and services. 
 *
 * You should have received a copy of the GNU General Public License 
 * along with this file; if not, write to the Free Software Foundation, 
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 *
 * END_NSC_LIC_GPL */

/* SUPER IO DEFINITIONS */

#define INDEX_1					0x15C	/* base address 1 selected */
#define DATA_1					0x15D
#define INDEX_2					0x2E	/* base address 2 selected */
#define DATA_2					0x2F
#define PCI_INDEX				0xCF8	/* PCI configuration space INDEX */
#define PCI_DATA				0xCFC	/* PCI configuration space DATA  */
#define BASE_ADR_MSB_REG		0x60	/* base address MSB register */
#define BASE_ADR_LSB_REG		0x61	/* base address LSB register */

#define SIO_BASE_ADR_15C_15D	0x6000000
#define SIO_BASE_ADR_2E_2F 		0x4000000

/* SUPER IO GLOBALS */

unsigned short index_reg, data_reg;

/* ACCESS BUS DEFINITIONS */

#define ACC_I2C_TIMEOUT 1000000		/* Number of reads before timing out */
#define ACB1_BASE 	    0x810	/* ACCESS.bus base addresses         */
#define ACB2_BASE 	    0x820
#define ACBSDA			0	/* ACB serial data                   */
#define ACBST			1	/* ACB status                        */
#define ACBCST			2	/* ACB control status                */
#define ACBCTL1			3	/* ACB control 1                     */
#define ACBADDR			4	/* ACB own address                   */
#define ACBCTL2		    5		/* ACB control 2                     */
#define LDN				0x7	/* Logical Device Numbers            */
#define ACB1_LDN		0x5
#define ACB2_LDN		0x6

/* INITIAL ACCESS.bus BASE ADDRESS VALUES */

unsigned short base_address_array[3] = { 0, ACB1_BASE, ACB2_BASE };
char Freq = 0x71;

/* LOCAL ACCESS.bus FUNCTION DECLARATIONS */

void acc_i2c_start(unsigned char busnum);
void acc_i2c_stop(unsigned char busnum);
void acc_i2c_abort_data(unsigned char busnum);
void acc_i2c_bus_recovery(unsigned char busnum);
void acc_i2c_stall_after_start(unsigned char busnum, int state);
void acc_i2c_send_address(unsigned char busnum, unsigned char cData);
int acc_i2c_ack(unsigned char busnum, int fPut, int negAck);
void acc_i2c_stop_clock(unsigned char busnum);
void acc_i2c_activate_clock(unsigned char busnum);
void acc_i2c_write_byte(unsigned char busnum, unsigned char cData);
unsigned char acc_i2c_read_byte(unsigned char busnum, int last_byte);
void acc_i2c_reset_bus(unsigned char busnum);
int acc_i2c_request_master(unsigned char busnum);
void acc_i2c_config(unsigned char busnum, short adr, char freq);
char acc_i2c_set_freq(unsigned char busnum, char freq);
unsigned short acc_i2c_set_base_address(unsigned char busnum, short adr);

/* LOCAL HELPER ROUTINES */

void OsPciReadDWord(int bus, int dev, int func, int address,
		    unsigned long *data);
int sio_set_index_data_reg(void);
void sio_write_reg(unsigned char reg, unsigned char data);
unsigned char sio_read_reg(unsigned char reg);

int acc_i2c_reset(unsigned char busnum, short adr, char freq);
int acc_i2c_write(unsigned char busnum, unsigned char chipadr,
		  unsigned char subadr, unsigned char bytes,
		  unsigned char *data);
int acc_i2c_read(unsigned char busnum, unsigned char chipadr,
		 unsigned char subadr, unsigned char bytes,
		 unsigned char *data);
int acc_i2c_select_gpio(int clock, int data);
int acc_i2c_init(void);
void acc_i2c_cleanup(void);

/*---------------------------------------------------------------------------
 * OsPciReadDWord
 *
 * This routine reads one double word from the PCI configuration header
 * defined by 'bus', 'dev', 'func' and 'address' to the double word
 * pointed to by 'data'.
 *
 * Returns : None.
 *---------------------------------------------------------------------------
 */
void
OsPciReadDWord(int bus, int dev, int func, int address, unsigned long *data)
{
   /*
    * The address of a double word in the Configuration Header is built in
    * the following way :
    * {10000000,bus[23:16],device[15:11],function[10:8],address[7:2],00}
    */
   long addr = (0x80000000 |
		((bus & 0xff) << 16) |
		((dev & 0x1f) << 11) |
		((func & 0x7) << 8) | (address & 0xff));
   OUTD(PCI_INDEX, addr);
   *data = IND(PCI_DATA);
}

/*---------------------------------------------------------------------------
 *	sio_set_index_data_reg
 *
 *	This routine checks which index and data registers to use
 *  in order to access the Super I/O registers
 *
 *	Returns : 1 - OK
 *            0 - Super I/O disabled or configuration access disabled
 *
 *---------------------------------------------------------------------------
 */
int
sio_set_index_data_reg(void)
{
   unsigned long xbus_expention_bar, io_control_reg1;

   OsPciReadDWord(0, 0x12, 5, 0x10, &xbus_expention_bar);
   xbus_expention_bar = xbus_expention_bar & 0xfffffffe;
   io_control_reg1 = IND((unsigned short)xbus_expention_bar);

   if ((io_control_reg1) & (SIO_BASE_ADR_15C_15D)) {
      index_reg = INDEX_1;
      data_reg = DATA_1;
      return (1);
   }

   if ((io_control_reg1) & (SIO_BASE_ADR_2E_2F)) {
      index_reg = INDEX_2;
      data_reg = DATA_2;
      return (1);
   }

   return (0);
}

/*---------------------------------------------------------------------------
 *	sio_write_reg
 *
 *	This routine writes 'data' to 'reg' Super I/O register
 *
 *	Returns : None
 *---------------------------------------------------------------------------
 */
void
sio_write_reg(unsigned char reg, unsigned char data)
{
   OUTB(index_reg, reg);
   OUTB(data_reg, data);
}

/*---------------------------------------------------------------------------
 *	sio_read_reg
 *
 *	This routine reads data from 'reg' Super I/O register
 *
 *	Returns : The data read from the requested register
 *---------------------------------------------------------------------------
 */
unsigned char
sio_read_reg(unsigned char reg)
{
   OUTB(index_reg, reg);
   return INB(data_reg);
}

/*---------------------------------------------------------------------------
 *	gfx_i2c_reset
 *
 *	This routine resets the I2C bus as follows :
 *	· Sets the base address of the ACCESS.bus
 *	· Sets the frequency of the ACCESS.bus
 *	· Resets the ACCESS.bus
 *
 * 	If 'adr'  is -1 the address is read from the hardware.
 *	If 'freq' is -1 the frequency is set to 56 clock cycles.
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
acc_i2c_reset(unsigned char busnum, short adr, char freq)
#else
int
gfx_i2c_reset(unsigned char busnum, short adr, char freq)
#endif
{
   if ((busnum != 1) && (busnum != 2))
      return GFX_STATUS_BAD_PARAMETER;
   acc_i2c_config(busnum, adr, freq);
   if (base_address_array[busnum] == 0)
      return GFX_STATUS_ERROR;
   acc_i2c_reset_bus(busnum);
   return GFX_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * gfx_i2c_select_gpio
 *
 * This routine selects which GPIO pins to use.
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
acc_i2c_select_gpio(int clock, int data)
#else
int
gfx_i2c_select_gpio(int clock, int data)
#endif
{
   /* THIS ROUTINE DOES NOT APPLY TO THE ACCESS.bus IMPLEMENTATION. */

   return (GFX_STATUS_OK);
}

/*---------------------------------------------------------------------------
 *	gfx_i2c_write
 *
 *	This routine writes data to the specified I2C address.
 *  busnum - ACCESS.bus number (1 or 2).
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
acc_i2c_write(unsigned char busnum, unsigned char chipadr,
	      unsigned char subadr, unsigned char bytes, unsigned char *data)
#else
int
gfx_i2c_write(unsigned char busnum, unsigned char chipadr,
	      unsigned char subadr, unsigned char bytes, unsigned char *data)
#endif
{
   int loop = 0;

   if ((busnum != 1) && (busnum != 2))
      return GFX_STATUS_BAD_PARAMETER;

   /* REQUEST MASTER */

   if (!acc_i2c_request_master(busnum))
      return (GFX_STATUS_ERROR);

   /* WRITE ADDRESS COMMAND */

   acc_i2c_ack(busnum, 1, 0);
   acc_i2c_stall_after_start(busnum, 1);
   acc_i2c_send_address(busnum, (unsigned char)(chipadr & 0xFE));
   acc_i2c_stall_after_start(busnum, 0);
   if (!acc_i2c_ack(busnum, 0, 0))
      return (GFX_STATUS_ERROR);

   /* WRITE COMMAND */

   acc_i2c_write_byte(busnum, subadr);
   if (!acc_i2c_ack(busnum, 0, 0))
      return (GFX_STATUS_ERROR);

   /* WRITE DATA */

   for (loop = 0; loop < bytes; loop++) {
      acc_i2c_write_byte(busnum, *data);
      if (loop < (bytes - 1))
	 data += sizeof(unsigned char);
      if (!acc_i2c_ack(busnum, 0, 0))
	 return (GFX_STATUS_ERROR);
   }
   data -= (bytes - 1);
   acc_i2c_stop(busnum);

   return GFX_STATUS_OK;
}

/*---------------------------------------------------------------------------
 *	gfx_i2c_read
 *
 *	This routine reads data from the specified I2C address.
 *  busnum - ACCESS.bus number (1 or 2).
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
acc_i2c_read(unsigned char busnum, unsigned char chipadr,
	     unsigned char subadr, unsigned char bytes, unsigned char *data)
#else
int
gfx_i2c_read(unsigned char busnum, unsigned char chipadr,
	     unsigned char subadr, unsigned char bytes, unsigned char *data)
#endif
{
   unsigned char bytesRead;

   if ((busnum != 1) && (busnum != 2))
      return GFX_STATUS_BAD_PARAMETER;

   if (bytes == 0)
      return GFX_STATUS_OK;

   /* REQUEST MASTER */

   if (!acc_i2c_request_master(busnum))
      return (GFX_STATUS_ERROR);

   /* WRITE ADDRESS COMMAND */

   acc_i2c_ack(busnum, 1, 0);
   acc_i2c_stall_after_start(busnum, 1);
   acc_i2c_send_address(busnum, (unsigned char)(chipadr & 0xFE));
   acc_i2c_stall_after_start(busnum, 0);
   if (!acc_i2c_ack(busnum, 0, 0))
      return (GFX_STATUS_ERROR);

   /* WRITE COMMAND */

   acc_i2c_write_byte(busnum, subadr);
   if (!acc_i2c_ack(busnum, 0, 0))
      return (GFX_STATUS_ERROR);

   /* START THE READ */

   acc_i2c_start(busnum);

   /* WRITE ADDRESS COMMAND */

   acc_i2c_ack(busnum, 1, 1);
   acc_i2c_stall_after_start(busnum, 1);
   acc_i2c_send_address(busnum, (unsigned char)(chipadr | 0x01));

   /* IF LAST BYTE */

   if (bytes == 1)
      acc_i2c_ack(busnum, 1, 1);
   else
      acc_i2c_ack(busnum, 1, 0);

   acc_i2c_stall_after_start(busnum, 0);

   if (!acc_i2c_ack(busnum, 0, 0))
      return (GFX_STATUS_ERROR);

   /* READ COMMAND */

   for (bytesRead = 0; bytesRead < bytes; bytesRead += 1) {
      if (bytesRead < (bytes - 2)) {
	 data[bytesRead] = acc_i2c_read_byte(busnum, 0);
	 acc_i2c_ack(busnum, 1, 0);
      } else if (bytesRead == (bytes - 2)) {	/* TWO BYTES LEFT */
	 acc_i2c_ack(busnum, 1, 1);
	 data[bytesRead] = acc_i2c_read_byte(busnum, 0);
	 acc_i2c_ack(busnum, 1, 1);
      } else {				/* LAST BYTE */

	 data[bytesRead] = acc_i2c_read_byte(busnum, 1);
	 acc_i2c_stop(busnum);
      }

      /* WHILE NOT LAST BYTE */

      if ((!(bytesRead == (bytes - 1))) && (!acc_i2c_ack(busnum, 0, 0)))
	 return (bytesRead);
   }

   return GFX_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * gfx_i2c_init
 *
 * This routine initializes the use of the ACCESS.BUS.
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
int
acc_i2c_init(void)
#else
int
gfx_i2c_init(void)
#endif
{
   /* ### ADD ### THIS ROUTINE IS NOT YET IMPLEMENTED FOR ACCESS.bus */
   return (GFX_STATUS_OK);
}

/*---------------------------------------------------------------------------
 * gfx_i2c_cleanup
 *
 * This routine ends the use of the ACCESS.BUS.
 *---------------------------------------------------------------------------
 */
#if GFX_I2C_DYNAMIC
void
acc_i2c_cleanup(void)
#else
void
gfx_i2c_cleanup(void)
#endif
{
   /* ### ADD ### THIS ROUTINE IS NOT YET IMPLEMENTED FOR ACCESS.bus */
}

/*--------------------------------------------------------*/
/*  LOCAL ROUTINES SPECIFIC TO ACCESS.bus IMPLEMENTATION  */
/*--------------------------------------------------------*/

/*---------------------------------------------------------------------------
 * acc_i2c_reset_bus
 *
 * This routine resets the I2C bus.
 *---------------------------------------------------------------------------
 */
void
acc_i2c_reset_bus(unsigned char busnum)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];

   /* Disable the ACCESS.bus device and */
   /* Configure the SCL frequency */
   OUTB((unsigned short)(bus_base_address + ACBCTL2),
	(unsigned char)(Freq & 0xFE));

   /* Configure no interrupt mode (polling) and */
   /* Disable global call address */
   OUTB((unsigned short)(bus_base_address + ACBCTL1), 0x0);

   /* Disable slave address */
   OUTB((unsigned short)(bus_base_address + ACBADDR), 0x0);

   /* Enable the ACCESS.bus device */
   reg = INB((unsigned short)(bus_base_address + ACBCTL2));
   reg |= 0x01;
   OUTB((unsigned short)(bus_base_address + ACBCTL2), reg);

   /* Issue STOP event */

   acc_i2c_stop(busnum);

   /* Clear NEGACK, STASTR and BER bits */
   OUTB((unsigned short)(bus_base_address + ACBST), 0x38);

   /* Clear BB (BUS BUSY) bit */
   reg = INB((unsigned short)(bus_base_address + ACBCST));
   reg |= 0x02;
   OUTB((unsigned short)(bus_base_address + ACBCST), reg);
}

/*---------------------------------------------------------------------------
 * acc_i2c_start
 *
 * This routine starts a transfer on the I2C bus.
 *---------------------------------------------------------------------------
 */
void
acc_i2c_start(unsigned char busnum)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];

   reg = INB((unsigned short)(bus_base_address + ACBCTL1));
   reg |= 0x01;
   OUTB((unsigned short)(bus_base_address + ACBCTL1), reg);
}

/*---------------------------------------------------------------------------
 * acc_i2c_stop
 *
 * This routine stops a transfer on the I2C bus.
 *---------------------------------------------------------------------------
 */
void
acc_i2c_stop(unsigned char busnum)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];

   reg = INB((unsigned short)(bus_base_address + ACBCTL1));
   reg |= 0x02;
   OUTB((unsigned short)(bus_base_address + ACBCTL1), reg);
}

/*---------------------------------------------------------------------------
 * acc_i2c_abort_data
 *---------------------------------------------------------------------------
 */
void
acc_i2c_abort_data(unsigned char busnum)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];

   acc_i2c_stop(busnum);
   reg = INB((unsigned short)(bus_base_address + ACBCTL1));
   reg |= 0x10;
   OUTB((unsigned short)(bus_base_address + ACBCTL1), reg);
}

/*---------------------------------------------------------------------------
 * acc_i2c_bus_recovery
 *---------------------------------------------------------------------------
 */
void
acc_i2c_bus_recovery(unsigned char busnum)
{
   acc_i2c_abort_data(busnum);
   acc_i2c_reset_bus(busnum);
}

/*---------------------------------------------------------------------------
 * acc_i2c_stall_after_start
 *---------------------------------------------------------------------------
 */
void
acc_i2c_stall_after_start(unsigned char busnum, int state)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];

   reg = INB((unsigned short)(bus_base_address + ACBCTL1));
   if (state)
      reg |= 0x80;
   else
      reg &= 0x7F;
   OUTB((unsigned short)(bus_base_address + ACBCTL1), reg);

   if (!state) {
      reg = INB((unsigned short)(bus_base_address + ACBST));
      reg |= 0x08;
      OUTB((unsigned short)(bus_base_address + ACBST), reg);
   }
}

/*---------------------------------------------------------------------------
 * acc_i2c_send_address
 *---------------------------------------------------------------------------
 */
void
acc_i2c_send_address(unsigned char busnum, unsigned char cData)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];
   unsigned long timeout = 0;

   /* WRITE THE DATA */

   OUTB((unsigned short)(bus_base_address + ACBSDA), cData);
   while (1) {
      reg = INB((unsigned short)(bus_base_address + ACBST));
      if ((reg & 0x38) != 0)		/* check STASTR, BER and NEGACK */
	 break;
      if (timeout++ == ACC_I2C_TIMEOUT) {
	 acc_i2c_bus_recovery(busnum);
	 return;
      }
   }

   /* CHECK FOR BUS ERROR */

   if (reg & 0x20) {
      acc_i2c_bus_recovery(busnum);
      return;
   }

   /* CHECK NEGATIVE ACKNOWLEDGE */

   if (reg & 0x10) {
      acc_i2c_abort_data(busnum);
      return;
   }

}

/*---------------------------------------------------------------------------
 * acc_i2c_ack
 *
 * This routine looks for acknowledge on the I2C bus.
 *---------------------------------------------------------------------------
 */
int
acc_i2c_ack(unsigned char busnum, int fPut, int negAck)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];
   unsigned long timeout = 0;

   if (fPut) {				/* read operation */
      if (!negAck) {
	 /* Push Ack onto I2C bus */
	 reg = INB((unsigned short)(bus_base_address + ACBCTL1));
	 reg &= 0xE7;
	 OUTB((unsigned short)(bus_base_address + ACBCTL1), reg);
      } else {
	 /* Push negAck onto I2C bus */
	 reg = INB((unsigned short)(bus_base_address + ACBCTL1));
	 reg |= 0x10;
	 OUTB((unsigned short)(bus_base_address + ACBCTL1), reg);
      }
   } else {				/* write operation */
      /* Receive Ack from I2C bus */
      while (1) {
	 reg = INB((unsigned short)(bus_base_address + ACBST));
	 if ((reg & 0x70) != 0)		/* check SDAST, BER and NEGACK */
	    break;
	 if (timeout++ == ACC_I2C_TIMEOUT) {
	    acc_i2c_bus_recovery(busnum);
	    return (0);
	 }
      }

      /* CHECK FOR BUS ERROR */

      if (reg & 0x20) {
	 acc_i2c_bus_recovery(busnum);
	 return (0);
      }

      /* CHECK NEGATIVE ACKNOWLEDGE */

      if (reg & 0x10) {
	 acc_i2c_abort_data(busnum);
	 return (0);
      }
   }
   return (1);
}

/*---------------------------------------------------------------------------
 * acc_i2c_stop_clock
 *
 * This routine stops the ACCESS.bus clock.
 *---------------------------------------------------------------------------
 */
void
acc_i2c_stop_clock(unsigned char busnum)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];

   reg = INB((unsigned short)(bus_base_address + ACBCTL2));
   reg &= ~0x01;
   OUTB((unsigned short)(bus_base_address + ACBCTL2), reg);
}

/*---------------------------------------------------------------------------
 * acc_i2c_activate_clock
 *
 * This routine activates the ACCESS.bus clock.
 *---------------------------------------------------------------------------
 */
void
acc_i2c_activate_clock(unsigned char busnum)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];

   reg = INB((unsigned short)(bus_base_address + ACBCTL2));
   reg |= 0x01;
   OUTB((unsigned short)(bus_base_address + ACBCTL2), reg);
}

/*---------------------------------------------------------------------------
 * acc_i2c_write_byte
 *
 * This routine writes a byte to the I2C bus
 *---------------------------------------------------------------------------
 */
void
acc_i2c_write_byte(unsigned char busnum, unsigned char cData)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];
   unsigned long timeout = 0;

   while (1) {
      reg = INB((unsigned short)(bus_base_address + ACBST));
      if (reg & 0x70)
	 break;
      if (timeout++ == ACC_I2C_TIMEOUT) {
	 acc_i2c_bus_recovery(busnum);
	 return;
      }
   }

   /* CHECK FOR BUS ERROR */

   if (reg & 0x20) {
      acc_i2c_bus_recovery(busnum);
      return;
   }

   /* CHECK NEGATIVE ACKNOWLEDGE */

   if (reg & 0x10) {
      acc_i2c_abort_data(busnum);
      return;
   }

   /* WRITE THE DATA */

   OUTB((unsigned short)(bus_base_address + ACBSDA), cData);
}

/*---------------------------------------------------------------------------
 * acc_i2c_read_byte
 *
 * This routine reads a byte from the I2C bus
 *---------------------------------------------------------------------------
 */
unsigned char
acc_i2c_read_byte(unsigned char busnum, int last_byte)
{
   unsigned char cData, reg;
   unsigned short bus_base_address = base_address_array[busnum];
   unsigned long timeout = 0;

   while (1) {
      reg = INB((unsigned short)(bus_base_address + ACBST));
      if (reg & 0x60)
	 break;
      if (timeout++ == ACC_I2C_TIMEOUT) {
	 acc_i2c_bus_recovery(busnum);
	 return (0xEF);
      }
   }

   /* CHECK FOR BUS ERROR */

   if (reg & 0x20) {
      acc_i2c_bus_recovery(busnum);
      return (0xEE);
   }

   /* READ DATA */
   if (last_byte)
      acc_i2c_stop_clock(busnum);
   cData = INB((unsigned short)(bus_base_address + ACBSDA));
   if (last_byte)
      acc_i2c_activate_clock(busnum);

   return (cData);
}

/*---------------------------------------------------------------------------
 * acc_i2c_request_master
 *---------------------------------------------------------------------------
 */
int
acc_i2c_request_master(unsigned char busnum)
{
   unsigned char reg;
   unsigned short bus_base_address = base_address_array[busnum];
   unsigned long timeout = 0;

   acc_i2c_start(busnum);
   while (1) {
      reg = INB((unsigned short)(bus_base_address + ACBST));
      if (reg & 0x60)
	 break;
      if (timeout++ == ACC_I2C_TIMEOUT) {
	 acc_i2c_bus_recovery(busnum);
	 return (0);
      }
   }

   /* CHECK FOR BUS ERROR */

   if (reg & 0x20) {
      acc_i2c_abort_data(busnum);
      return (0);
   }

   /* CHECK NEGATIVE ACKNOWLEDGE */

   if (reg & 0x10) {
      acc_i2c_abort_data(busnum);
      return (0);
   }
   return (1);
}

/*--------------------------------------------------------*/
/*  LOCAL ROUTINES SPECIFIC TO ACCESS.bus INITIALIZATION  */
/*--------------------------------------------------------*/

/*----------------------------------------------------------------------------
 * acc_i2c_config
 *
 * This routine configures the I2C bus
 *----------------------------------------------------------------------------
 */
void
acc_i2c_config(unsigned char busnum, short adr, char freq)
{
   base_address_array[busnum] = acc_i2c_set_base_address(busnum, adr);
   Freq = acc_i2c_set_freq(busnum, freq);
}

/*----------------------------------------------------------------------------
 * acc_i2c_set_freq
 *
 * This routine sets the frequency of the I2C bus
 *----------------------------------------------------------------------------
 */
char
acc_i2c_set_freq(unsigned char busnum, char freq)
{
   unsigned short bus_base_address = base_address_array[busnum];

   OUTB((unsigned short)(bus_base_address + ACBCTL2), 0x0);

   if (freq == -1)
      freq = 0x71;
   else {
      freq = freq << 1;
      freq |= 0x01;
   }

   OUTB((unsigned short)(bus_base_address + ACBCTL2), freq);
   return (freq);
}

/*---------------------------------------------------------------------------
 * acc_i2c_set_base_address
 *
 * This routine sets the base address of the I2C bus
 *---------------------------------------------------------------------------
 */
unsigned short
acc_i2c_set_base_address(unsigned char busnum, short adr)
{
   unsigned short ab_base_addr;

   /* Get Super I/O Index and Data registers */
   if (!sio_set_index_data_reg())
      return (0);

   /* Configure LDN to current ACB */
   if (busnum == 1)
      sio_write_reg(LDN, ACB1_LDN);
   if (busnum == 2)
      sio_write_reg(LDN, ACB2_LDN);

   if (adr == -1) {
      /* Get ACCESS.bus base address */
      ab_base_addr = sio_read_reg(BASE_ADR_MSB_REG);
      ab_base_addr = ab_base_addr << 8;
      ab_base_addr |= sio_read_reg(BASE_ADR_LSB_REG);
      if (ab_base_addr != 0)
	 return ab_base_addr;
      else
	 adr = (busnum == 1 ? ACB1_BASE : ACB2_BASE);
   }

   /* Set ACCESS.bus base address */
   sio_write_reg(BASE_ADR_LSB_REG, (unsigned char)(adr & 0xFF));
   sio_write_reg(BASE_ADR_MSB_REG, (unsigned char)(adr >> 8));

   return adr;
}

/* END OF FILE */
