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

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

commit 270d1f32f923f69c5f3017165057811aac065945
Author: Filipe Cavalcanti <[email protected]>
AuthorDate: Fri Jul 24 08:12:46 2026 -0300

    drives/ioexpander: add support to PI4IOE IO Expander
    
    Adds support for the PIO4IOE IO Expander, more specifically the 
PIO4IOE5V6408 version.
    
    Assisted-by: Cursor IDE agents
    Signed-off-by: Filipe Cavalcanti <[email protected]>
---
 drivers/ioexpander/CMakeLists.txt       |   4 +
 drivers/ioexpander/Kconfig              |  28 ++
 drivers/ioexpander/Make.defs            |   4 +
 drivers/ioexpander/pi4ioe5v6408.c       | 864 ++++++++++++++++++++++++++++++++
 include/nuttx/ioexpander/pi4ioe5v6408.h | 110 ++++
 5 files changed, 1010 insertions(+)

diff --git a/drivers/ioexpander/CMakeLists.txt 
b/drivers/ioexpander/CMakeLists.txt
index 23e712e1d35..34bde646335 100644
--- a/drivers/ioexpander/CMakeLists.txt
+++ b/drivers/ioexpander/CMakeLists.txt
@@ -55,6 +55,10 @@ if(CONFIG_IOEXPANDER)
     list(APPEND SRCS pca9538.c)
   endif()
 
+  if(CONFIG_IOEXPANDER_PI4IOE5V6408)
+    list(APPEND SRCS pi4ioe5v6408.c)
+  endif()
+
   if(CONFIG_IOEXPANDER_PCA9557)
     list(APPEND SRCS pca9557.c)
   endif()
diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig
index 1bd2cd89283..0de2ee4e56e 100644
--- a/drivers/ioexpander/Kconfig
+++ b/drivers/ioexpander/Kconfig
@@ -312,6 +312,34 @@ config PCA9538_RETRY
 
 endif # IOEXPANDER_PCA9538
 
+config IOEXPANDER_PI4IOE5V6408
+       bool "PI4IOE5V6408 I2C IO expander"
+       default n
+       depends on I2C
+       ---help---
+               Enable support for the Diodes Incorporated PI4IOE5V6408
+               8-bit I2C I/O expander.
+
+if IOEXPANDER_PI4IOE5V6408
+
+config PI4IOE5V6408_INT_ENABLE
+       bool "Enable PI4IOE5V6408 interrupt support"
+       default n
+       select IOEXPANDER_INT_ENABLE
+       select SCHED_HPWORK
+       ---help---
+               Enable the PI4IOE5V6408 interrupt callback interface.  The board
+               must provide hooks for the device's active-low interrupt pin.
+
+config PI4IOE5V6408_INT_NCALLBACKS
+       int "Max number of interrupt callbacks"
+       default 4
+       depends on PI4IOE5V6408_INT_ENABLE
+       ---help---
+               This is the maximum number of interrupt callbacks supported.
+
+endif # IOEXPANDER_PI4IOE5V6408
+
 config IOEXPANDER_PCA9557
        bool "PCA9557 I2C IO expander"
        default n
diff --git a/drivers/ioexpander/Make.defs b/drivers/ioexpander/Make.defs
index 96cdc9cb45d..2c949558bb2 100644
--- a/drivers/ioexpander/Make.defs
+++ b/drivers/ioexpander/Make.defs
@@ -54,6 +54,10 @@ ifeq ($(CONFIG_IOEXPANDER_PCA9538),y)
   CSRCS += pca9538.c
 endif
 
+ifeq ($(CONFIG_IOEXPANDER_PI4IOE5V6408),y)
+  CSRCS += pi4ioe5v6408.c
+endif
+
 ifeq ($(CONFIG_IOEXPANDER_PCA9557),y)
   CSRCS += pca9557.c
 endif
