This is an automated email from the ASF dual-hosted git repository.

jerpelea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 8342c51d59516cc22ef6ce9b586819b094667a9d
Author: Alan Carvalho de Assis <[email protected]>
AuthorDate: Sun Aug 23 13:34:17 2026 -0300

    ioexpander/ch422g: add a driver for the WCH CH422G I/O
    
    The CH422G offers eight bi-directional pins, IO0-IO7, and four open-drain
    outputs, OC0-OC3.  It appears on boards that have run out of usable GPIOs
    once a parallel RGB panel has taken its share, the Waveshare
    ESP32-S3-Touch-LCD-7 among them, where it holds the panel and touch
    controller in and out of reset and switches the backlight.
    
    Two things about the device do not fit the shape a register-per-address
    I2C driver usually takes, and both are handled here rather than pushed on
    to board logic:
    
      - A register is selected by the I2C address the transfer is addressed
        to, not by a register address written ahead of the data.  Each access
        carries a single byte to one of four addresses.
      - None of the write-only registers can be read back, so the driver
        keeps a shadow copy of each and updates it in step with the device.
    
    IO0-IO7 have no individual direction control; one bit of the system
    parameter register drives the whole group.  The driver records the
    direction asked of each pin and puts the group in output mode once at
    least one of them is an output, which is what a board that drives some of
    the pins would expect.  Reading a pin of a group held in output mode
    reports the value last written, because the hardware cannot report the
    level, and that is documented rather than hidden.
    
    The four open-drain outputs are presented as pins 8-11 of the same
    ioexpander_dev_s so that one instance covers the chip, which means
    CONFIG_IOEXPANDER_NPINS must be at least 12.
    
    Builds clean with no new warnings on esp32s3-touch-lcd7:usbnsh and passes
    nxstyle.
    
    Signed-off-by: Alan Carvalho de Assis <[email protected]>
    Assisted-by: Claude Code
---
 .../components/drivers/special/ioexpander.rst      |  44 +-
 drivers/ioexpander/CMakeLists.txt                  |   4 +
 drivers/ioexpander/Kconfig                         |  12 +
 drivers/ioexpander/Make.defs                       |   4 +
 drivers/ioexpander/ch422g.c                        | 750 +++++++++++++++++++++
 include/nuttx/ioexpander/ch422g.h                  | 116 ++++
 6 files changed, 915 insertions(+), 15 deletions(-)

diff --git a/Documentation/components/drivers/special/ioexpander.rst 
b/Documentation/components/drivers/special/ioexpander.rst
index 2f2fa32f1b1..5a4e19af896 100644
--- a/Documentation/components/drivers/special/ioexpander.rst
+++ b/Documentation/components/drivers/special/ioexpander.rst
@@ -62,64 +62,78 @@ routine in the header of the same name under
    * - Awinic AW9523B
      - I2C
      - 16
-     - ``IOEXPANDER_AW9523B``
+     - ``CONFIG_IOEXPANDER_AW9523B``
+   * - WCH CH422G
+     - I2C
+     - 12
+     - ``CONFIG_IOEXPANDER_CH422G``
    * - iC-Haus iC-JX
      - SPI
      - 16
-     - ``IOEXPANDER_ICJX``
+     - ``CONFIG_IOEXPANDER_ICJX``
    * - ISO1H812G
      - SPI
      - 8
-     - ``IOEXPANDER_ISO1H812G``
+     - ``CONFIG_IOEXPANDER_ISO1H812G``
    * - ISO1I813T
      - SPI
      - 8
-     - ``IOEXPANDER_ISO1I813T``
+     - ``CONFIG_IOEXPANDER_ISO1I813T``
    * - Microchip MCP23008 / MCP23S08
      - I2C
      - 8
-     - ``IOEXPANDER_MCP23X08``
+     - ``CONFIG_IOEXPANDER_MCP23X08``
    * - Microchip MCP23017 / MCP23S17
      - I2C
      - 16
-     - ``IOEXPANDER_MCP23X17``
+     - ``CONFIG_IOEXPANDER_MCP23X17``
    * - NXP PCA9538
      - I2C
      - 8
