diff options
Diffstat (limited to 'drivers/i2c/i2c_bitbang.c')
-rw-r--r-- | drivers/i2c/i2c_bitbang.c | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/drivers/i2c/i2c_bitbang.c b/drivers/i2c/i2c_bitbang.c new file mode 100644 index 000000000..a06cc3b13 --- /dev/null +++ b/drivers/i2c/i2c_bitbang.c @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2017 Linaro Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Software driven 'bit-banging' library for I2C + * + * This code implements the I2C single master protocol in software by directly + * manipulating the levels of the SCL and SDA lines of an I2C bus. It supports + * the Standard-mode and Fast-mode speeds and doesn't support optional + * protocol feature like 10-bit addresses or clock stretching. + * + * Timings and protocol are based Rev. 6 of the I2C specification: + * http://www.nxp.com/documents/user_manual/UM10204.pdf + */ + +#include <errno.h> +#include <i2c.h> +#include <i2c_bitbang.h> +#include <kernel.h> + +/* + * Indexes into delay table for each part of I2C timing waveform we are + * interested in. In practice, for Standard and Fast modes, there are only two + * different numerical values (T_LOW and T_HIGH) so we alias the others to + * these. (Actually, we're simplifying a little, T_SU_STA could be T_HIGH on + * Fast mode) + */ +#define T_LOW 0 +#define T_HIGH 1 +#define T_SU_STA T_LOW +#define T_HD_STA T_HIGH +#define T_SU_STP T_HIGH +#define T_BUF T_LOW + +#define NS_TO_SYS_CLOCK_HW_CYCLES(ns) \ + ((u64_t)CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC * (ns) / NSEC_PER_SEC + 1) + +static const u32_t delays_fast[] = { + [T_LOW] = NS_TO_SYS_CLOCK_HW_CYCLES(1300), + [T_HIGH] = NS_TO_SYS_CLOCK_HW_CYCLES(600), +}; + +static const u32_t delays_standard[] = { + [T_LOW] = NS_TO_SYS_CLOCK_HW_CYCLES(4700), + [T_HIGH] = NS_TO_SYS_CLOCK_HW_CYCLES(4000), +}; + +int i2c_bitbang_configure(struct i2c_bitbang *context, u32_t dev_config) +{ + union dev_config config = { .raw = dev_config }; + + /* Check for features we don't support */ + if (config.bits.is_slave_read) { + return -ENOTSUP; + } + if (config.bits.use_10_bit_addr) { + return -ENOTSUP; + } + + /* Setup speed to use */ + switch (config.bits.speed) { + case I2C_SPEED_STANDARD: + context->delays = delays_standard; + break; + case I2C_SPEED_FAST: + context->delays = delays_fast; + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static void i2c_set_scl(struct i2c_bitbang *context, int state) +{ + context->io->set_scl(context->io_context, state); +} + +static void i2c_set_sda(struct i2c_bitbang *context, int state) +{ + context->io->set_sda(context->io_context, state); +} + +static int i2c_get_sda(struct i2c_bitbang *context) +{ + return context->io->get_sda(context->io_context); +} + +static void i2c_delay(unsigned int cycles_to_wait) +{ + u32_t start = k_cycle_get_32(); + + /* Wait until the given number of cycles have passed */ + while (k_cycle_get_32() - start < cycles_to_wait) { + } +} + +static void i2c_start(struct i2c_bitbang *context) +{ + if (!i2c_get_sda(context)) { + /* + * SDA is already low, so we need to do something to make it + * high. Try pulsing clock low to get slave to release SDA. + */ + i2c_set_scl(context, 0); + i2c_delay(context->delays[T_LOW]); + i2c_set_scl(context, 1); + i2c_delay(context->delays[T_SU_STA]); + } + i2c_set_sda(context, 0); + i2c_delay(context->delays[T_HD_STA]); +} + +static void i2c_repeated_start(struct i2c_bitbang *context) +{ + i2c_delay(context->delays[T_SU_STA]); + i2c_start(context); +} + +static void i2c_stop(struct i2c_bitbang *context) +{ + if (i2c_get_sda(context)) { + /* + * SDA is already high, so we need to make it low so that + * we can create a rising edge. This means we're effectively + * doing a START. + */ + i2c_delay(context->delays[T_SU_STA]); + i2c_set_sda(context, 0); + i2c_delay(context->delays[T_HD_STA]); + } + i2c_delay(context->delays[T_SU_STP]); + i2c_set_sda(context, 1); + i2c_delay(context->delays[T_BUF]); /* In case we start again too soon */ +} + +static void i2c_write_bit(struct i2c_bitbang *context, int bit) +{ + i2c_set_scl(context, 0); + /* SDA hold time is zero, so no need for a delay here */ + i2c_set_sda(context, bit); + i2c_delay(context->delays[T_LOW]); + i2c_set_scl(context, 1); + i2c_delay(context->delays[T_HIGH]); +} + +static bool i2c_read_bit(struct i2c_bitbang *context) +{ + bool bit; + + i2c_set_scl(context, 0); + /* SDA hold time is zero, so no need for a delay here */ + i2c_set_sda(context, 1); /* Stop driving low, so slave has control */ + i2c_delay(context->delays[T_LOW]); + bit = i2c_get_sda(context); + i2c_set_scl(context, 1); + i2c_delay(context->delays[T_HIGH]); + return bit; +} + +static bool i2c_write_byte(struct i2c_bitbang *context, u8_t byte) +{ + u8_t mask = 1 << 7; + + do { + i2c_write_bit(context, byte & mask); + } while (mask >>= 1); + + /* Return inverted ACK bit, i.e. 'true' for ACK, 'false' for NACK */ + return !i2c_read_bit(context); +} + +static u8_t i2c_read_byte(struct i2c_bitbang *context) +{ + unsigned int byte = 1; + + do { + byte <<= 1; + byte |= i2c_read_bit(context); + } while (!(byte & (1 << 8))); + + return byte; +} + +int i2c_bitbang_transfer(struct i2c_bitbang *context, + struct i2c_msg *msgs, u8_t num_msgs, + u16_t slave_address) +{ + u8_t *buf, *buf_end; + unsigned int flags; + int result = -EIO; + + if (!num_msgs) { + return 0; + } + + /* We want an initial Start condition */ + flags = I2C_MSG_RESTART; + + /* Make sure we're in a good state so slave recognises the Start */ + i2c_set_scl(context, 1); + flags |= I2C_MSG_STOP; + + do { + /* Stop flag from previous message? */ + if (flags & I2C_MSG_STOP) { + i2c_stop(context); + } + + /* Forget old flags except start flag */ + flags &= I2C_MSG_RESTART; + + /* Start condition? */ + if (flags & I2C_MSG_RESTART) { + i2c_start(context); + } else if (msgs->flags & I2C_MSG_RESTART) { + i2c_repeated_start(context); + } + + /* Get flags for new message */ + flags |= msgs->flags; + + /* Send address after any Start condition */ + if (flags & I2C_MSG_RESTART) { + unsigned int byte0 = slave_address << 1; + + byte0 |= (flags & I2C_MSG_RW_MASK) == I2C_MSG_READ; + if (!i2c_write_byte(context, byte0)) { + goto finish; /* No ACK received */ + } + flags &= ~I2C_MSG_RESTART; + } + + /* Transfer data */ + buf = msgs->buf; + buf_end = buf + msgs->len; + if ((flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) { + /* Read */ + while (buf < buf_end) { + *buf++ = i2c_read_byte(context); + /* ACK the byte, except for the last one */ + i2c_write_bit(context, buf == buf_end); + } + } else { + /* Write */ + while (buf < buf_end) { + if (!i2c_write_byte(context, *buf++)) { + goto finish; /* No ACK received */ + } + } + } + + /* Next message */ + msgs++; + num_msgs--; + } while (num_msgs); + + /* Complete without error */ + result = 0; +finish: + i2c_stop(context); + + return result; +} + +void i2c_bitbang_init(struct i2c_bitbang *context, + const struct i2c_bitbang_io *io, void *io_context) +{ + union dev_config dev_config = { .bits.speed = I2C_SPEED_STANDARD }; + + context->io = io; + context->io_context = io_context; + i2c_bitbang_configure(context, dev_config.raw); +} |