+/* This implemenation is based on an example I once grabbed from
+ the Philips bbs.
+ Don't know who wrote it, but is has been hacked so heavely, he/she wouldn't
+ recogize it anyway */
+
+#define DEBUG_I2C
+
+#ifdef DEBUG_I2C
+#include <stdio.h>
+#endif
+
+#include <ds80c390.h>
+#include "i2clole.h"
+
+#define I2CDELAY 1
+
+// we are (ab)using the CAN CTX and CRX for serial data and serial clock
+#define SCL_HIGH (P5 |= 1)
+#define SCL_LOW (P5 &= ~1)
+#define SDA_HIGH (P5 |= 2)
+#define SDA_LOW (P5 &= ~2)
+
+#define SDA_OUT(b) (b ? SDA_HIGH : SDA_LOW)
+#define SDA_IN ((P5>>1)&1)
+#define SCL_IN (P5&1)
+
+char i2cTransmitBuffer[I2C_BUFSIZE]; /* Global transfer buffers */
+char i2cReceiveBuffer[I2C_BUFSIZE];
+
+char i2cError = 0; /* Last error */
+
+
+/*
+ * Makes sure that the bus is in a known condition. Returns 1 on success,
+ * 0 if some other device is pulling on the bus.
+ */
+
+char I2CReset(void)
+{
+ //puts ("<I2CReset>");
+ SDA_LOW;
+ SCL_LOW;
+ SCL_HIGH;
+ SDA_HIGH;
+ i2cError = 0;
+ return (SCL_IN && SDA_IN);
+}
+
+
+/*
+ * Generates a start condition on the bus. Returns 0 on success, 1 if some
+ * other device is holding the bus.
+ */
+
+char I2CStart(void)
+{
+ //puts ("<I2CStart>");
+
+ SDA_HIGH;
+ SCL_HIGH;
+ I2CDelay(I2CDELAY);
+ SDA_LOW; /* Pull SDA down... */
+ I2CDelay(I2CDELAY);
+ SCL_LOW; /* ...and then SCL -> start condition. */
+ I2CDelay(I2CDELAY);
+ return 0;
+}
+
+
+/*
+ * Generates a stop condition on the bus. Returns 0 on success, 1 if some
+ * other device is holding the bus.
+ */
+
+char I2CStop(void)
+{
+ //puts ("<I2CStop>");
+
+ SDA_LOW;
+ SCL_HIGH; /* Let SCL go up */
+ I2CDelay(I2CDELAY);
+ SDA_HIGH; /* ...and then SDA up -> stop condition. */
+ I2CDelay(I2CDELAY);
+
+ return (SCL_IN && SDA_IN); /* Both will be up, if everything is fine */
+}
+
+
+/*
+ * Clock out one bit.
+ * Returns 0 on success, 1 if we lose arbitration.
+ */
+
+char BitOutI2C(char bout)
+{
+ //puts("<BitOutI2C>");
+
+ SDA_OUT(bout); /* Put data out on SDA */
+ I2CDelay(I2CDELAY);
+ SCL_HIGH; /* Let SCL go up */
+ while(!SCL_IN) /* Wait until all other devices are ready */
+ {
+ // should do a timeout here
+ }
+
+ if (SDA_IN != bout) /* Arbitration lost, release bus and return */
+ {
+ SDA_HIGH; /* Should be up anyway, but make sure */
+ i2cError = I2CERR_LOST;
+ I2CDumpError(i2cError);
+ return 1;
+ }
+ I2CDelay(I2CDELAY);
+ SCL_LOW; /* Pull SCL back down */
+ I2CDelay(I2CDELAY);
+ return 0; /* OK */
+}
+
+
+/*
+ * Clock in one bit.
+ */
+
+char BitInI2C(void)
+{
+ char bin;
+
+ //puts ("<BitInI2C>");
+
+ // SDA is opencollector, so:
+ SDA_HIGH;
+
+ SCL_HIGH; /* Let SCL go up */
+ while(!SCL_IN) /* Wait for other devices */
+ {
+ // should do a timeout here
+ }
+ bin = SDA_IN; /* Read in data */
+ I2CDelay(I2CDELAY);
+ SCL_LOW; /* Pull SCL back up */
+ I2CDelay(I2CDELAY);
+ return bin; /* Return the sampled bit */
+}
+
+
+/*
+ * Send one byte on the bus. No start or stop conditions are generated here,
+ * but i2cError will be set according to the result.
+ * Returns 0 on success, 1 if we lose arbitration or if the slave doesn't
+ * acknowledge the byte. Check i2cError for the actual result on error.
+ */
+
+char ByteOutI2C(char dat)
+{
+ char bit_count;
+
+ //puts ("<ByteOutI2C>");
+
+ bit_count = 8;
+ while(bit_count) {
+ if (dat & 0x80) {
+ if (BitOutI2C(1)) {
+ I2CDumpError(i2cError);
+ return 1;
+ }
+ } else {
+ if (BitOutI2C(0)) {
+ I2CDumpError(i2cError);
+ return 1;
+ }
+ }
+ dat <<= 1;
+ bit_count--;
+ }
+
+ if (BitInI2C()) {
+ i2cError = I2CERR_NAK;
+ I2CDumpError(i2cError);
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * Reads one byte in from the slave. Ack must be 1 if this is the last byte
+ * to be read during this transfer, 0 otherwise (as per I2C bus specification,
+ * the receiving master must acknowledge all but the last byte during a
+ * transfer).
+ */
+
+char I2CByteIn(char ack)
+{
+ char bit_count, byte_in;
+
+ //puts ("<I2CByteIn>");
+
+ bit_count = 8;
+ byte_in = 0;
+
+ while(bit_count)
+ {
+ byte_in <<= 1;
+ if (BitInI2C()) byte_in |= 0x01;
+ bit_count--;
+ }
+
+ BitOutI2C(ack);
+ SDA_HIGH; /* Added 18-Jul-95 - thanks to Ray Bellis */
+ return byte_in;
+}
+
+
+/*
+ * Send 'count' bytes to slave 'addr'.
+ * Returns 0 on success. Stop condition is sent only when send_stop is true.
+ */
+
+char I2CSendStop(char addr, char count, char send_stop)
+{
+ char byteptr, byte_out;
+
+ //puts ("<I2CSendStop>");
+
+ if (I2CStart()) return 1;
+ i2cError = 0;
+
+ byte_out = addr & 0xfe; /* Ensure that it's a write address */
+ count++; /* Include slave address to byte count */
+ byteptr = 0;
+ while(count)
+ {
+ if (ByteOutI2C(byte_out))
+ {
+ if (i2cError == I2CERR_NAK && send_stop) I2CStop();
+ return i2cError;
+ }
+ byte_out = i2cTransmitBuffer[byteptr];
+ byteptr++;
+ count--;
+ }
+
+ if (send_stop) I2CStop();
+ return 0;
+}
+
+
+/*
+ * Read in 'count' bytes from slave 'addr'.
+ * Returns 0 on success.
+ */
+
+char i2c_recv(char addr, char count)
+{
+ char byteptr, byte_in;
+
+ //puts ("<i2c_rec>");
+
+ if (I2CStart()) return 1;
+ i2cError = 0;
+ byteptr = 0;
+
+ byte_in = addr | 0x01;
+
+ if (ByteOutI2C(byte_in))
+ {
+ if (i2cError == I2CERR_NAK) I2CStop();
+ return i2cError;
+ }
+
+ while(count)
+ {
+ count-=1;
+ if (count) {
+ byte_in = I2CByteIn(0);
+ } else {
+ byte_in = I2CByteIn(1); /* No ACK during last byte */
+ }
+ i2cReceiveBuffer[byteptr] = byte_in;
+ byteptr++;
+ }
+
+ I2CStop();
+
+ return (i2cError ? 1 : 0);
+}
+
+
+/*
+ * Write 'tx_count' bytes to slave 'addr', then use a repeated start condition
+ * to read 'rx_count' bytes from the same slave during the same transfer.
+ * Returns 0 on success, 1 otherwise. On error, check i2cError for the actual
+ * error value.
+ */
+
+char I2CSendReceive(char addr, char tx_count, char rx_count)
+{
+ //puts ("<i2c_send_recv>");
+
+ if (I2CSendStop(addr, tx_count, 0))
+ {
+ /* If send fails, abort but don't send a stop condition if we lost
+ arbitration */
+
+ if (i2cError != I2CERR_LOST) I2CStop();
+ return 1;
+ }
+
+ SDA_HIGH; /* One of these may be low now, in which case the next */
+ SCL_HIGH; /* start condition wouldn't be detected so make */
+ I2CDelay(I2CDELAY); /* sure that they're up and wait for one delay slot */
+
+ if (i2c_recv((char)(addr|0x01), rx_count)) return 1;
+ return (i2cError ? 1 : 0);
+}
+
+/*
+ * Dump an error message.
+ */
+
+void I2CDumpError(char error)
+{
+#ifdef DEBUG_I2C
+ switch(error)
+ {
+ case 0:
+ puts("I2C: OK.");
+ break;
+ case I2CERR_NAK:
+ puts("I2C: Slave didn't acknowledge");
+ break;
+ case I2CERR_LOST:
+ puts("I2C: Lost arbitration with another master");
+ break;
+ case I2CERR_TIMEOUT:
+ puts("I2C: Timeout on bus");
+ break;
+ case I2CERR_BUS:
+ puts("I2C: The bus is stuck");
+ break;
+ default:
+ puts("I2C: Unknown error");
+ break;
+ }
+#endif
+}
+
+void I2CDelay(volatile long delay) {
+ while (delay--)
+ ;
+}