-     - ``IOEXPANDER_PCA9538``
+     - ``CONFIG_IOEXPANDER_PCA9538``
    * - NXP PCA9555
      - I2C
      - 16
-     - ``IOEXPANDER_PCA9555``
+     - ``CONFIG_IOEXPANDER_PCA9555``
    * - NXP PCA9557
      - I2C
      - 8
-     - ``IOEXPANDER_PCA9557``
+     - ``CONFIG_IOEXPANDER_PCA9557``
    * - PCF8574
      - I2C
      - 8
-     - ``IOEXPANDER_PCF8574``
+     - ``CONFIG_IOEXPANDER_PCF8574``
    * - PCF8575
      - I2C
      - 16
-     - ``IOEXPANDER_PCF8575``
+     - ``CONFIG_IOEXPANDER_PCF8575``
    * - Diodes PI4IOE5V6408
      - I2C
      - 8
-     - ``IOEXPANDER_PI4IOE5V6408``
+     - ``CONFIG_IOEXPANDER_PI4IOE5V6408``
    * - Semtech SX1509
      - I2C
      - 16
-     - ``IOEXPANDER_SX1509``
+     - ``CONFIG_IOEXPANDER_SX1509``
    * - TCA6408 / TCA6416 / TCA6424 / PCAL6416A
      - I2C
      - 8 / 16 / 24 / 16
-     - ``IOEXPANDER_TCA64XX``
+     - ``CONFIG_IOEXPANDER_TCA64XX``
 
 Notes on individual drivers:
 
-- ``IOEXPANDER_TCA64XX`` and ``IOEXPANDER_PCF8574`` additionally depend on
+- ``CONFIG_IOEXPANDER_TCA64XX`` and ``CONFIG_IOEXPANDER_PCF8574`` additionally 
depend on
   ``CONFIG_EXPERIMENTAL``.
+- ``CONFIG_IOEXPANDER_CH422G`` presents the eight bi-directional pins, 
IO0-IO7, as
+  pins 0-7 and the four open-drain outputs, OC0-OC3, as pins 8-11, so
+  ``CONFIG_IOEXPANDER_NPINS`` must be at least 12.  The device selects a
+  register by the I2C address a transfer is addressed to rather than by a
+  register address written ahead of the data, and none of its write-only
+  registers can be read back, so the driver shadows them.  IO0-IO7 share a
+  single direction control in the hardware: the driver records the direction
+  asked of each pin and puts the group in output mode once at least one of
+  them is an output.  Reading a pin of a group held in output mode reports
+  the value last written, because the device cannot report the pin level.
 - Drivers with a ``<device>_MULTIPLE`` option support more than one
   instance of the same chip on a board.
 - Drivers with a ``<device>_SHADOW_MODE`` option keep the output and
diff --git a/drivers/ioexpander/CMakeLists.txt 
b/drivers/ioexpander/CMakeLists.txt
index 34bde646335..62d66604928 100644
--- a/drivers/ioexpander/CMakeLists.txt
+++ b/drivers/ioexpander/CMakeLists.txt
@@ -59,6 +59,10 @@ if(CONFIG_IOEXPANDER)
     list(APPEND SRCS pi4ioe5v6408.c)
   endif()
 
+  if(CONFIG_IOEXPANDER_CH422G)
+    list(APPEND SRCS ch422g.c)
+  endif()
+
   if(CONFIG_IOEXPANDER_PCA9557)
     list(APPEND SRCS pca9557.c)
   endif()
diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig
index 489ca1b0c7c..a0ff52ec85b 100644
--- a/drivers/ioexpander/Kconfig
+++ b/drivers/ioexpander/Kconfig
@@ -56,6 +56,18 @@ config IOEXPANDER_DUMMY_INT_POLLDELAY
 
 endif # IOEXPANDER_DUMMY
 
+config IOEXPANDER_CH422G
+       bool "CH422G I2C IO expander"
+       default n
+       depends on I2C
+       ---help---
+               Enable support for the WCH CH422G IO Expander, which offers 
eight
+               bi-directional pins, IO0-IO7, and four open-drain outputs, 
OC0-OC3.
+
+               IO0-IO7 share a single direction control in the hardware.  The
+               driver puts the group in output mode when at least one of them 
has
+               been configured as an output.
+
 config IOEXPANDER_ICJX
        bool "iC-JX SPI IO expander"
        default n
