/* 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 * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #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; }