/*
 * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All Rights Reserved.
 * Copyright 2003, Thomas Kurschel. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 */
 
/*!
	I2C protocol
*/
 
#include "i2c.h"
 
#include <KernelExport.h>
#include <OS.h>
#include <string.h>
 
 
//#define TRACE_I2C
#ifdef TRACE_I2C
#ifdef __cplusplus
extern "C" 
#endif
void _sPrintf(const char *format, ...);
#	define TRACE(x...) _sPrintf("I2C: " x)
#else
#	define TRACE(x...) ;
#endif
 
 
/*!
	I2c timings, rounded up (Philips 1995 i2c bus specification, p20)
*/
 
//! Timing for standard mode i2c (100kHz max)
const static i2c_timing kTiming100k = {
	.buf = 5,
	.hd_sta = 4,
	.low = 5,
	.high = 4,
	.su_sta = 5,
	.hd_dat = 1,
	.su_dat = 2,
	.r = 2,
	.f = 2,
	.su_sto = 4,
 
	// these are unspecified, use half a clock cycle as a safe guess
	.start_timeout = 5,
	.byte_timeout = 5,
	.bit_timeout = 5,
	.ack_start_timeout = 5,
	.ack_timeout = 5
};
 
//! Timing for fast mode i2c (400kHz max)
const static i2c_timing kTiming400k = {
	.buf = 2,
	.hd_sta = 1,
	.low = 2,
	.high = 1,
	.su_sta = 1,
	.hd_dat = 0,
	.su_dat = 1,
	.r = 1,
	.f = 1,
	.su_sto = 1,
 
	// these are unspecified, use half a clock cycle as a safe guess
	.start_timeout = 2,
	.byte_timeout = 2,
	.bit_timeout = 2,
	.ack_start_timeout = 2,
	.ack_timeout = 2
};
 
 
/*!
	There's no spin in user space, but we need it to wait a couple
	of microseconds only
	(in this case, snooze has much too much overhead)
*/
void
spin(bigtime_t delay)
{
	bigtime_t startTime = system_time();
 
	while (system_time() - startTime < delay)
		;
}
 
 
//! Wait until slave releases clock signal ("clock stretching")
static status_t
wait_for_clk(const i2c_bus *bus, bigtime_t timeout)
{
	bigtime_t startTime;
 
	// wait for clock signal to raise
	spin(bus->timing.r);
 
	startTime = system_time();
 
	while (true) {
		int clk, data;
 
		bus->get_signals(bus->cookie, &clk, &data);
		if (clk != 0)
			return B_OK;
 
		if (system_time() - startTime > timeout) {
			TRACE("%s: Timeout waiting on clock (r)\n");
			return B_TIMEOUT;
		}
 
		spin(bus->timing.r);
	}
}
 
 
//!	Send start or repeated start condition
static status_t
send_start_condition(const i2c_bus *bus)
{
	status_t status;
 
	bus->set_signals(bus->cookie, 1, 1);
 
	status = wait_for_clk(bus, bus->timing.start_timeout);
	if (status != B_OK) {
		TRACE("%s: Timeout sending start condition\n", __func__);
		return status;
	}
 
	spin(bus->timing.su_sta);
	bus->set_signals(bus->cookie, 1, 0);
	spin(bus->timing.hd_sta);
	bus->set_signals(bus->cookie, 0, 0);
	spin(bus->timing.f);
 
	return B_OK;
}
 
 
//!	Send stop condition
static status_t
send_stop_condition(const i2c_bus *bus)
{
	status_t status;
 
	bus->set_signals(bus->cookie, 0, 0);
	spin(bus->timing.r);
	bus->set_signals(bus->cookie, 1, 0);
 
	// a slave may wait for us, so let elapse the acknowledge timeout
	// to make the slave release bus control
	status = wait_for_clk(bus, bus->timing.ack_timeout);
	if (status != B_OK) {
		TRACE("%s: Timeout sending stop condition\n", __func__);
		return status;
	}
 
	spin(bus->timing.su_sto);
	bus->set_signals(bus->cookie, 1, 1);
	spin(bus->timing.buf);
 
	return B_OK;
}
 
 
//!	Send one bit
static status_t
send_bit(const i2c_bus *bus, uint8 bit, int timeout)
{
	status_t status;
 
	//TRACE("send_bit(bit = %d)\n", bit & 1);
 
	bus->set_signals(bus->cookie, 0, bit & 1);
	spin(bus->timing.su_dat);
	bus->set_signals(bus->cookie, 1, bit & 1);
 
	status = wait_for_clk(bus, timeout);
	if (status != B_OK) {
		TRACE("%s: Timeout when sending next bit\n", __func__);
		return status;
	}
 
	spin(bus->timing.high);
	bus->set_signals(bus->cookie, 0, bit & 1);
	spin(bus->timing.f + bus->timing.low);
 
	return B_OK;
}
 
 
//!	Send acknowledge and wait for reply
static status_t
send_acknowledge(const i2c_bus *bus)
{
	status_t status;
	bigtime_t startTime;
 
	// release data so slave can modify it
	bus->set_signals(bus->cookie, 0, 1);
	spin(bus->timing.su_dat);
	bus->set_signals(bus->cookie, 1, 1);
 
	status = wait_for_clk(bus, bus->timing.ack_start_timeout);
	if (status != B_OK) {
		TRACE("%s: Timeout when sending acknowledge\n", __func__);
		return status;
	}
 
	// data and clock is high, now wait for slave to pull data low
	// (according to spec, this can happen any time once clock is high)
	startTime = system_time();
 
	while (true) {
		int clk, data;
 
		bus->get_signals(bus->cookie, &clk, &data);
 
		if (data == 0)
			break;
 
		if (system_time() - startTime > bus->timing.ack_timeout) {
			TRACE("%s: slave didn't acknowledge byte within ack_timeout: %ld\n",
				__func__, bus->timing.ack_timeout);
			return B_TIMEOUT;
		}
 
		spin(bus->timing.r);
	}
 
	TRACE("send_acknowledge(): Success!\n");
 
	// make sure we've waited at least t_high
	spin(bus->timing.high);
 
	bus->set_signals(bus->cookie, 0, 1);
	spin(bus->timing.f + bus->timing.low);	
 
	return B_OK;
}
 
 
//!	Send byte and wait for acknowledge if <ackowledge> is true
static status_t
send_byte(const i2c_bus *bus, uint8 byte, bool acknowledge)
{
	int i;
 
	//TRACE("%s: (byte = %x)\n", __func__, byte);
 
	for (i = 7; i >= 0; --i) {
		status_t status = send_bit(bus, byte >> i,
			i == 7 ? bus->timing.byte_timeout : bus->timing.bit_timeout);
		if (status != B_OK)
			return status;
	}
 
	if (acknowledge)
		return send_acknowledge(bus);
 
	return B_OK;
}
 
 
//!	Send slave address, obeying 10-bit addresses and general call addresses
static status_t
send_slave_address(const i2c_bus *bus, int slaveAddress, bool isWrite)
{
	status_t status;
 
	TRACE("%s: 0x%X\n", __func__, slaveAddress);
	status = send_byte(bus, (slaveAddress & 0xfe) | !isWrite, true);
	if (status != B_OK)
		return status;
 
	// there are the following special cases if the first byte looks like:
	// - 0000 0000 - general call address (second byte with address follows)
	// - 0000 0001 - start byte
	// - 0000 001x - CBus address
	// - 0000 010x - address reserved for different bus format
	// - 0000 011x |
	// - 0000 1xxx |-> reserved
	// - 1111 1xxx |
	// - 1111 0xxx - 10 bit address  (second byte contains remaining 8 bits)
 
	// the lsb is 0 for write and 1 for read (except for general call address)
	if ((slaveAddress & 0xff) != 0 && (slaveAddress & 0xf8) != 0xf0)
		return B_OK;
 
	return send_byte(bus, slaveAddress >> 8, true);
		// send second byte if required
}
 
 
//!	Receive one bit
static status_t
receive_bit(const i2c_bus *bus, bool *bit, int timeout)
{
	status_t status;
	int clk, data;
 
	bus->set_signals(bus->cookie, 1, 1);
		// release clock
 
	// wait for slave to raise clock
	status = wait_for_clk(bus, timeout);
	if (status != B_OK) {
		TRACE("%s: Timeout waiting for bit sent by slave\n", __func__);
		return status;
	}
 
	bus->get_signals(bus->cookie, &clk, &data);
		// sample data
 
	spin(bus->timing.high);
		// leave clock high for minimal time
 
	bus->set_signals(bus->cookie, 0, 1);
		// pull clock low so slave waits for us before next bit
 
	spin(bus->timing.f + bus->timing.low);
		// let it settle and leave it low for minimal time
		// to make sure slave has finished bit transmission too
 
	*bit = data;
	return B_OK;
}
 
 
/*!
	Send positive acknowledge afterwards if <acknowledge> is true,
	else send negative one
*/
static status_t
receive_byte(const i2c_bus *bus, uint8 *resultByte, bool acknowledge)
{
	uint8 byte = 0;
	int i;
 
	// pull clock low to let slave wait for us
	bus->set_signals(bus->cookie, 0, 1);
 
	for (i = 7; i >= 0; i--) {
		bool bit;
 
		status_t status = receive_bit(bus, &bit,
			i == 7 ? bus->timing.byte_timeout : bus->timing.bit_timeout);
		if (status != B_OK)
			return status;
 
		byte = (byte << 1) | bit;
	}
 
	//SHOW_FLOW(3, "%x ", byte);
 
	*resultByte = byte;
 
	return send_bit(bus, acknowledge ? 0 : 1, bus->timing.bit_timeout);
}
 
 
//!	Send multiple bytes
static status_t
send_bytes(const i2c_bus *bus, const uint8 *writeBuffer, ssize_t writeLength)
{
	TRACE("%s: (length = %ld)\n", __func__, writeLength);
 
	for (; writeLength > 0; --writeLength, ++writeBuffer) {
		status_t status = send_byte(bus, *writeBuffer, true);
		if (status != B_OK)
			return status;
	}
 
	return B_OK;
}
 
 
//!	Receive multiple bytes
static status_t
receive_bytes(const i2c_bus *bus, uint8 *readBuffer, ssize_t readLength)
{
	TRACE("%s: (length = %ld)\n", __func__, readLength);
 
	for (; readLength > 0; --readLength, ++readBuffer) {
		status_t status = receive_byte(bus, readBuffer, readLength > 1);
		if (status != B_OK)
			return status;
	}
 
	return B_OK;
}
 
 
//	#pragma mark - exported functions
 
 
//!	Combined i2c send+receive format
status_t
i2c_send_receive(const i2c_bus *bus, int slaveAddress, const uint8 *writeBuffer,
	size_t writeLength, uint8 *readBuffer, size_t readLength)
{
	status_t status = send_start_condition(bus);
	if (status != B_OK)
		return status;
 
	status = send_slave_address(bus, slaveAddress, true);
	if (status != B_OK)
		goto err;
 
	status = send_bytes(bus, writeBuffer, writeLength);
	if (status != B_OK)
		goto err;
 
	status = send_start_condition(bus);
	if (status != B_OK)
		return status;
 
	status = send_slave_address(bus, slaveAddress, false);
	if (status != B_OK)
		goto err;
 
	status = receive_bytes(bus, readBuffer, readLength);
	if (status != B_OK)
		goto err;
 
	return send_stop_condition(bus);
 
err:
	TRACE("%s: Cancelling transmission\n", __func__);
	send_stop_condition(bus);
	return status;
}
 
 
void
i2c_get100k_timing(i2c_timing *timing)
{
	// AKA standard i2c mode
	memcpy(timing, &kTiming100k, sizeof(i2c_timing));
}
 
 
void
i2c_get400k_timing(i2c_timing *timing)
{
	// AKA fast i2c mode
	memcpy(timing, &kTiming400k, sizeof(i2c_timing));
}

V564 The '|' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '||' operator.