root/src/add-ons/accelerants/common/i2c.c
/*
 * 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", __func__);
                        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, uint8 slaveAddress, bool isWrite)
{
        status_t status;

        TRACE("%s: 0x%X\n", __func__, slaveAddress);
        status = send_byte(bus, (slaveAddress & 0xfe) | (isWrite ? 0 : 1), 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_callback(const i2c_bus *bus, uint32 slaveAddress, const uint8 *writeBuffer,
        size_t writeLength, uint8 *readBuffer, size_t readLength)
{
        status_t status;

        // the address is 7-bit
        slaveAddress <<= 1;
        if (slaveAddress > 0xff)
                return B_BAD_VALUE;

        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));
}