drivers: add generic i2c bitbang driver

This commit is contained in:
Matias N 2021-01-19 00:05:49 -03:00 committed by Brennan Ashton
parent 56ef94086f
commit 0fa34a0b64
4 changed files with 516 additions and 0 deletions

View file

@ -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

View file

@ -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)

389
drivers/i2c/i2c_bitbang.c Normal file
View file

@ -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 <nuttx/config.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/irq.h>
#include <nuttx/semaphore.h>
#include <nuttx/kmalloc.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/i2c/i2c_bitbang.h>
/****************************************************************************
* 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;
}

View file

@ -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 <nuttx/config.h>
#include <nuttx/i2c/i2c_master.h>
#include <stdbool.h>
/****************************************************************************
* 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 */