summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Medhurst <tixy@linaro.org>2016-08-25 14:06:32 +0100
committerKumar Gala <kumar.gala@linaro.org>2017-04-28 15:26:39 -0500
commit08185647530088db145be37e6ab1d9e51ce6dd21 (patch)
tree36ac2146e1eb1cf0939e3cdb0b4535140e7b1902
parent392803e4ccf581a68b7536d78e013fc8f7b764fb (diff)
i2c: bitbang: Add library for software driven I2C
This library implements the I2C single master protocol in software. It supports the Standard-mode and Fast-mode speeds and doesn't support optional protocol feature like 10-bit addresses or clock stretching. Change-Id: I375d572a83714522421f2967dc414b3bec169e95 Signed-off-by: Jon Medhurst <tixy@linaro.org>
-rw-r--r--drivers/i2c/Kconfig7
-rw-r--r--drivers/i2c/Makefile1
-rw-r--r--drivers/i2c/i2c_bitbang.c279
-rw-r--r--drivers/i2c/i2c_bitbang.h57
4 files changed, 344 insertions, 0 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 0c7589737..5968e936a 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -106,6 +106,13 @@ config I2C_STM32LX_INTERRUPT
help
Enable Interrupt support for the I2C Driver of STM32Lxx family.
+config I2C_BITBANG
+ bool
+ default n
+ help
+ Enable library used for software driven (bit banging) I2C support
+
+
config I2C_INIT_PRIORITY
int
depends on I2C
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index ee8d5da1e..f500583a7 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_I2C_ATMEL_SAM3) += i2c_atmel_sam3.o
+obj-$(CONFIG_I2C_BITBANG) += i2c_bitbang.o
obj-$(CONFIG_I2C_DW) += i2c_dw.o
obj-$(CONFIG_I2C_MCUX) += i2c_mcux.o
obj-$(CONFIG_I2C_NRF5) += i2c_nrf5.o
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);
+}
diff --git a/drivers/i2c/i2c_bitbang.h b/drivers/i2c/i2c_bitbang.h
new file mode 100644
index 000000000..970b69149
--- /dev/null
+++ b/drivers/i2c/i2c_bitbang.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2017 Linaro Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @brief Functions for setting and getting the state of the I2C lines.
+ *
+ * These need to be implemented by the user of this library.
+ */
+struct i2c_bitbang_io {
+ /* Set the state of the SCL line (zero/non-zero value) */
+ void (*set_scl)(void *io_context, int state);
+ /* Set the state of the SDA line (zero/non-zero value) */
+ void (*set_sda)(void *io_context, int state);
+ /* Return the state of the SDA line (zero/non-zero value) */
+ int (*get_sda)(void *io_context);
+};
+
+/**
+ * @brief Instance data for i2c_bitbang
+ *
+ * A driver or other code wishing to use the i2c_bitbang library should
+ * create one of these structures then use it via the library APIs.
+ * Structure members are private, and shouldn't be accessed directly.
+ */
+struct i2c_bitbang {
+ const struct i2c_bitbang_io *io;
+ void *io_context;
+ const u32_t *delays;
+};
+
+/**
+ * @brief Initialise an i2c_bitbang instance
+ *
+ * @param bitbang The instance to initialise
+ * @param io Functions to use for controlling I2C bus lines
+ * @param io_context Context pointer to pass to i/o functions when then are
+ * called.
+ */
+void i2c_bitbang_init(struct i2c_bitbang *bitbang,
+ const struct i2c_bitbang_io *io, void *io_context);
+
+/**
+ * Implementation of the functionality required by the 'configure' function
+ * in struct i2c_driver_api.
+ */
+int i2c_bitbang_configure(struct i2c_bitbang *bitbang, u32_t dev_config);
+
+/**
+ * Implementation of the functionality required by the 'transfer' function
+ * in struct i2c_driver_api.
+ */
+int i2c_bitbang_transfer(struct i2c_bitbang *bitbang,
+ struct i2c_msg *msgs, u8_t num_msgs,
+ u16_t slave_address);