diff --git a/drivers/ioexpander/Make.defs b/drivers/ioexpander/Make.defs
index 2c949558bb2..7ca38196d81 100644
--- a/drivers/ioexpander/Make.defs
+++ b/drivers/ioexpander/Make.defs
@@ -58,6 +58,10 @@ ifeq ($(CONFIG_IOEXPANDER_PI4IOE5V6408),y)
   CSRCS += pi4ioe5v6408.c
 endif
 
+ifeq ($(CONFIG_IOEXPANDER_CH422G),y)
+  CSRCS += ch422g.c
+endif
+
 ifeq ($(CONFIG_IOEXPANDER_PCA9557),y)
   CSRCS += pca9557.c
 endif
diff --git a/drivers/ioexpander/ch422g.c b/drivers/ioexpander/ch422g.c
new file mode 100644
index 00000000000..97f75895ca4
--- /dev/null
+++ b/drivers/ioexpander/ch422g.c
@@ -0,0 +1,750 @@
+/****************************************************************************
+ * drivers/ioexpander/ch422g.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/* The WCH CH422G is an I2C I/O expander offering eight bi-directional
+ * pins, IO0-IO7, and four open-drain outputs, OC0-OC3.
+ *
+ * It does not follow the usual convention of a register address written
+ * ahead of the data.  Each register is reached through an I2C address of
+ * its own and every access carries a single data byte:
+ *
+ *   0x24  write  System parameter register
+ *   0x23  write  OC0-OC3 output register
+ *   0x38  write  IO0-IO7 output register
+ *   0x26  read   IO0-IO7 input register
+ *
+ * None of the write-only registers can be read back, so the driver keeps a
+ * shadow copy of each and updates it in step with the device.
+ *
+ * IO0-IO7 do not have individual direction control.  A single bit of the
+ * system parameter register, IO_OE, drives the whole group.  The driver
+ * records the direction requested for each pin and puts the group in output
+ * mode when at least one of them is an output, which is what a board that
+ * mixes the two would expect of the pins it drives.  Reading a pin of a
+ * group held in output mode returns the shadowed output value rather than
+ * the level on the pin, because the hardware cannot report it.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <debug.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <nuttx/kmalloc.h>
+#include <nuttx/mutex.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/ioexpander/ioexpander.h>
+#include <nuttx/ioexpander/ch422g.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* I2C addresses that select a register.  See the comment above. */
+
+#define CH422G_ADDR_WR_SET      0x24  /* System parameter register */
+#define CH422G_ADDR_WR_OC       0x23  /* OC0-OC3 output register */
+#define CH422G_ADDR_WR_IO       0x38  /* IO0-IO7 output register */
+#define CH422G_ADDR_RD_IO       0x26  /* IO0-IO7 input register */
+
+/* System parameter register bits */
+
+#define CH422G_SET_IO_OE        (1 << 0)  /* IO0-IO7 drive their pins */
+#define CH422G_SET_A_SCAN       (1 << 1)  /* Digital tube scan enable */
+#define CH422G_SET_OD_EN        (1 << 2)  /* OC0-OC3 open-drain enable */
+#define CH422G_SET_SLEEP        (1 << 3)  /* Low power mode */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct ch422g_dev_s
+{
+  /* Must appear first so the structure can be cast to the public type */
+
+  struct ioexpander_dev_s dev;
+
+  FAR struct i2c_master_s     *i2c;    /* Saved I2C driver instance */
+  FAR struct ch422g_config_s  *config; /* Board configuration data */
+  mutex_t                      lock;   /* Mutual exclusion */
+
+  uint8_t sysparam;                    /* Shadow of system parameter reg */
+  uint8_t outio;                       /* Shadow of IO0-IO7 output reg */
+  uint8_t outoc;                       /* Shadow of OC0-OC3 output reg */
+  uint8_t outmask;                     /* IO0-IO7 configured as outputs */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int ch422g_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                            int dir);
+static int ch422g_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                         int opt, FAR void *val);
+static int ch422g_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                           bool value);
+static int ch422g_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                          FAR bool *value);
+static int ch422g_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                          FAR bool *value);
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+static int ch422g_multiwritepin(FAR struct ioexpander_dev_s *dev,
+                                FAR const uint8_t *pins,
+                                FAR const bool *values, int count);
+static int ch422g_multireadpin(FAR struct ioexpander_dev_s *dev,
+                               FAR const uint8_t *pins, FAR bool *values,
+                               int count);
+static int ch422g_multireadbuf(FAR struct ioexpander_dev_s *dev,
+                               FAR const uint8_t *pins, FAR bool *values,
+                               int count);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct ioexpander_ops_s g_ch422g_ops =
+{
+  .ioe_direction     = ch422g_direction,
+  .ioe_option        = ch422g_option,
+  .ioe_writepin      = ch422g_writepin,
+  .ioe_readpin       = ch422g_readpin,
+  .ioe_readbuf       = ch422g_readbuf,
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+  .ioe_multiwritepin = ch422g_multiwritepin,
+  .ioe_multireadpin  = ch422g_multireadpin,
+  .ioe_multireadbuf  = ch422g_multireadbuf,
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ch422g_write_reg
+ *
+ * Description:
+ *   Write the single data byte of one register.  The register is selected
+ *   by the I2C address the transfer is addressed to.
+ *
+ ****************************************************************************/
+
+static int ch422g_write_reg(FAR struct ch422g_dev_s *priv, uint8_t addr,
+                            uint8_t value)
+{
+  struct i2c_msg_s msg;
+
+  msg.frequency = priv->config->frequency;
+  msg.addr      = addr;
+  msg.flags     = 0;
+  msg.buffer    = &value;
+  msg.length    = 1;
+
+  return I2C_TRANSFER(priv->i2c, &msg, 1);
+}
+
+/****************************************************************************
+ * Name: ch422g_read_io
+ *
+ * Description:
+ *   Read the level of IO0-IO7.  Only meaningful while the group is in
+ *   input mode.
+ *
+ ****************************************************************************/
+
+static int ch422g_read_io(FAR struct ch422g_dev_s *priv, FAR uint8_t *value)
+{
+  struct i2c_msg_s msg;
+
+  msg.frequency = priv->config->frequency;
+  msg.addr      = CH422G_ADDR_RD_IO;
+  msg.flags     = I2C_M_READ;
+  msg.buffer    = value;
+  msg.length    = 1;
+
+  return I2C_TRANSFER(priv->i2c, &msg, 1);
+}
+
+/****************************************************************************
+ * Name: ch422g_setdir
+ *
+ * Description:
+ *   Bring the IO_OE bit into agreement with the recorded per-pin
+ *   directions.  Must be called with the lock held.
+ *
+ ****************************************************************************/
+
+static int ch422g_setdir(FAR struct ch422g_dev_s *priv)
+{
+  uint8_t sysparam = priv->sysparam;
+
+  if (priv->outmask != 0)
+    {
+      sysparam |= CH422G_SET_IO_OE;
+    }
+  else
+    {
+      sysparam &= ~CH422G_SET_IO_OE;
+    }
+
+  if (sysparam == priv->sysparam)
+    {
+      return OK;
+    }
+
+  priv->sysparam = sysparam;
+  return ch422g_write_reg(priv, CH422G_ADDR_WR_SET, sysparam);
+}
+
+/****************************************************************************
+ * Name: ch422g_direction
+ *
+ * Description:
+ *   Set the direction of an I/O pin.
+ *
+ ****************************************************************************/
+
+static int ch422g_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                            int dir)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  int ret;
+
+  if (pin >= CH422G_NPINS)
+    {
+      return -ENXIO;
+    }
+
+  if (dir != IOEXPANDER_DIRECTION_IN && dir != IOEXPANDER_DIRECTION_OUT)
+    {
+      return -EINVAL;
+    }
+
+  /* OC0-OC3 are open-drain outputs and cannot be inputs */
+
+  if (pin >= CH422G_NIO)
+    {
+      return dir == IOEXPANDER_DIRECTION_OUT ? OK : -EINVAL;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (dir == IOEXPANDER_DIRECTION_OUT)
+    {
+      priv->outmask |= (1 << pin);
+    }
+  else
+    {
+      priv->outmask &= ~(1 << pin);
+    }
+
+  ret = ch422g_setdir(priv);
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: ch422g_option
+ *
+ * Description:
+ *   Set pin options.  The CH422G offers none of the options the interface
+ *   defines, so only a request that asks for nothing succeeds.
+ *
+ ****************************************************************************/
+
+static int ch422g_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                         int opt, FAR void *val)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  int ret = -ENOSYS;
+
+  if (pin >= CH422G_NPINS)
+    {
+      return -ENXIO;
+    }
+
+  if (opt == IOEXPANDER_OPTION_INVERT)
+    {
+      ret = ((uintptr_t)val == IOEXPANDER_VAL_NORMAL) ? OK : -ENOSYS;
+    }
+  else if (opt == IOEXPANDER_OPTION_INTCFG)
+    {
+      ret = ((uintptr_t)val == IOEXPANDER_VAL_DISABLE) ? OK : -ENOSYS;
+    }
+
+  UNUSED(priv);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: ch422g_writepin
+ *
+ * Description:
+ *   Set the level of an output pin.
+ *
+ ****************************************************************************/
+
+static int ch422g_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                           bool value)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  FAR uint8_t *shadow;
+  uint8_t addr;
+  uint8_t bit;
+  int ret;
+
+  if (pin >= CH422G_NPINS)
+    {
+      return -ENXIO;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (pin < CH422G_NIO)
+    {
+      shadow = &priv->outio;
+      addr   = CH422G_ADDR_WR_IO;
+      bit    = 1 << pin;
+    }
+  else
+    {
+      shadow = &priv->outoc;
+      addr   = CH422G_ADDR_WR_OC;
+      bit    = 1 << (pin - CH422G_NIO);
+    }
+
+  if (value)
+    {
+      *shadow |= bit;
+    }
+  else
+    {
+      *shadow &= ~bit;
+    }
+
+  ret = ch422g_write_reg(priv, addr, *shadow);
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: ch422g_readpin
+ *
+ * Description:
+ *   Read the level of a pin.  A pin of a group held in output mode, and any
+ *   of the write-only open-drain outputs, reports the value last written.
+ *
+ ****************************************************************************/
+
+static int ch422g_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                          FAR bool *value)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  uint8_t regval;
+  int ret;
+
+  if (pin >= CH422G_NPINS)
+    {
+      return -ENXIO;
+    }
+
+  DEBUGASSERT(value != NULL);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (pin >= CH422G_NIO)
+    {
+      *value = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0;
+      ret    = OK;
+    }
+  else if ((priv->sysparam & CH422G_SET_IO_OE) != 0)
+    {
+      *value = ((priv->outio >> pin) & 1) != 0;
+      ret    = OK;
+    }
+  else
+    {
+      ret = ch422g_read_io(priv, &regval);
+      if (ret >= 0)
+        {
+          *value = ((regval >> pin) & 1) != 0;
+        }
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: ch422g_readbuf
+ *
+ * Description:
+ *   Read the last value written to a pin.  The CH422G does not buffer a
+ *   separately readable copy, so this is the shadowed output value.
+ *
+ ****************************************************************************/
+
+static int ch422g_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                          FAR bool *value)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  int ret;
+
+  if (pin >= CH422G_NPINS)
+    {
+      return -ENXIO;
+    }
+
+  DEBUGASSERT(value != NULL);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (pin < CH422G_NIO)
+    {
+      *value = ((priv->outio >> pin) & 1) != 0;
+    }
+  else
+    {
+      *value = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0;
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+
+/****************************************************************************
+ * Name: ch422g_multiwritepin
+ *
+ * Description:
+ *   Set the level of several pins.  Pins that share a register are gathered
+ *   so that the register is written once.
+ *
+ ****************************************************************************/
+
+static int ch422g_multiwritepin(FAR struct ioexpander_dev_s *dev,
+                                FAR const uint8_t *pins,
+                                FAR const bool *values, int count)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  bool touchio = false;
+  bool touchoc = false;
+  int ret;
+  int i;
+
+  DEBUGASSERT(pins != NULL && values != NULL);
+
+  for (i = 0; i < count; i++)
+    {
+      if (pins[i] >= CH422G_NPINS)
+        {
+          return -ENXIO;
+        }
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  for (i = 0; i < count; i++)
+    {
+      uint8_t pin = pins[i];
+
+      if (pin < CH422G_NIO)
+        {
+          if (values[i])
+            {
+              priv->outio |= (1 << pin);
+            }
+          else
+            {
+              priv->outio &= ~(1 << pin);
+            }
+
+          touchio = true;
+        }
+      else
+        {
+          if (values[i])
+            {
+              priv->outoc |= (1 << (pin - CH422G_NIO));
+            }
+          else
+            {
+              priv->outoc &= ~(1 << (pin - CH422G_NIO));
+            }
+
+          touchoc = true;
+        }
+    }
+
+  ret = OK;
+
+  if (touchio)
+    {
+      ret = ch422g_write_reg(priv, CH422G_ADDR_WR_IO, priv->outio);
+    }
+
+  if (ret >= 0 && touchoc)
+    {
+      ret = ch422g_write_reg(priv, CH422G_ADDR_WR_OC, priv->outoc);
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: ch422g_multireadpin
+ *
+ * Description:
+ *   Read the level of several pins with a single read of the device.
+ *
+ ****************************************************************************/
+
+static int ch422g_multireadpin(FAR struct ioexpander_dev_s *dev,
+                               FAR const uint8_t *pins, FAR bool *values,
+                               int count)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  uint8_t regval = 0;
+  bool haveinput = false;
+  int ret;
+  int i;
+
+  DEBUGASSERT(pins != NULL && values != NULL);
+
+  for (i = 0; i < count; i++)
+    {
+      if (pins[i] >= CH422G_NPINS)
+        {
+          return -ENXIO;
+        }
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if ((priv->sysparam & CH422G_SET_IO_OE) == 0)
+    {
+      for (i = 0; i < count; i++)
+        {
+          if (pins[i] < CH422G_NIO)
+            {
+              haveinput = true;
+              break;
+            }
+        }
+    }
+
+  if (haveinput)
+    {
+      ret = ch422g_read_io(priv, &regval);
+      if (ret < 0)
+        {
+          nxmutex_unlock(&priv->lock);
+          return ret;
+        }
+    }
+
+  for (i = 0; i < count; i++)
+    {
+      uint8_t pin = pins[i];
+
+      if (pin >= CH422G_NIO)
+        {
+          values[i] = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0;
+        }
+      else if (haveinput)
+        {
+          values[i] = ((regval >> pin) & 1) != 0;
+        }
+      else
+        {
+          values[i] = ((priv->outio >> pin) & 1) != 0;
+        }
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ch422g_multireadbuf
+ *
+ * Description:
+ *   Read the last value written to several pins.
+ *
+ ****************************************************************************/
+
+static int ch422g_multireadbuf(FAR struct ioexpander_dev_s *dev,
+                               FAR const uint8_t *pins, FAR bool *values,
+                               int count)
+{
+  FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev;
+  int ret;
+  int i;
+
+  DEBUGASSERT(pins != NULL && values != NULL);
+
+  for (i = 0; i < count; i++)
+    {
+      if (pins[i] >= CH422G_NPINS)
+        {
+          return -ENXIO;
+        }
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  for (i = 0; i < count; i++)
+    {
+      uint8_t pin = pins[i];
+
+      if (pin < CH422G_NIO)
+        {
+          values[i] = ((priv->outio >> pin) & 1) != 0;
+        }
+      else
+        {
+          values[i] = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0;
+        }
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+
+#endif /* CONFIG_IOEXPANDER_MULTIPIN */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ch422g_initialize
+ *
+ * Description:
+ *   Instantiate and configure the CH422G device driver to use the provided
+ *   I2C device instance.
+ *
+ * Input Parameters:
+ *   i2c    - An I2C driver instance
+ *   config - Persistent board configuration data
+ *
+ * Returned Value:
+ *   An ioexpander_dev_s instance on success, NULL on failure.
+ *
+ ****************************************************************************/
+
+FAR struct ioexpander_dev_s *
+ch422g_initialize(FAR struct i2c_master_s *i2c,
+                  FAR struct ch422g_config_s *config)
+{
+  FAR struct ch422g_dev_s *priv;
+  int ret;
+
+  DEBUGASSERT(i2c != NULL && config != NULL);
+
+  priv = kmm_zalloc(sizeof(struct ch422g_dev_s));
+  if (priv == NULL)
+    {
+      gpioerr("ERROR: Failed to allocate driver instance\n");
+      return NULL;
+    }
+
+  priv->dev.ops = &g_ch422g_ops;
+  priv->i2c     = i2c;
+  priv->config  = config;
+
+  nxmutex_init(&priv->lock);
+
+  /* Leave the device in a known state: the open-drain outputs off, the
+   * bi-directional pins low, and the group in input mode until a board
+   * asks for an output.  Writing the system parameter register first also
+   * confirms that the device is answering.
+   */
+
+  ret = ch422g_write_reg(priv, CH422G_ADDR_WR_SET, priv->sysparam);
+  if (ret < 0)
+    {
+      gpioerr("ERROR: CH422G not responding: %d\n", ret);
+      goto errout;
+    }
+
+  ret = ch422g_write_reg(priv, CH422G_ADDR_WR_OC, priv->outoc);
+  if (ret < 0)
+    {
+      goto errout;
+    }
+
+  ret = ch422g_write_reg(priv, CH422G_ADDR_WR_IO, priv->outio);
+  if (ret < 0)
+    {
+      goto errout;
+    }
+
+  return &priv->dev;
+
+errout:
+  nxmutex_destroy(&priv->lock);
+  kmm_free(priv);
+  return NULL;
+}
+
diff --git a/include/nuttx/ioexpander/ch422g.h 
b/include/nuttx/ioexpander/ch422g.h
new file mode 100644
index 00000000000..2d48bb308b8
--- /dev/null
+++ b/include/nuttx/ioexpander/ch422g.h
@@ -0,0 +1,116 @@
+/****************************************************************************
+ * include/nuttx/ioexpander/ch422g.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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_IOEXPANDER_CH422G_H
+#define __INCLUDE_NUTTX_IOEXPANDER_CH422G_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/i2c/i2c_master.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Pin numbering used by the ioexpander interface.
+ *
+ * The CH422G offers eight bi-directional pins, IO0-IO7, and four
+ * open-drain outputs, OC0-OC3.  They are presented as one contiguous pin
+ * space so that a single ioexpander_dev_s covers the whole chip.
+ */
+
+#define CH422G_IO0              0
+#define CH422G_IO1              1
+#define CH422G_IO2              2
+#define CH422G_IO3              3
+#define CH422G_IO4              4
+#define CH422G_IO5              5
+#define CH422G_IO6              6
+#define CH422G_IO7              7
+#define CH422G_OC0              8
+#define CH422G_OC1              9
+#define CH422G_OC2              10
+#define CH422G_OC3              11
+
+#define CH422G_NPINS            12
+#define CH422G_NIO              8    /* IO0-IO7 */
+#define CH422G_NOC              4    /* OC0-OC3 */
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* A reference to a structure of this type must be passed to the CH422G
+ * driver when the driver is instantiated.  Memory for this structure is
+ * provided by the caller.  It is not copied by the driver and is presumed
+ * to persist while the driver is active.
+ */
+
+struct ch422g_config_s
+{
+  uint32_t frequency;  /* I2C frequency */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: ch422g_initialize
+ *
+ * Description:
+ *   Instantiate and configure the CH422G device driver to use the provided
+ *   I2C device instance.
+ *
+ * Input Parameters:
+ *   i2c    - An I2C driver instance
+ *   config - Persistent board configuration data
+ *
+ * Returned Value:
+ *   An ioexpander_dev_s instance on success, NULL on failure.
+ *
+ ****************************************************************************/
+
+FAR struct ioexpander_dev_s *
+ch422g_initialize(FAR struct i2c_master_s *i2c,
+                  FAR struct ch422g_config_s *config);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INCLUDE_NUTTX_IOEXPANDER_CH422G_H */

Reply via email to