diff --git a/Documentation/components/drivers/special/i2c.rst b/Documentation/components/drivers/special/i2c.rst index 0215e0f1d2449..ae76b864448a9 100644 --- a/Documentation/components/drivers/special/i2c.rst +++ b/Documentation/components/drivers/special/i2c.rst @@ -23,6 +23,138 @@ I2C Device Drivers ``arch/z80/src/z8/z8_i2c.c``, etc. +======================= +I2C Bit-Bang Driver +======================= + +The I2C bit-bang driver provides a software implementation of the I2C +protocol using GPIO pins. This is useful when hardware I2C peripherals +are not available or when additional I2C buses are needed. + +Overview +-------- + +- ``include/nuttx/i2c/i2c_bitbang.h`` + Generic upper-half I2C bit-bang driver interface. + +- ``drivers/i2c/i2c_bitbang.c`` + Generic upper-half implementation that handles the I2C protocol timing. + +- Platform-specific lower-half drivers control the GPIO pins (SDA and SCL). + +IO Expander-Based I2C Bit-Bang +------------------------------- + +A generic lower-half implementation is provided for systems using IO expanders +to control GPIO pins. This eliminates the need for platform-specific code when +implementing I2C bit-bang via IO expander pins. + +Configuration +~~~~~~~~~~~~~ + +- ``CONFIG_I2C_BITBANG`` - Enable I2C bit-bang driver framework +- ``CONFIG_I2C_BITBANG_IOEXPANDER`` - Enable IO expander-based lower-half + (depends on ``CONFIG_IOEXPANDER``) + +Header Files +~~~~~~~~~~~~ + +- ``include/nuttx/i2c/i2c_bitbang_ioexpander.h`` + IO expander-based lower-half driver interface. + +API +~~~ + +.. c:function:: FAR struct i2c_master_s *i2c_bitbang_ioexpander_initialize(FAR struct ioexpander_dev_s *ioe, int scl_pin, int sda_pin, int busnum); + + Initialize an I2C bit-bang driver using IO expander pins. + + :param ioe: Pointer to the IO expander device + :param scl_pin: IO expander pin number for SCL (clock line) + :param sda_pin: IO expander pin number for SDA (data line) + :param busnum: I2C bus number to register (use negative value to skip registration) + + :return: Pointer to ``struct i2c_master_s`` on success, NULL on failure + + The pins will be configured as open-drain outputs, which is required + for proper I2C operation. If busnum >= 0, the I2C bus is automatically + registered and accessible via standard I2C APIs. + +Usage Example +~~~~~~~~~~~~~ + +.. code-block:: c + + #include + #include + #include + + /* Assume we have an IO expander device */ + FAR struct ioexpander_dev_s *ioe = /* ... get IO expander ... */; + FAR struct i2c_master_s *i2c; + + /* Initialize I2C bit-bang using IO expander pins 10 (SCL) and 11 (SDA) */ + /* Register as I2C bus 0 */ + i2c = i2c_bitbang_ioexpander_initialize(ioe, 10, 11, 0); + if (i2c == NULL) + { + /* Initialization failed */ + return -1; + } + + /* Now use the I2C master device normally */ + /* For example, with I2C character driver or directly */ + + /* If registered (busnum >= 0), can also access via /dev/i2c0 */ + +Use Cases +~~~~~~~~~ + +- **GPIO Expansion**: When using I2C or SPI IO expanders for GPIO expansion, + and need to implement additional I2C buses using those expanded pins. + +- **Multi-Master Scenarios**: Software bit-bang can be useful in multi-master + configurations where hardware I2C has limitations. + +- **Pin Flexibility**: Implement I2C on any GPIO pins, not limited to + hardware I2C peripheral pins. + +- **Testing and Debugging**: Use IO expander pins for I2C communication + during prototyping and debugging. + +- **Hardware I2C Unavailable**: When hardware I2C peripherals are exhausted + or not available on specific pins. + +Features +~~~~~~~~ + +- Uses standard IO expander API (``IOEXP_WRITEPIN``, ``IOEXP_READPIN``) +- Automatic open-drain configuration +- Supports clock stretching (via pin reading) +- Platform-independent implementation +- Works with any IO expander that implements the standard interface +- Automatic I2C bus registration + +Limitations +~~~~~~~~~~~ + +- Software timing (slower than hardware I2C) +- Timing accuracy depends on system load and IO expander response time +- Limited to standard I2C speeds (fast-mode and high-speed may not be reliable) + +Implementation Details +~~~~~~~~~~~~~~~~~~~~~~ + +The IO expander-based implementation provides these callbacks: + +- ``initialize``: Configures SCL and SDA pins as open-drain outputs +- ``set_scl/set_sda``: Controls pin output values +- ``get_scl/get_sda``: Reads current pin states (for clock stretching detection) + +The driver automatically manages the IO expander pin states and handles +the bit-bang protocol timing according to I2C specifications. + + ======================== I2C Slave Device Drivers ======================== diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index 3b97aa78744eb..e882fae1ab874 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -29,6 +29,11 @@ if(CONFIG_I2C) if(CONFIG_I2C_BITBANG) list(APPEND SRCS i2c_bitbang.c) + + if(CONFIG_I2C_BITBANG_IOEXPANDER) + list(APPEND SRCS i2c_bitbang_ioexpander.c) + endif() + endif() if(CONFIG_I2C_SLAVE_DRIVER) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index dfa74af883c7b..a68f64c158471 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -112,6 +112,13 @@ config I2C_BITBANG_CLOCK_STRETCHING the pin into open-collector mode (master sets SCL high and waits until slave stops holding it low). +config I2C_BITBANG_IOEXPANDER + bool "Support the ioexpander lower half of i2c bitbang" + default n + depends on IOEXPANDER + ---help--- + Using ioexpander to control gpio to implement i2c bitbang function. + endif # I2C_BITBANG config I2C_DRIVER diff --git a/drivers/i2c/Make.defs b/drivers/i2c/Make.defs index 059dbd3cd2ef3..5d7e276493b3f 100644 --- a/drivers/i2c/Make.defs +++ b/drivers/i2c/Make.defs @@ -32,6 +32,11 @@ endif ifeq ($(CONFIG_I2C_BITBANG),y) CSRCS += i2c_bitbang.c + +ifeq ($(CONFIG_I2C_BITBANG_IOEXPANDER),y) +CSRCS += i2c_bitbang_ioexpander.c +endif + endif ifeq ($(CONFIG_I2C_SLAVE_DRIVER),y) diff --git a/drivers/i2c/i2c_bitbang_ioexpander.c b/drivers/i2c/i2c_bitbang_ioexpander.c new file mode 100644 index 0000000000000..b5fcf46a2f890 --- /dev/null +++ b/drivers/i2c/i2c_bitbang_ioexpander.c @@ -0,0 +1,276 @@ +/**************************************************************************** + * drivers/i2c/i2c_bitbang_ioexpander.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 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct i2c_bb_ioe_s +{ + struct i2c_bitbang_lower_dev_s lower; + FAR struct ioexpander_dev_s *ioe; + int sda_pin; + int scl_pin; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void i2c_bb_ioe_init(FAR struct i2c_bitbang_lower_dev_s *lower); +static void i2c_bb_ioe_set_scl(FAR struct i2c_bitbang_lower_dev_s *lower, + bool value); +static void i2c_bb_ioe_set_sda(FAR struct i2c_bitbang_lower_dev_s *lower, + bool value); +static bool i2c_bb_ioe_get_scl(FAR struct i2c_bitbang_lower_dev_s *lower); +static bool i2c_bb_ioe_get_sda(FAR struct i2c_bitbang_lower_dev_s *lower); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Lower-half I2C bitbang data */ + +const static struct i2c_bitbang_lower_ops_s g_i2c_bitbang_ops = +{ + .initialize = i2c_bb_ioe_init, + .set_scl = i2c_bb_ioe_set_scl, + .set_sda = i2c_bb_ioe_set_sda, + .get_scl = i2c_bb_ioe_get_scl, + .get_sda = i2c_bb_ioe_get_sda +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_bb_ioe_init + * + * Description: + * Initialize the I2C bit-bang driver + * + * Input Parameters: + * lower - A pointer the publicly visible representation of + * the "lower-half" driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void i2c_bb_ioe_init(FAR struct i2c_bitbang_lower_dev_s *lower) +{ + FAR struct i2c_bb_ioe_s *dev = lower->priv; + + IOEXP_WRITEPIN(dev->ioe, dev->scl_pin, 1); + IOEXP_WRITEPIN(dev->ioe, dev->sda_pin, 1); + + IOEXP_SETDIRECTION(dev->ioe, dev->scl_pin, + IOEXPANDER_DIRECTION_OUT_OPENDRAIN); + IOEXP_SETDIRECTION(dev->ioe, dev->sda_pin, + IOEXPANDER_DIRECTION_OUT_OPENDRAIN); +} + +/**************************************************************************** + * Name: i2c_bb_ioe_set_scl + * + * Description: + * Set SCL line value + * + * Input Parameters: + * lower - A pointer the publicly visible representation of + * the "lower-half" driver state structure. + * value - The value to be written (0 or 1). + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void i2c_bb_ioe_set_scl(FAR struct i2c_bitbang_lower_dev_s *lower, + bool value) +{ + FAR struct i2c_bb_ioe_s *dev = lower->priv; + + IOEXP_WRITEPIN(dev->ioe, dev->scl_pin, value); +} + +/**************************************************************************** + * Name: i2c_bb_ioe_set_sda + * + * Description: + * Set SDA line value + * + * Input Parameters: + * lower - A pointer the publicly visible representation of + * the "lower-half" driver state structure. + * value - The value to be written (0 or 1). + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void i2c_bb_ioe_set_sda(FAR struct i2c_bitbang_lower_dev_s *lower, + bool value) +{ + FAR struct i2c_bb_ioe_s *dev = lower->priv; + + IOEXP_WRITEPIN(dev->ioe, dev->sda_pin, value); +} + +/**************************************************************************** + * Name: i2c_bb_ioe_get_scl + * + * Description: + * Get value from SCL line + * + * Input Parameters: + * lower - A pointer the publicly visible representation of + * the "lower-half" driver state structure. + * + * Returned Value: + * The boolean representation of the SCL line value (true/false). + * + ****************************************************************************/ + +static bool i2c_bb_ioe_get_scl(FAR struct i2c_bitbang_lower_dev_s *lower) +{ + FAR struct i2c_bb_ioe_s *dev = lower->priv; + bool value = true; + int ret; + + ret = IOEXP_READPIN(dev->ioe, dev->scl_pin, &value); + if (ret < 0) + { + i2cerr("read scl pin:%d, error res:%d\n", dev->scl_pin, ret); + } + + return value; +} + +/**************************************************************************** + * Name: i2c_bb_ioe_get_sda + * + * Description: + * Get value from SDA line + * + * Input Parameters: + * lower - A pointer the publicly visible representation of + * the "lower-half" driver state structure. + * + * Returned Value: + * The boolean representation of the SDA line value (true/false). + * + ****************************************************************************/ + +static bool i2c_bb_ioe_get_sda(FAR struct i2c_bitbang_lower_dev_s *lower) +{ + FAR struct i2c_bb_ioe_s *dev = lower->priv; + bool value = true; + int ret; + + ret = IOEXP_READPIN(dev->ioe, dev->sda_pin, &value); + if (ret < 0) + { + i2cerr("read sda pin:%d, error res:%d\n", dev->sda_pin, ret); + } + + return value; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_bitbang_ioexpander_initialize + * + * Description: + * Initialize i2c bitbang ioexapnder lower half driver. + * + * Input Parameters: + * ioe - An instance of the ioexpander device to use for bitbanging + * scl_pin - The pin number to use for SCL + * sda_pin - The pin number to use for SDA + * busnum - The I2C bus number to register + * + * Returned Value: + * On success, a pointer to the initialized I2C driver for the specified. + * + ****************************************************************************/ + +FAR struct i2c_master_s * +i2c_bitbang_ioexpander_initialize(FAR struct ioexpander_dev_s *ioe, + int scl_pin, int sda_pin, int busnum) +{ + FAR struct i2c_master_s *master = NULL; + FAR struct i2c_bb_ioe_s *priv; + + priv = kmm_zalloc(sizeof(struct i2c_bb_ioe_s)); + if (priv == NULL) + { + i2cerr("i2c-bitbang%d kmm_zalloc failed\n", busnum); + return master; + } + + priv->lower.priv = priv; + priv->lower.ops = &g_i2c_bitbang_ops; + priv->scl_pin = scl_pin; + priv->sda_pin = sda_pin; + priv->ioe = ioe; + + master = i2c_bitbang_initialize(&priv->lower); + if (master == NULL) + { + kmm_free(priv); + i2cerr("I2c bitbang:%d initialize failed\n", busnum); + return master; + } + + if (busnum >= 0) + { + int ret; + + ret = i2c_register(master, busnum); + if (ret < 0) + { + kmm_free(priv); + kmm_free(master); + master = NULL; + i2cerr("I2c bitbang:%d register failed:%d\n", busnum, ret); + } + } + + return master; +} diff --git a/include/nuttx/i2c/i2c_bitbang_ioexpander.h b/include/nuttx/i2c/i2c_bitbang_ioexpander.h new file mode 100644 index 0000000000000..07269a625405f --- /dev/null +++ b/include/nuttx/i2c/i2c_bitbang_ioexpander.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * include/nuttx/i2c/i2c_bitbang_ioexpander.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 __INCLUDE_NUTTX_I2C_I2C_BITBANG_IOEXPANDER_H +#define __INCLUDE_NUTTX_I2C_I2C_BITBANG_IOEXPANDER_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Type Definitions + ****************************************************************************/ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_bitbang_ioexpander_initialize + * + * Description: + * Initialize i2c bitbang ioexapnder lower half driver. + * + * Input Parameters: + * ioe - An instance of the ioexpander device to use for bitbanging + * scl_pin - The pin number to use for SCL + * sda_pin - The pin number to use for SDA + * busnum - The I2C bus number to register + * + * Returned Value: + * On success, a pointer to the initialized I2C driver for the specified. + * + ****************************************************************************/ + +FAR struct i2c_master_s * +i2c_bitbang_ioexpander_initialize(FAR struct ioexpander_dev_s *ioe, + int scl_pin, int sda_pin, int busnum); + +#endif /* __INCLUDE_NUTTX_I2C_I2C_BITBANG_IOEXPANDER_H */