#include <stdlib.h>
#include <string.h>
#include <KernelExport.h>
#include "i2c_core.h"
#define TRACE_I2C
#ifdef TRACE_I2C
#define TRACE dprintf
#else
#define TRACE(a...)
#endif
static status_t i2c_writebyte(i2c_bus *bus, uint8 byte, int *ack);
static status_t i2c_readbyte(i2c_bus *bus, uint8 *pbyte);
static status_t i2c_read_unlocked(i2c_bus *bus, int address, void *data, int size);
static status_t i2c_write_unlocked(i2c_bus *bus, int address, const void *data, int size);
struct _i2c_bus
{
void *cookie;
bigtime_t delay;
bigtime_t timeout;
i2c_set_scl set_scl;
i2c_set_sda set_sda;
i2c_get_scl get_scl;
i2c_get_sda get_sda;
sem_id sem;
};
i2c_bus *
i2c_create_bus(void *cookie,
int frequency,
bigtime_t timeout,
i2c_set_scl set_scl,
i2c_set_sda set_sda,
i2c_get_scl get_scl,
i2c_get_sda get_sda)
{
i2c_bus *bus = malloc(sizeof(i2c_bus));
if (!bus)
return NULL;
bus->sem = create_sem(1, "i2c bus access");
if (bus->sem < 0) {
free(bus);
return NULL;
}
bus->cookie = cookie;
bus->delay = 1000000 / frequency;
if (bus->delay == 0)
bus->delay = 1;
bus->timeout = timeout;
bus->set_scl = set_scl;
bus->set_sda = set_sda;
bus->get_scl = get_scl;
bus->get_sda = get_sda;
set_scl(cookie, 1);
set_sda(cookie, 1);
return bus;
}
void
i2c_delete_bus(i2c_bus *bus)
{
if (!bus)
return;
delete_sem(bus->sem);
free(bus);
}
static inline void
set_sda_low(i2c_bus *bus)
{
bus->set_sda(bus->cookie, 0);
snooze(bus->delay);
}
static inline void
set_sda_high(i2c_bus *bus)
{
bus->set_sda(bus->cookie, 1);
snooze(bus->delay);
}
static inline void
set_scl_low(i2c_bus *bus)
{
bus->set_scl(bus->cookie, 0);
snooze(bus->delay);
}
static inline status_t
set_scl_high(i2c_bus *bus)
{
bigtime_t end = system_time() + bus->timeout;
bus->set_scl(bus->cookie, 1);
while (0 == bus->get_scl(bus->cookie)) {
if (system_time() > end)
return B_TIMED_OUT;
snooze(5);
}
snooze(bus->delay);
return B_OK;
}
static inline void
i2c_start(i2c_bus *bus)
{
set_sda_low(bus);
set_scl_low(bus);
}
static inline void
i2c_stop(i2c_bus *bus)
{
set_sda_low(bus);
set_scl_high(bus);
set_sda_high(bus);
}
static inline status_t
i2c_start_address(i2c_bus *bus, int address, int read )
{
status_t res;
uint8 addr;
int ack;
int i;
addr = (address << 1) | (read & 1);
for (i = 0; i < 5; i++) {
i2c_start(bus);
res = i2c_writebyte(bus, addr, &ack);
if (res == B_OK) {
if (ack)
break;
res = B_ERROR;
}
if (res == B_TIMED_OUT)
break;
i2c_stop(bus);
snooze(50);
}
return res;
}
status_t
i2c_writebyte(i2c_bus *bus, uint8 byte, int *ack)
{
int has_arbitration;
int i;
has_arbitration = 1;
for (i = 7; i >= 0; i--) {
int bit = (byte >> i) & 1;
if (has_arbitration) {
if (bit)
set_sda_high(bus);
else
set_sda_low(bus);
}
if (set_scl_high(bus) != B_OK) {
set_sda_high(bus);
TRACE("i2c_writebyte timeout at bit %d\n", i);
return B_TIMED_OUT;
}
if (has_arbitration) {
if (bit == 1 && 0 == bus->get_sda(bus->cookie)) {
has_arbitration = 0;
}
}
set_scl_low(bus);
}
set_sda_high(bus);
if (set_scl_high(bus) != B_OK) {
TRACE("i2c_writebyte timeout at ack\n");
return B_TIMED_OUT;
}
*ack = 0 == bus->get_sda(bus->cookie);
set_scl_low(bus);
return has_arbitration ? B_OK : B_ERROR;
}
status_t
i2c_readbyte(i2c_bus *bus, uint8 *pbyte)
{
int i;
uint8 byte;
set_sda_high(bus);
byte = 0;
for (i = 7; i >= 0; i--) {
if (set_scl_high(bus) != B_OK) {
TRACE("i2c_readbyte timeout at bit %d\n", i);
return B_TIMED_OUT;
}
byte = (byte << 1) | bus->get_sda(bus->cookie);
set_scl_low(bus);
}
*pbyte = byte;
return B_OK;
}
status_t
i2c_read_unlocked(i2c_bus *bus, int address, void *data, int size)
{
status_t status;
uint8 *bytes;
if (size <= 0)
return B_BAD_VALUE;
status = i2c_start_address(bus, address, 1 );
if (status != B_OK) {
TRACE("i2c_read: error on i2c_start_address: %s\n", strerror(status));
return status;
}
bytes = data;
while (size > 0) {
if (i2c_readbyte(bus, bytes) != B_OK) {
TRACE("i2c_read: timeout error on byte %ld\n", bytes - (uint8 *)data);
return B_TIMED_OUT;
}
if (size > 0)
set_sda_low(bus);
else
set_sda_high(bus);
if (set_scl_high(bus) != B_OK) {
set_sda_high(bus);
TRACE("i2c_read: timeout at ack\n");
return B_TIMED_OUT;
}
set_scl_low(bus);
set_sda_high(bus);
size--;
bytes++;
}
i2c_stop(bus);
return B_OK;
}
status_t
i2c_write_unlocked(i2c_bus *bus, int address, const void *data, int size)
{
const uint8 *bytes;
status_t status;
int ack;
if (size <= 0)
return B_BAD_VALUE;
status = i2c_start_address(bus, address, 0 );
if (status != B_OK) {
TRACE("i2c_write: error on i2c_start_address: %s\n", strerror(status));
return status;
}
bytes = data;
while (size > 0) {
status = i2c_writebyte(bus, *bytes, &ack);
if (status == B_TIMED_OUT) {
TRACE("i2c_write: timeout error on byte %ld\n", bytes - (uint8 *)data);
return B_TIMED_OUT;
}
if (status != B_OK) {
TRACE("i2c_write: arbitration lost on byte %ld\n", bytes - (uint8 *)data);
break;
}
if (!ack) {
TRACE("i2c_write: error, got NACK on byte %ld\n", bytes - (uint8 *)data);
break;
}
bytes++;
size--;
}
i2c_stop(bus);
if (status != B_OK)
return status;
return ack ? B_OK : B_ERROR;
}
status_t
i2c_read(i2c_bus *bus, int address, void *data, int size)
{
status_t res;
acquire_sem(bus->sem);
res = i2c_read_unlocked(bus, address, data, size);
release_sem(bus->sem);
return res;
}
status_t
i2c_write(i2c_bus *bus, int address, const void *data, int size)
{
status_t res;
acquire_sem(bus->sem);
res = i2c_write_unlocked(bus, address, data, size);
release_sem(bus->sem);
return res;
}
status_t
i2c_xfer(i2c_bus *bus, int address,
const void *write_data, int write_size,
void *read_data, int read_size)
{
status_t res;
acquire_sem(bus->sem);
if (write_data != 0 && write_size != 0) {
res = i2c_write_unlocked(bus, address, write_data, write_size);
if (res != B_OK) {
release_sem(bus->sem);
return res;
}
}
if (read_data != 0 && read_size != 0) {
res = i2c_read_unlocked(bus, address, read_data, read_size);
if (res != B_OK) {
release_sem(bus->sem);
return res;
}
}
release_sem(bus->sem);
return B_OK;
}