From 0fa34a0b64e80ef2d4d282dcda8923881f11beb4 Mon Sep 17 00:00:00 2001 From: Matias N Date: Tue, 19 Jan 2021 00:05:49 -0300 Subject: [PATCH] drivers: add generic i2c bitbang driver --- drivers/i2c/Kconfig | 41 ++++ drivers/i2c/Make.defs | 4 + drivers/i2c/i2c_bitbang.c | 389 ++++++++++++++++++++++++++++++++ include/nuttx/i2c/i2c_bitbang.h | 82 +++++++ 4 files changed, 516 insertions(+) create mode 100644 drivers/i2c/i2c_bitbang.c create mode 100644 include/nuttx/i2c/i2c_bitbang.h diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 1de8b7f0d2e95..c656f38419a35 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -38,6 +38,47 @@ config I2C_NTRACE default 32 depends on I2C_TRACE +config I2C_BITBANG + bool "I2C bitbang implementation" + default n + ---help--- + Enable support for a bitbang implementation of I2C + +if I2C_BITBANG + + config I2C_BITBANG_NO_DELAY + bool "Do not add delay" + default n + ---help--- + If you want to go full speed (depending on how fast pins can be toggled) + you can enable this option. This will not respect the desired frequency + set during the I2C transfer operation. + + config I2C_BITBANG_GPIO_OVERHEAD + int "GPIO overhead" + depends on !I2C_BITBANG_NO_DELAY + default 0 + ---help--- + Overhead of GPIO toggling operation to consider when computing + delays. This overhead will be subtracted from sleep times to achieve + desired frquency. + + config I2C_BITBANG_TIMEOUT + int "I2C timeout" + default 1000 + ---help--- + Timeout (microseconds) to abort wait on slave + + config I2C_BITBANG_CLOCK_STRETCHING + bool "Support clock stretching" + default n + ---help--- + This enables I2C clock stretching. This requires the hardware to set + the pin into open-collector mode (master sets SCL high and waits until + slave stops holding it low). + +endif # I2C_BITBANG + config I2C_DRIVER bool "I2C character driver" default n diff --git a/drivers/i2c/Make.defs b/drivers/i2c/Make.defs index 5aeefc8bb5538..b0c77b077bf7c 100644 --- a/drivers/i2c/Make.defs +++ b/drivers/i2c/Make.defs @@ -44,6 +44,10 @@ ifeq ($(CONFIG_I2C_DRIVER),y) CSRCS += i2c_driver.c endif +ifeq ($(CONFIG_I2C_BITBANG),y) +CSRCS += i2c_bitbang.c +endif + # Include the selected I2C multiplexer drivers ifeq ($(CONFIG_I2CMULTIPLEXER_PCA9540BDP),y) diff --git a/drivers/i2c/i2c_bitbang.c b/drivers/i2c/i2c_bitbang.c new file mode 100644 index 0000000000000..65e45421917b6 --- /dev/null +++ b/drivers/i2c/i2c_bitbang.c @@ -0,0 +1,389 @@ +/**************************************************************************** + * drivers/i2c/i2c_bitbang.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct i2c_bitbang_dev_s +{ + struct i2c_master_s i2c; + struct i2c_bitbang_lower_dev_s *lower; + +#ifndef CONFIG_I2C_BITBANG_NO_DELAY + int32_t delay; +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int i2c_bitbang_transfer(FAR struct i2c_master_s *dev, + FAR struct i2c_msg_s *msgs, int count); + +static int i2c_bitbang_set_scl(FAR struct i2c_bitbang_dev_s *dev, + bool high, bool nodelay); +static void i2c_bitbang_set_sda(FAR struct i2c_bitbang_dev_s *dev, + bool high); + +static int i2c_bitbang_wait_ack(FAR struct i2c_bitbang_dev_s *dev); +static void i2c_bitbang_send(FAR struct i2c_bitbang_dev_s *dev, + uint8_t data); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct i2c_ops_s g_i2c_ops = +{ + .transfer = i2c_bitbang_transfer +}; + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +inline static bool i2c_bitbang_get_sda(FAR struct i2c_bitbang_dev_s *dev) +{ + return dev->lower->ops->get_sda(dev->lower); +} + +#ifdef CONFIG_I2C_BITBANG_CLOCK_STRETCHING +inline static bool i2c_bitbang_get_scl(FAR struct i2c_bitbang_dev_s *dev) +{ + return dev->lower->ops->get_scl(dev->lower); +} +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_bitbang_transfer + ****************************************************************************/ + +static int i2c_bitbang_transfer(FAR struct i2c_master_s *dev, + FAR struct i2c_msg_s *msgs, int count) +{ + FAR struct i2c_bitbang_dev_s *priv = (FAR struct i2c_bitbang_dev_s *)dev; + int ret = OK; + int i; + irqstate_t flags; + + /* Lock to enforce timings */ + + flags = spin_lock_irqsave(); + + for (i = 0; i < count; i++) + { + uint8_t addr; + FAR struct i2c_msg_s *msg = &msgs[i]; + +#ifndef CONFIG_I2C_BITBANG_NO_DELAY + /* Compute delay from frequency */ + + priv->delay = (USEC_PER_SEC / (2 * msg->frequency)) - + CONFIG_I2C_BITBANG_GPIO_OVERHEAD; + + if (priv->delay < 0) + { + priv->delay = 0; + } +#endif + + /* If this is the start of transfer or we're changing sending direction + * from last transfer, send START + */ + + if (i == 0 || + (msgs[i - 1].flags & I2C_M_READ) != (msgs[i].flags & I2C_M_READ)) + { + /* Send start bit */ + + i2c_bitbang_set_scl(priv, true, false); + i2c_bitbang_set_sda(priv, false); + i2c_bitbang_set_scl(priv, false, false); + + /* Send the address */ + + addr = (msg->flags & I2C_M_READ ? I2C_READADDR8(msg->addr) : + I2C_WRITEADDR8(msg->addr)); + + i2c_bitbang_send(priv, addr); + + /* Wait for ACK */ + + ret = i2c_bitbang_wait_ack(priv); + + if (ret < 0) + { + goto out; + } + } + + i2c_bitbang_set_scl(priv, false, false); + + if (msg->flags & I2C_M_READ) + { + int j; + int k; + + for (j = 0; j < msg->length; j++) + { + uint8_t data = 0; + + i2c_bitbang_set_sda(priv, true); + + msg->buffer[j] = 0; + + for (k = 0; k < 8; k++) + { + i2c_bitbang_set_scl(priv, true, false); + data |= (i2c_bitbang_get_sda(priv) & 1) << (7 - k); + i2c_bitbang_set_scl(priv, false, false); + } + + msg->buffer[j] = data; + + if (j < msg->length - 1) + { + /* Send ACK */ + + i2c_bitbang_set_sda(priv, false); + i2c_bitbang_set_scl(priv, true, false); + i2c_bitbang_set_scl(priv, false, false); + } + else + { + /* On the last byte send NAK */ + + i2c_bitbang_set_sda(priv, true); + i2c_bitbang_set_scl(priv, true, false); + i2c_bitbang_set_scl(priv, false, false); + } + } + } + else + { + int j; + + for (j = 0; j < msg->length; j++) + { + /* Send the data */ + + i2c_bitbang_send(priv, msg->buffer[j]); + + ret = i2c_bitbang_wait_ack(priv); + + if (ret < 0) + { + goto out; + } + + i2c_bitbang_set_scl(priv, false, false); + } + } + + if (!(msg->flags & I2C_M_NOSTOP)) + { + /* Send stop */ + + i2c_bitbang_set_sda(priv, false); + i2c_bitbang_set_scl(priv, true, true); + i2c_bitbang_set_sda(priv, true); + } + } + +out: + + /* Ensure lines are released */ + + i2c_bitbang_set_scl(priv, true, false); + i2c_bitbang_set_sda(priv, true); + + spin_unlock_irqrestore(flags); + + return ret; +} + +/**************************************************************************** + * Name: i2c_bitbang_wait_ack + ****************************************************************************/ + +static int i2c_bitbang_wait_ack(FAR struct i2c_bitbang_dev_s *priv) +{ + int ret = OK; + int i; + + /* Wait for ACK */ + + i2c_bitbang_set_sda(priv, true); + i2c_bitbang_set_scl(priv, true, true); + + for (i = 0; i2c_bitbang_get_sda(priv) && + i < CONFIG_I2C_BITBANG_TIMEOUT; i++) + { + up_udelay(1); + } + + if (i == CONFIG_I2C_BITBANG_TIMEOUT) + { + ret = -EIO; + } + else + { + int remaining = priv->delay - i; + + if (remaining > 0) + { + up_udelay(remaining); + } + } + + return ret; +} + +/**************************************************************************** + * Name: i2c_bitbang_send + ****************************************************************************/ + +static void i2c_bitbang_send(FAR struct i2c_bitbang_dev_s *priv, + uint8_t data) +{ + uint8_t bit = 0b10000000; + + while (bit) + { + i2c_bitbang_set_sda(priv, !!(data & bit)); + i2c_bitbang_set_scl(priv, true, false); + i2c_bitbang_set_scl(priv, false, false); + + bit >>= 1; + } +} + +/**************************************************************************** + * Name: i2c_bitbang_set_sda + ****************************************************************************/ + +static void i2c_bitbang_set_sda(FAR struct i2c_bitbang_dev_s *dev, bool high) +{ + dev->lower->ops->set_sda(dev->lower, high); +} + +/**************************************************************************** + * Name: i2c_bitbang_set_scl + ****************************************************************************/ + +static int i2c_bitbang_set_scl(FAR struct i2c_bitbang_dev_s *dev, bool high, + bool nodelay) +{ + dev->lower->ops->set_scl(dev->lower, high); + +#ifndef CONFIG_I2C_BITBANG_NO_DELAY + if (!nodelay && dev->delay) + { + up_udelay(dev->delay); + } +#endif + +#ifdef CONFIG_I2C_BITBANG_CLOCK_STRETCHING + /* Allow for clock stretching */ + + if (high) + { + int i; + + for (i = 0; !i2c_bitbang_get_scl(dev) && + i < CONFIG_I2C_BITBANG_TIMEOUT; i++) + { + up_udelay(1); + + if (i == CONFIG_I2C_BITBANG_TIMEOUT) + { + return -ETIMEDOUT; + } + } + } +#endif + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_bitbang_initialize + * + * Description: + * Initialize a bitbang I2C device instance + * + * Input Parameters: + * lower - Lower half of driver + * + * Returned Value: + * Pointer to a the I2C instance + * + ****************************************************************************/ + +FAR struct i2c_master_s *i2c_bitbang_initialize( + FAR struct i2c_bitbang_lower_dev_s *lower) +{ + FAR struct i2c_bitbang_dev_s *dev; + + DEBUGASSERT(lower && lower->ops); + + dev = (FAR struct i2c_bitbang_dev_s *)kmm_zalloc(sizeof(*dev)); + + if (!dev) + { + return NULL; + } + + dev->i2c.ops = &g_i2c_ops; + dev->lower = lower; + dev->lower->ops->initialize(dev->lower); + + return &dev->i2c; +} + diff --git a/include/nuttx/i2c/i2c_bitbang.h b/include/nuttx/i2c/i2c_bitbang.h new file mode 100644 index 0000000000000..3db29f2b20598 --- /dev/null +++ b/include/nuttx/i2c/i2c_bitbang.h @@ -0,0 +1,82 @@ +/**************************************************************************** + * drivers/i2c/i2c_bitbang.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __DRIVERS_I2C_I2C_BITBANG_H +#define __DRIVERS_I2C_I2C_BITBANG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct i2c_bitbang_lower_dev_s; + +struct i2c_bitbang_lower_ops_s +{ + /* Initialize pins to appropriate state (usually open-drain) */ + + CODE void (*initialize)(FAR struct i2c_bitbang_lower_dev_s *lower); + + /* Set high/low level for SCL/SDA pins */ + + CODE void (*set_scl)(FAR struct i2c_bitbang_lower_dev_s *lower, bool high); + CODE void (*set_sda)(FAR struct i2c_bitbang_lower_dev_s *lower, bool high); + + /* Read level of SCL/SDA pins */ + + CODE bool (*get_scl)(FAR struct i2c_bitbang_lower_dev_s *lower); + CODE bool (*get_sda)(FAR struct i2c_bitbang_lower_dev_s *lower); +}; + +struct i2c_bitbang_lower_dev_s +{ + FAR const struct i2c_bitbang_lower_ops_s *ops; + FAR void *priv; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_bitbang_initialize + * + * Description: + * Initialize a bitbang I2C device instance + * + * Input Parameters: + * lower - Lower half of driver + * + * Returned Value: + * Pointer to a the I2C instance + * + ****************************************************************************/ + +FAR struct i2c_master_s *i2c_bitbang_initialize( + FAR struct i2c_bitbang_lower_dev_s *lower); + +#endif /* __DRIVERS_I2C_I2C_BITBANG_H */