diff --git a/drivers/ioexpander/pi4ioe5v6408.c 
b/drivers/ioexpander/pi4ioe5v6408.c
new file mode 100644
index 00000000000..c6820e9ec39
--- /dev/null
+++ b/drivers/ioexpander/pi4ioe5v6408.c
@@ -0,0 +1,864 @@
+/****************************************************************************
+ * drivers/ioexpander/pi4ioe5v6408.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <nuttx/debug.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/ioexpander/ioexpander.h>
+#include <nuttx/ioexpander/pi4ioe5v6408.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mutex.h>
+#include <nuttx/signal.h>
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+#  include <nuttx/wqueue.h>
+#endif
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define PI4IOE5V6408_REG_CHIP_ID_CTRL   0x01
+#define PI4IOE5V6408_REG_DIRECTION      0x03
+#define PI4IOE5V6408_REG_OUTPUT         0x05
+#define PI4IOE5V6408_REG_OUTPUT_HIGH_Z  0x07
+#define PI4IOE5V6408_REG_INPUT_DEFAULT  0x09
+#define PI4IOE5V6408_REG_PULL_ENABLE    0x0b
+#define PI4IOE5V6408_REG_PULL_SELECT    0x0d
+#define PI4IOE5V6408_REG_INPUT          0x0f
+#define PI4IOE5V6408_REG_INT_MASK       0x11
+#define PI4IOE5V6408_REG_INT_STATUS     0x13
+
+#define PI4IOE5V6408_SOFT_RESET         0xff
+#define PI4IOE5V6408_RESET_DELAY_US     1000
+#define PI4IOE5V6408_INT_MASK_DEFAULT   0xff
+#define PI4IOE5V6408_IN_DEF_DEFAULT     0x00
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+struct pi4ioe5v6408_callback_s
+{
+  ioe_pinset_t pinset;
+  ioe_callback_t callback;
+  FAR void *arg;
+};
+#endif
+
+struct pi4ioe5v6408_dev_s
+{
+  struct ioexpander_dev_s dev;
+  FAR struct i2c_master_s *i2c;
+  FAR struct pi4ioe5v6408_config_s *config;
+  mutex_t lock;
+  uint8_t direction;
+  uint8_t output;
+  uint8_t high_impedance;
+  uint8_t pull_enable;
+  uint8_t pull_select;
+  uint8_t input_default;
+  uint8_t interrupt_mask;
+
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+  struct work_s work;
+  struct pi4ioe5v6408_callback_s
+    callbacks[CONFIG_PI4IOE5V6408_INT_NCALLBACKS];
+#endif
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int pi4ioe5v6408_direction(FAR struct ioexpander_dev_s *dev,
+                                  uint8_t pin, int direction);
+static int pi4ioe5v6408_option(FAR struct ioexpander_dev_s *dev,
+                               uint8_t pin, int option, FAR void *value);
+static int pi4ioe5v6408_writepin(FAR struct ioexpander_dev_s *dev,
+                                 uint8_t pin, bool value);
+static int pi4ioe5v6408_readpin(FAR struct ioexpander_dev_s *dev,
+                                uint8_t pin, FAR bool *value);
+static int pi4ioe5v6408_readbuf(FAR struct ioexpander_dev_s *dev,
+                                uint8_t pin, FAR bool *value);
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+static int pi4ioe5v6408_multiwritepin(FAR struct ioexpander_dev_s *dev,
+                                      FAR const uint8_t *pins,
+                                      FAR const bool *values, int count);
+static int pi4ioe5v6408_multireadpin(FAR struct ioexpander_dev_s *dev,
+                                     FAR const uint8_t *pins,
+                                     FAR bool *values, int count);
+static int pi4ioe5v6408_multireadbuf(FAR struct ioexpander_dev_s *dev,
+                                     FAR const uint8_t *pins,
+                                     FAR bool *values, int count);
+#endif
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+static FAR void *pi4ioe5v6408_attach(FAR struct ioexpander_dev_s *dev,
+                                     ioe_pinset_t pinset,
+                                     ioe_callback_t callback, FAR void *arg);
+static int pi4ioe5v6408_detach(FAR struct ioexpander_dev_s *dev,
+                               FAR void *handle);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct ioexpander_ops_s g_pi4ioe5v6408_ops =
+{
+  pi4ioe5v6408_direction,
+  pi4ioe5v6408_option,
+  pi4ioe5v6408_writepin,
+  pi4ioe5v6408_readpin,
+  pi4ioe5v6408_readbuf
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+  , pi4ioe5v6408_multiwritepin
+  , pi4ioe5v6408_multireadpin
+  , pi4ioe5v6408_multireadbuf
+#endif
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+#  ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+  , pi4ioe5v6408_attach
+  , pi4ioe5v6408_detach
+#  else
+  , NULL
+  , NULL
+#  endif
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static int pi4ioe5v6408_write(FAR struct pi4ioe5v6408_dev_s *priv,
+                              uint8_t reg, uint8_t value)
+{
+  struct i2c_config_s config;
+  uint8_t buffer[2];
+  int ret;
+
+  config.frequency = priv->config->frequency;
+  config.address   = priv->config->address;
+  config.addrlen   = 7;
+
+  buffer[0] = reg;
+  buffer[1] = value;
+
+  ret = i2c_write(priv->i2c, &config, buffer, sizeof(buffer));
+  if (ret < 0)
+    {
+      gpioerr("ERROR: PI4IOE5V6408 write reg 0x%02x val 0x%02x failed: %d\n",
+              reg, value, ret);
+    }
+
+  return ret;
+}
+
+static int pi4ioe5v6408_read(FAR struct pi4ioe5v6408_dev_s *priv,
+                             uint8_t reg, FAR uint8_t *value)
+{
+  struct i2c_config_s config;
+  int ret;
+
+  config.frequency = priv->config->frequency;
+  config.address   = priv->config->address;
+  config.addrlen   = 7;
+
+  ret = i2c_writeread(priv->i2c, &config, &reg, 1, value, 1);
+  if (ret < 0)
+    {
+      gpioerr("ERROR: PI4IOE5V6408 read reg 0x%02x failed: %d\n",
+              reg, ret);
+    }
+
+  return ret;
+}
+
+static int pi4ioe5v6408_update(FAR struct pi4ioe5v6408_dev_s *priv,
+                               uint8_t reg, FAR uint8_t *shadow,
+                               uint8_t mask, bool set)
+{
+  uint8_t value;
+  int ret;
+
+  value = set ? (*shadow | mask) : (*shadow & ~mask);
+
+  /* Skip the I2C write when the register is unchanged. */
+
+  if (value == *shadow)
+    {
+      return OK;
+    }
+
+  ret = pi4ioe5v6408_write(priv, reg, value);
+  if (ret >= 0)
+    {
+      *shadow = value;
+    }
+
+  return ret;
+}
+
+static int pi4ioe5v6408_checkpin(uint8_t pin)
+{
+  return pin < PI4IOE5V6408_NPINS ? OK : -ENXIO;
+}
+
+static int pi4ioe5v6408_setpull(FAR struct pi4ioe5v6408_dev_s *priv,
+                                uint8_t pin, int direction)
+{
+  uint8_t mask = 1 << pin;
+  bool enable;
+  bool pullup;
+  int ret;
+
+  enable = direction == IOEXPANDER_DIRECTION_IN_PULLUP ||
+           direction == IOEXPANDER_DIRECTION_IN_PULLDOWN;
+  pullup = direction == IOEXPANDER_DIRECTION_IN_PULLUP;
+
+  if (enable)
+    {
+      ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_PULL_SELECT,
+                                &priv->pull_select, mask, pullup);
+      if (ret < 0)
+        {
+          return ret;
+        }
+    }
+
+  return pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_PULL_ENABLE,
+                             &priv->pull_enable, mask, enable);
+}
+
+static int pi4ioe5v6408_direction(FAR struct ioexpander_dev_s *dev,
+                                  uint8_t pin, int direction)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  uint8_t mask;
+  bool output;
+  int ret;
+
+  ret = pi4ioe5v6408_checkpin(pin);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (direction != IOEXPANDER_DIRECTION_IN &&
+      direction != IOEXPANDER_DIRECTION_IN_PULLUP &&
+      direction != IOEXPANDER_DIRECTION_IN_PULLDOWN &&
+      direction != IOEXPANDER_DIRECTION_OUT)
+    {
+      return -ENOTSUP;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  mask = 1 << pin;
+  output = direction == IOEXPANDER_DIRECTION_OUT;
+
+  /* Leave pull registers at reset defaults when configuring
+   * an output.  Only input modes with explicit pull request change them.
+   */
+
+  if (!output)
+    {
+      ret = pi4ioe5v6408_setpull(priv, pin, direction);
+    }
+  else
+    {
+      ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_OUTPUT_HIGH_Z,
+                                &priv->high_impedance, mask, false);
+    }
+
+  if (ret >= 0)
+    {
+      ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_DIRECTION,
+                                &priv->direction, mask, output);
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+static int pi4ioe5v6408_option(FAR struct ioexpander_dev_s *dev,
+                               uint8_t pin, int option, FAR void *value)
+{
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  uintptr_t setting = (uintptr_t)value;
+  uint8_t mask;
+#endif
+  int ret;
+
+  ret = pi4ioe5v6408_checkpin(pin);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+#ifndef CONFIG_PI4IOE5V6408_INT_ENABLE
+  UNUSED(dev);
+  UNUSED(option);
+  UNUSED(value);
+  return -ENOTSUP;
+#else
+  if (option != IOEXPANDER_OPTION_INTCFG &&
+      option != IOEXPANDER_OPTION_SETMASK)
+    {
+      return -ENOTSUP;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  mask = 1 << pin;
+
+  if (option == IOEXPANDER_OPTION_SETMASK)
+    {
+      if (setting != IOEXPANDER_MASK_DISABLE &&
+          setting != IOEXPANDER_MASK_ENABLE)
+        {
+          ret = -EINVAL;
+        }
+      else
+        {
+          ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_INT_MASK,
+                                    &priv->interrupt_mask, mask,
+                                    setting == IOEXPANDER_MASK_ENABLE);
+        }
+    }
+  else if (setting == IOEXPANDER_VAL_DISABLE)
+    {
+      ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_INT_MASK,
+                                &priv->interrupt_mask, mask, true);
+    }
+  else if (setting == IOEXPANDER_VAL_HIGH ||
+           setting == IOEXPANDER_VAL_RISING ||
+           setting == IOEXPANDER_VAL_LOW ||
+           setting == IOEXPANDER_VAL_FALLING)
+    {
+      /* The chip compares the input against INPUT_DEFAULT.  A default low
+       * detects high/rising and a default high detects low/falling.
+       */
+
+      bool default_high = setting == IOEXPANDER_VAL_LOW ||
+                          setting == IOEXPANDER_VAL_FALLING;
+
+      ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_INPUT_DEFAULT,
+                                &priv->input_default, mask, default_high);
+      if (ret >= 0)
+        {
+          ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_INT_MASK,
+                                    &priv->interrupt_mask, mask, false);
+        }
+    }
+  else
+    {
+      ret = -ENOTSUP;
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+#endif
+}
+
+static int pi4ioe5v6408_writepin(FAR struct ioexpander_dev_s *dev,
+                                 uint8_t pin, bool value)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  int ret;
+
+  ret = pi4ioe5v6408_checkpin(pin);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = pi4ioe5v6408_update(priv, PI4IOE5V6408_REG_OUTPUT,
+                            &priv->output, 1 << pin, value);
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+static int pi4ioe5v6408_readpin(FAR struct ioexpander_dev_s *dev,
+                                uint8_t pin, FAR bool *value)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  uint8_t input;
+  int ret;
+
+  if (value == NULL)
+    {
+      return -EINVAL;
+    }
+
+  ret = pi4ioe5v6408_checkpin(pin);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = pi4ioe5v6408_read(priv, PI4IOE5V6408_REG_INPUT, &input);
+  if (ret >= 0)
+    {
+      *value = (input & (1 << pin)) != 0;
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+static int pi4ioe5v6408_readbuf(FAR struct ioexpander_dev_s *dev,
+                                uint8_t pin, FAR bool *value)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  int ret;
+
+  if (value == NULL)
+    {
+      return -EINVAL;
+    }
+
+  ret = pi4ioe5v6408_checkpin(pin);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  *value = (priv->output & (1 << pin)) != 0;
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+static int pi4ioe5v6408_multiwritepin(FAR struct ioexpander_dev_s *dev,
+                                      FAR const uint8_t *pins,
+                                      FAR const bool *values, int count)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  uint8_t output;
+  int ret;
+  int i;
+
+  if (pins == NULL || values == NULL || count < 0)
+    {
+      return -EINVAL;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  output = priv->output;
+  for (i = 0; i < count; i++)
+    {
+      ret = pi4ioe5v6408_checkpin(pins[i]);
+      if (ret < 0)
+        {
+          nxmutex_unlock(&priv->lock);
+          return ret;
+        }
+
+      if (values[i])
+        {
+          output |= 1 << pins[i];
+        }
+      else
+        {
+          output &= ~(1 << pins[i]);
+        }
+    }
+
+  ret = pi4ioe5v6408_write(priv, PI4IOE5V6408_REG_OUTPUT, output);
+  if (ret >= 0)
+    {
+      priv->output = output;
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return ret;
+}
+
+static int pi4ioe5v6408_multiread(FAR struct ioexpander_dev_s *dev,
+                                  FAR const uint8_t *pins,
+                                  FAR bool *values, int count, bool buffered)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  uint8_t data;
+  int ret;
+  int i;
+
+  if (pins == NULL || values == NULL || count < 0)
+    {
+      return -EINVAL;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (buffered)
+    {
+      data = priv->output;
+    }
+  else
+    {
+      ret = pi4ioe5v6408_read(priv, PI4IOE5V6408_REG_INPUT, &data);
+      if (ret < 0)
+        {
+          nxmutex_unlock(&priv->lock);
+          return ret;
+        }
+    }
+
+  for (i = 0; i < count; i++)
+    {
+      ret = pi4ioe5v6408_checkpin(pins[i]);
+      if (ret < 0)
+        {
+          nxmutex_unlock(&priv->lock);
+          return ret;
+        }
+
+      values[i] = (data & (1 << pins[i])) != 0;
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+
+static int pi4ioe5v6408_multireadpin(FAR struct ioexpander_dev_s *dev,
+                                     FAR const uint8_t *pins,
+                                     FAR bool *values, int count)
+{
+  return pi4ioe5v6408_multiread(dev, pins, values, count, false);
+}
+
+static int pi4ioe5v6408_multireadbuf(FAR struct ioexpander_dev_s *dev,
+                                     FAR const uint8_t *pins,
+                                     FAR bool *values, int count)
+{
+  return pi4ioe5v6408_multiread(dev, pins, values, count, true);
+}
+#endif
+
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+static void pi4ioe5v6408_irqworker(FAR void *arg)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)arg;
+  uint8_t status = 0;
+  int ret;
+  int i;
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret >= 0)
+    {
+      ret = pi4ioe5v6408_read(priv, PI4IOE5V6408_REG_INT_STATUS, &status);
+      nxmutex_unlock(&priv->lock);
+    }
+
+  if (ret >= 0)
+    {
+      for (i = 0; i < CONFIG_PI4IOE5V6408_INT_NCALLBACKS; i++)
+        {
+          ioe_pinset_t match = priv->callbacks[i].pinset & status;
+
+          if (match != 0 && priv->callbacks[i].callback != NULL)
+            {
+              priv->callbacks[i].callback(&priv->dev, match,
+                                          priv->callbacks[i].arg);
+            }
+        }
+    }
+
+  priv->config->enable(priv->config, true);
+}
+
+static int pi4ioe5v6408_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)arg;
+  int ret;
+
+  if (work_available(&priv->work))
+    {
+      priv->config->enable(priv->config, false);
+      ret = work_queue(HPWORK, &priv->work, pi4ioe5v6408_irqworker,
+                       priv, 0);
+      if (ret < 0)
+        {
+          priv->config->enable(priv->config, true);
+          return ret;
+        }
+    }
+
+  return OK;
+}
+
+static FAR void *pi4ioe5v6408_attach(FAR struct ioexpander_dev_s *dev,
+                                     ioe_pinset_t pinset,
+                                     ioe_callback_t callback, FAR void *arg)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  FAR void *handle = NULL;
+  int ret;
+  int i;
+
+  if (callback == NULL || pinset == 0)
+    {
+      return NULL;
+    }
+
+  pinset &= (1 << PI4IOE5V6408_NPINS) - 1;
+  if (pinset == 0)
+    {
+      return NULL;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return NULL;
+    }
+
+  for (i = 0; i < CONFIG_PI4IOE5V6408_INT_NCALLBACKS; i++)
+    {
+      if (priv->callbacks[i].callback == NULL)
+        {
+          priv->callbacks[i].pinset = pinset;
+          priv->callbacks[i].callback = callback;
+          priv->callbacks[i].arg = arg;
+          handle = &priv->callbacks[i];
+          break;
+        }
+    }
+
+  nxmutex_unlock(&priv->lock);
+  return handle;
+}
+
+static int pi4ioe5v6408_detach(FAR struct ioexpander_dev_s *dev,
+                               FAR void *handle)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv =
+    (FAR struct pi4ioe5v6408_dev_s *)dev;
+  FAR struct pi4ioe5v6408_callback_s *callback =
+    (FAR struct pi4ioe5v6408_callback_s *)handle;
+  int ret;
+
+  if ((uintptr_t)callback < (uintptr_t)&priv->callbacks[0] ||
+      (uintptr_t)callback >=
+      (uintptr_t)&priv->callbacks[CONFIG_PI4IOE5V6408_INT_NCALLBACKS])
+    {
+      return -EINVAL;
+    }
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  callback->pinset = 0;
+  callback->callback = NULL;
+  callback->arg = NULL;
+  nxmutex_unlock(&priv->lock);
+  return OK;
+}
+#endif
+
+static int pi4ioe5v6408_reset(FAR struct pi4ioe5v6408_dev_s *priv)
+{
+  uint8_t chip_id;
+  int ret;
+
+  ret = pi4ioe5v6408_write(priv, PI4IOE5V6408_REG_CHIP_ID_CTRL,
+                           PI4IOE5V6408_SOFT_RESET);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Give the chip time to leave reset before the follow-up register
+   * burst (avoids intermittent -EIO).
+   */
+
+  nxsig_usleep(PI4IOE5V6408_RESET_DELAY_US);
+
+  /* Reading CHIP_ID_CTRL clears the reset interrupt indication. */
+
+  ret = pi4ioe5v6408_read(priv, PI4IOE5V6408_REG_CHIP_ID_CTRL, &chip_id);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  gpioinfo("PI4IOE5V6408 chip ID 0x%02x\n", chip_id);
+
+  ret = pi4ioe5v6408_write(priv, PI4IOE5V6408_REG_DIRECTION,
+                           PI4IOE5V6408_DIR_DEFAULT);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = pi4ioe5v6408_write(priv, PI4IOE5V6408_REG_OUTPUT_HIGH_Z,
+                           PI4IOE5V6408_HIGHZ_DEFAULT);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = pi4ioe5v6408_write(priv, PI4IOE5V6408_REG_PULL_SELECT,
+                           PI4IOE5V6408_PULLSEL_DEFAULT);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = pi4ioe5v6408_write(priv, PI4IOE5V6408_REG_PULL_ENABLE,
+                           PI4IOE5V6408_PULLEN_DEFAULT);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = pi4ioe5v6408_write(priv, PI4IOE5V6408_REG_OUTPUT,
+                           PI4IOE5V6408_OUT_DEFAULT);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  priv->direction      = PI4IOE5V6408_DIR_DEFAULT;
+  priv->high_impedance = PI4IOE5V6408_HIGHZ_DEFAULT;
+  priv->pull_select    = PI4IOE5V6408_PULLSEL_DEFAULT;
+  priv->pull_enable    = PI4IOE5V6408_PULLEN_DEFAULT;
+  priv->output         = PI4IOE5V6408_OUT_DEFAULT;
+  priv->input_default  = PI4IOE5V6408_IN_DEF_DEFAULT;
+  priv->interrupt_mask = PI4IOE5V6408_INT_MASK_DEFAULT;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+FAR struct ioexpander_dev_s *
+pi4ioe5v6408_initialize(FAR struct i2c_master_s *i2c,
+                        FAR struct pi4ioe5v6408_config_s *config)
+{
+  FAR struct pi4ioe5v6408_dev_s *priv;
+  int ret;
+
+  DEBUGASSERT(i2c != NULL && config != NULL);
+
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+  DEBUGASSERT(config->attach != NULL && config->enable != NULL);
+#endif
+
+  priv = kmm_zalloc(sizeof(*priv));
+  if (priv == NULL)
+    {
+      gpioerr("ERROR: Failed to allocate PI4IOE5V6408 instance\n");
+      return NULL;
+    }
+
+  priv->dev.ops = &g_pi4ioe5v6408_ops;
+  priv->i2c     = i2c;
+  priv->config  = config;
+
+  nxmutex_init(&priv->lock);
+
+  ret = pi4ioe5v6408_reset(priv);
+  if (ret < 0)
+    {
+      nxmutex_destroy(&priv->lock);
+      kmm_free(priv);
+      return NULL;
+    }
+
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+  ret = config->attach(config, pi4ioe5v6408_interrupt, priv);
+  if (ret < 0)
+    {
+      nxmutex_destroy(&priv->lock);
+      kmm_free(priv);
+      return NULL;
+    }
+
+  config->enable(config, true);
+#endif
+
+  return &priv->dev;
+}
diff --git a/include/nuttx/ioexpander/pi4ioe5v6408.h 
b/include/nuttx/ioexpander/pi4ioe5v6408.h
new file mode 100644
index 00000000000..dcb9358c0f3
--- /dev/null
+++ b/include/nuttx/ioexpander/pi4ioe5v6408.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+ * include/nuttx/ioexpander/pi4ioe5v6408.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_PI4IOE5V6408_H
+#define __INCLUDE_NUTTX_IOEXPANDER_PI4IOE5V6408_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/compiler.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+#  include <nuttx/irq.h>
+#endif
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define PI4IOE5V6408_I2C_ADDRESS_LOW   0x43
+#define PI4IOE5V6408_I2C_ADDRESS_HIGH  0x44
+#define PI4IOE5V6408_NPINS             8
+
+/* Power-up / soft-reset defaults.
+ * Direction: 1 = output, 0 = input.
+ * Pull select: 1 = pull-up, 0 = pull-down.
+ */
+
+#define PI4IOE5V6408_DIR_DEFAULT       0xff
+#define PI4IOE5V6408_OUT_DEFAULT       0x00
+#define PI4IOE5V6408_HIGHZ_DEFAULT     0xff
+#define PI4IOE5V6408_PULLEN_DEFAULT    0xff
+#define PI4IOE5V6408_PULLSEL_DEFAULT   0x00
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+struct i2c_master_s;
+struct ioexpander_dev_s;
+
+struct pi4ioe5v6408_config_s
+{
+  uint8_t address;
+  uint32_t frequency;
+
+#ifdef CONFIG_PI4IOE5V6408_INT_ENABLE
+  CODE int (*attach)(FAR struct pi4ioe5v6408_config_s *config,
+                     xcpt_t isr, FAR void *arg);
+  CODE void (*enable)(FAR struct pi4ioe5v6408_config_s *config, bool enable);
+#endif
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/****************************************************************************
+ * Name: pi4ioe5v6408_initialize
+ *
+ * Description:
+ *   Instantiate and initialize a PI4IOE5V6408 I2C I/O expander.
+ *
+ * Input Parameters:
+ *   i2c    - I2C controller instance
+ *   config - Persistent board configuration
+ *
+ * Returned Value:
+ *   An ioexpander_dev_s instance on success, NULL on failure.
+ *
+ ****************************************************************************/
+
+FAR struct ioexpander_dev_s *
+pi4ioe5v6408_initialize(FAR struct i2c_master_s *i2c,
+                        FAR struct pi4ioe5v6408_config_s *config);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INCLUDE_NUTTX_IOEXPANDER_PI4IOE5V6408_H */

Reply via email to