blob: b1a9a627381282a056157206e2598110fcaa2676 [file] [log] [blame]
/* Bit-banging I2C layer, inspired to a large extent from Linux kernel
* i2c-algo-bit.c code (C) 1995-2000 Simon G. Vogl. This particular
* implementation is (C) 2019 by Harald Welte <laforge@gnumonks.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <hal_gpio.h>
#include <hal_delay.h>
#include <err_codes.h>
#include "i2c_bitbang.h"
#define setsda(adap, val) gpio_set_pin_level((adap)->pin_sda, val)
#define setscl(adap, val) gpio_set_pin_level((adap)->pin_scl, val)
static int getsda(const struct i2c_adapter *adap)
{
int rc;
gpio_set_pin_direction(adap->pin_sda, GPIO_DIRECTION_IN);
rc = gpio_get_pin_level(adap->pin_sda);
gpio_set_pin_direction(adap->pin_sda, GPIO_DIRECTION_OUT);
return rc;
}
static int getscl(const struct i2c_adapter *adap)
{
int rc;
gpio_set_pin_direction(adap->pin_scl, GPIO_DIRECTION_IN);
rc = gpio_get_pin_level(adap->pin_scl);
gpio_set_pin_direction(adap->pin_scl, GPIO_DIRECTION_OUT);
return rc;
}
static inline void sdalo(const struct i2c_adapter *adap)
{
setsda(adap, 0);
delay_us((adap->udelay+1) / 2);
}
static inline void sdahi(const struct i2c_adapter *adap)
{
setsda(adap, 1);
delay_us((adap->udelay+1) / 2);
}
static inline void scllo(const struct i2c_adapter *adap)
{
setscl(adap, 0);
delay_us(adap->udelay / 2);
}
static int sclhi(const struct i2c_adapter *adap)
{
setscl(adap, 1);
/* wait for slow slaves' clock stretching to complete */
while (!getscl(adap)) {
/* FIXME: abort at some point */
}
return 0;
}
static void i2c_start(const struct i2c_adapter *adap)
{
/* Assert: SCL + SDA are high */
setsda(adap, 0); /* set SDA to low */
delay_us(adap->udelay); /* delay */
scllo(adap); /* Set SCL to low */
}
static void i2c_repstart(const struct i2c_adapter *adap)
{
/* Assert: SCL is low */
sdahi(adap);
sclhi(adap);
setsda(adap, 0);
delay_us(adap->udelay);
scllo(adap);
}
static void i2c_stop(const struct i2c_adapter *adap)
{
/* Assert: SCL is low */
sdalo(adap); /* set SDA low */
sclhi(adap); /* set SCL to high */
setsda(adap, 1); /* set SDA to high */
delay_us(adap->udelay); /* delay */
}
static int i2c_outb(const struct i2c_adapter *adap, uint8_t outdata)
{
uint8_t sb;
int ack, i;
/* Assert: SCL is low */
for (i = 7; i >= 0; i--) {
sb = (outdata >> i) & 1;
setsda(adap, sb);
delay_us((adap->udelay + 1) / 2);
if (sclhi(adap) < 0)
return -ERR_TIMEOUT;
scllo(adap);
}
sdahi(adap);
if (sclhi(adap) < 0)
return -ERR_TIMEOUT;
ack = !getsda(adap);
scllo(adap);
return ack;
}
static int i2c_inb(const struct i2c_adapter *adap)
{
uint8_t indata = 0;
int i;
/* Assert: CSL is low */
sdahi(adap);
for (i = 0; i < 8; i++) {
/* SCL high */
if (sclhi(adap) < 0)
return -ERR_TIMEOUT;
indata = indata << 1;
if (getsda(adap))
indata |= 0x01;
setscl(adap, 0);
if (i == 7)
delay_us(adap->udelay / 2);
else
delay_us(adap->udelay);
}
/* Assert: SCL is low */
return indata;
}
/*! Single-byte register write. Assumes 8bit register address + 8bit values */
int i2c_write_reg(const struct i2c_adapter *adap, uint8_t addr, uint8_t reg, uint8_t val)
{
int rc;
i2c_start(adap);
rc = i2c_outb(adap, addr << 1);
if (rc < 0)
goto out_stop;
rc = i2c_outb(adap, reg);
if (rc < 0)
goto out_stop;
rc = i2c_outb(adap, val);
out_stop:
i2c_stop(adap);
return rc;
}
/*! Single-byte register read. Assumes 8bit register address + 8bit values */
int i2c_read_reg(const struct i2c_adapter *adap, uint8_t addr, uint8_t reg)
{
int rc;
i2c_start(adap);
rc = i2c_outb(adap, addr << 1);
if (rc < 0)
goto out_stop;
rc = i2c_outb(adap, reg);
if (rc < 0)
goto out_stop;
i2c_repstart(adap);
rc = i2c_outb(adap, addr << 1 | 1);
if (rc < 0)
goto out_stop;
rc = i2c_inb(adap);
out_stop:
i2c_stop(adap);
return rc;
}
/*! Initialize a given I2C adapter/bus */
int i2c_init(const struct i2c_adapter *adap)
{
gpio_set_pin_direction(adap->pin_sda, GPIO_DIRECTION_OUT);
gpio_set_pin_direction(adap->pin_scl, GPIO_DIRECTION_OUT);
/* Bring bus to a known state. Looks like STOP if bus is not free yet */
setscl(adap, 1);
delay_us(adap->udelay);
setsda(adap, 1);
return 0;
}