190 lines
4.1 KiB
C
190 lines
4.1 KiB
C
/* 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;
|
|
}
|