You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by gn...@apache.org on 2020/02/19 17:53:16 UTC

[incubator-nuttx] branch master updated: driver/ioexpander: adds driver for the PCA9538 I2C ioexpander

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 0f284c3  driver/ioexpander: adds driver for the PCA9538 I2C ioexpander
0f284c3 is described below

commit 0f284c3025c1e65438125abcb2754bfbeb22aca4
Author: Nicholas Chin <ni...@gmail.com>
AuthorDate: Wed Feb 19 10:09:52 2020 -0500

    driver/ioexpander: adds driver for the PCA9538 I2C ioexpander
---
 drivers/ioexpander/Kconfig         |  51 +++
 drivers/ioexpander/Make.defs       |   4 +
 drivers/ioexpander/pca9538.c       | 915 +++++++++++++++++++++++++++++++++++++
 drivers/ioexpander/pca9538.h       | 139 ++++++
 include/nuttx/ioexpander/pca9538.h | 115 +++++
 5 files changed, 1224 insertions(+)

diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig
index 7108d1c..f099524 100644
--- a/drivers/ioexpander/Kconfig
+++ b/drivers/ioexpander/Kconfig
@@ -65,6 +65,57 @@ config PCA9555_RETRY
 
 endif # IOEXPANDER_PCA9555
 
+config IOEXPANDER_PCA9538
+	bool "PCA9538 I2C IO expander"
+	default n
+	depends on I2C
+	---help---
+		Enable support for the NXP PCA9538 IO Expander
+
+if IOEXPANDER_PCA9538
+
+config PCA9538_MULTIPLE
+	bool "Multiple PCA9538 Devices"
+	default n
+	---help---
+		Can be defined to support multiple PCA9538 devices on board.
+
+config PCA9538_INT_ENABLE
+	bool "Enable PCA9538 Interrupt Support"
+	default n
+	select IOEXPANDER_INT_ENABLE
+	---help---
+		Enable driver interrupt functionality
+
+config PCA9538_INT_NCALLBACKS
+	int "Max number of interrupt callbacks"
+	default 4
+	depends on PCA9538_INT_ENABLE
+	---help---
+		This is the maximum number of interrupt callbacks supported
+
+config PCA9538_SHADOW_MODE
+	bool "Use Shadow Mode instead of Read-Modify-Write Operations"
+	default n
+	---help---
+		This setting enables a mode where the output and pin
+		configuration registers are held in RAM.
+		With this for example we do not need to read back the
+		output-register every time we want to change one pin.
+		We do instead change the bit in the internal register
+		and then just write this register to the IO-Expander.
+		This reduces bus traffic and eliminates the problem of
+		EMC-caused toggling of output pins.
+
+config PCA9538_RETRY
+	bool "Retry to send commands and data at I2C communication errors"
+	default n
+	---help---
+		Retry to send commands and data if a I2C-communication
+		error occurs (eg. caused by EMC).
+
+endif # IOEXPANDER_PCA9538
+
 config IOEXPANDER_TCA64XX
 	bool "TCA64XX I2C IO expander"
 	default n
diff --git a/drivers/ioexpander/Make.defs b/drivers/ioexpander/Make.defs
index 8c31079..7c421b1 100644
--- a/drivers/ioexpander/Make.defs
+++ b/drivers/ioexpander/Make.defs
@@ -44,6 +44,10 @@ ifeq ($(CONFIG_IOEXPANDER_PCA9555),y)
   CSRCS += pca9555.c
 endif
 
+ifeq ($(CONFIG_IOEXPANDER_PCA9538),y)
+  CSRCS += pca9538.c
+endif
+
 ifeq ($(CONFIG_IOEXPANDER_TCA64XX),y)
   CSRCS += tca64xx.c
 endif
diff --git a/drivers/ioexpander/pca9538.c b/drivers/ioexpander/pca9538.c
new file mode 100644
index 0000000..0442d7d
--- /dev/null
+++ b/drivers/ioexpander/pca9538.c
@@ -0,0 +1,915 @@
+/****************************************************************************
+ * drivers/ioexpander/pca9538.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 <stdbool.h>
+#include <assert.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/irq.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/ioexpander/ioexpander.h>
+
+#include "pca9538.h"
+
+#if defined(CONFIG_IOEXPANDER_PCA9538)
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#ifndef CONFIG_I2C
+#  warning I2C support is required (CONFIG_I2C)
+#endif
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static inline int pca9538_write(FAR struct pca9538_dev_s *pca,
+             FAR const uint8_t *wbuffer, int wbuflen);
+static inline int pca9538_writeread(FAR struct pca9538_dev_s *pca,
+             FAR const uint8_t *wbuffer, int wbuflen, FAR uint8_t *rbuffer,
+             int rbuflen);
+static int pca9538_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+             int dir);
+static int pca9538_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+             int opt, void *val);
+static int pca9538_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+             bool value);
+static int pca9538_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+             FAR bool *value);
+static int pca9538_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+             FAR bool *value);
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+static int pca9538_multiwritepin(FAR struct ioexpander_dev_s *dev,
+             FAR uint8_t *pins, FAR bool *values, int count);
+static int pca9538_multireadpin(FAR struct ioexpander_dev_s *dev,
+             FAR uint8_t *pins, FAR bool *values, int count);
+static int pca9538_multireadbuf(FAR struct ioexpander_dev_s *dev,
+             FAR uint8_t *pins, FAR bool *values, int count);
+#endif
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+static FAR void *pca9538_attach(FAR struct ioexpander_dev_s *dev,
+             ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg);
+static int pca9538_detach(FAR struct ioexpander_dev_s *dev,
+             FAR void *handle);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+#ifndef CONFIG_PCA9538_MULTIPLE
+/* If only a single PCA9538 device is supported, then the driver state
+ * structure may as well be pre-allocated.
+ */
+
+static struct pca9538_dev_s g_pca9538;
+
+/* Otherwise, we will need to maintain allocated driver instances in a list */
+
+#else
+static struct pca9538_dev_s *g_pca9538list;
+#endif
+
+/* I/O expander vtable */
+
+static const struct ioexpander_ops_s g_pca9538_ops =
+{
+  pca9538_direction,
+  pca9538_option,
+  pca9538_writepin,
+  pca9538_readpin,
+  pca9538_readbuf
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+  , pca9538_multiwritepin
+  , pca9538_multireadpin
+  , pca9538_multireadbuf
+#endif
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+  , pca9538_attach
+  , pca9538_detach
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: pca9538_lock
+ *
+ * Description:
+ *   Get exclusive access to the PCA9538
+ *
+ ****************************************************************************/
+
+static void pca9538_lock(FAR struct pca9538_dev_s *pca)
+{
+  nxsem_wait_uninterruptible(&pca->exclsem);
+}
+
+#define pca9538_unlock(p) nxsem_post(&(p)->exclsem)
+
+/****************************************************************************
+ * Name: pca9538_write
+ *
+ * Description:
+ *   Write to the I2C device.
+ *
+ ****************************************************************************/
+
+static inline int pca9538_write(FAR struct pca9538_dev_s *pca,
+                                FAR const uint8_t *wbuffer, int wbuflen)
+{
+  struct i2c_msg_s msg;
+  int ret;
+
+  /* Setup for the transfer */
+
+  msg.frequency = pca->config->frequency;
+  msg.addr      = pca->config->address;
+  msg.flags     = 0;
+  msg.buffer    = (FAR uint8_t *)wbuffer;  /* Override const */
+  msg.length    = wbuflen;
+
+  /* Then perform the transfer. */
+
+  ret = I2C_TRANSFER(pca->i2c, &msg, 1);
+  return (ret >= 0) ? OK : ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_writeread
+ *
+ * Description:
+ *   Write to then read from the I2C device.
+ *
+ ****************************************************************************/
+
+static inline int pca9538_writeread(FAR struct pca9538_dev_s *pca,
+                                    FAR const uint8_t *wbuffer, int wbuflen,
+                                    FAR uint8_t *rbuffer, int rbuflen)
+{
+  struct i2c_config_s config;
+
+  /* Set up the configuration and perform the write-read operation */
+
+  config.frequency = pca->config->frequency;
+  config.address   = pca->config->address;
+  config.addrlen   = 7;
+
+  return i2c_writeread(pca->i2c, &config, wbuffer,
+                       wbuflen, rbuffer, rbuflen);
+}
+
+/****************************************************************************
+ * Name: pca9538_setbit
+ *
+ * Description:
+ *  Write a bit in a register pair
+ *
+ ****************************************************************************/
+
+static int pca9538_setbit(FAR struct pca9538_dev_s *pca, uint8_t addr,
+                          uint8_t pin, int bitval)
+{
+  uint8_t buf[2];
+  int ret;
+
+  if (pin >= PCA9538_GPIO_NPINS)
+    {
+      return -ENXIO;
+    }
+
+  buf[0] = addr;
+
+#ifdef CONFIG_PCA9538_SHADOW_MODE
+  /* Get the shadowed register value */
+
+  buf[1] = pca->sreg[addr];
+
+#else
+  /* Get the register value from the IO-Expander */
+
+  ret = pca9538_writeread(pca, &buf[0], 1, &buf[1], 1);
+  if (ret < 0)
+    {
+      return ret;
+    }
+#endif
+
+  if (bitval)
+    {
+      buf[1] |= (1 << pin);
+    }
+  else
+    {
+      buf[1] &= ~(1 << pin);
+    }
+
+#ifdef CONFIG_PCA9538_SHADOW_MODE
+  /* Save the new register value in the shadow register */
+
+  pca->sreg[addr] = buf[1];
+#endif
+
+  ret = pca9538_write(pca, buf, 2);
+#ifdef CONFIG_PCA9538_RETRY
+  if (ret != OK)
+    {
+      /* Try again (only once) */
+
+      ret = pca9538_write(pca, buf, 2);
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_getbit
+ *
+ * Description:
+ *  Get a bit from a register pair
+ *
+ ****************************************************************************/
+
+static int pca9538_getbit(FAR struct pca9538_dev_s *pca, uint8_t addr,
+                          uint8_t pin, FAR bool *val)
+{
+  uint8_t buf;
+  int ret;
+
+  if (pin >= PCA9538_GPIO_NPINS)
+    {
+      return -ENXIO;
+    }
+
+  ret = pca9538_writeread(pca, &addr, 1, &buf, 1);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+#ifdef CONFIG_PCA9538_SHADOW_MODE
+  /* Save the new register value in the shadow register */
+
+  pca->sreg[addr] = buf;
+#endif
+
+  *val = (buf >> pin) & 1;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: pca9538_direction
+ *
+ * Description:
+ *   Set the direction of an ioexpander pin. Required.
+ *
+ * Input Parameters:
+ *   dev - Device-specific state data
+ *   pin - The index of the pin to alter in this call
+ *   dir - One of the IOEXPANDER_DIRECTION_ macros
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                             int direction)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  int ret;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+  ret = pca9538_setbit(pca, PCA9538_REG_CONFIG, pin,
+                       (direction == IOEXPANDER_DIRECTION_IN));
+  pca9538_unlock(pca);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_option
+ *
+ * Description:
+ *   Set pin options. Required.
+ *   Since all IO expanders have various pin options, this API allows setting
+ *     pin options in a flexible way.
+ *
+ * Input Parameters:
+ *   dev - Device-specific state data
+ *   pin - The index of the pin to alter in this call
+ *   opt - One of the IOEXPANDER_OPTION_ macros
+ *   val - The option's value
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                          int opt, FAR void *val)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  int ret = -EINVAL;
+
+  if (opt == IOEXPANDER_OPTION_INVERT)
+    {
+      int ival = (int)((intptr_t)val);
+
+      /* Get exclusive access to the PCA555 */
+
+      pca9538_lock(pca);
+      ret = pca9538_setbit(pca, PCA9538_REG_POLINV, pin, ival);
+      pca9538_unlock(pca);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_writepin
+ *
+ * Description:
+ *   Set the pin level. Required.
+ *
+ * Input Parameters:
+ *   dev - Device-specific state data
+ *   pin - The index of the pin to alter in this call
+ *   val - The pin level. Usually TRUE will set the pin high,
+ *         except if OPTION_INVERT has been set on this pin.
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                            bool value)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  int ret;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+  ret = pca9538_setbit(pca, PCA9538_REG_OUTPUT, pin, value);
+  pca9538_unlock(pca);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_readpin
+ *
+ * Description:
+ *   Read the actual PIN level. This can be different from the last value
+ *   written to this pin. Required.
+ *
+ * Input Parameters:
+ *   dev    - Device-specific state data
+ *   pin    - The index of the pin
+ *   valptr - Pointer to a buffer where the pin level is stored.
+ *            Usually TRUE if the pin is high, except if OPTION_INVERT
+ *            has been set on this pin.
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                           FAR bool *value)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  int ret;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+  ret = pca9538_getbit(pca, PCA9538_REG_INPUT, pin, value);
+  pca9538_unlock(pca);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_readbuf
+ *
+ * Description:
+ *   Read the buffered pin level.
+ *   This can be different from the actual pin state. Required.
+ *
+ * Input Parameters:
+ *   dev    - Device-specific state data
+ *   pin    - The index of the pin
+ *   valptr - Pointer to a buffer where the level is stored.
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                           FAR bool *value)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  int ret;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+  ret = pca9538_getbit(pca, PCA9538_REG_OUTPUT, pin, value);
+  pca9538_unlock(pca);
+  return ret;
+}
+
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+
+/****************************************************************************
+ * Name: pca9538_getmultibits
+ *
+ * Description:
+ *  Read multiple bits from PCA9538 registers.
+ *
+ ****************************************************************************/
+
+static int pca9538_getmultibits(FAR struct pca9538_dev_s *pca, uint8_t addr,
+                                FAR uint8_t *pins, FAR bool *values,
+                                int count)
+{
+  uint8_t buf[2];
+  int ret = OK;
+  int i;
+  int index;
+  int pin;
+
+  ret = pca9538_writeread(pca, &addr, 1, buf, 2);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+#ifdef CONFIG_PCA9538_SHADOW_MODE
+  /* Save the new register value in the shadow register */
+
+  pca->sreg[addr]   = buf[0];
+  pca->sreg[addr + 1] = buf[1];
+#endif
+
+  /* Read the requested bits */
+
+  for (i = 0; i < count; i++)
+    {
+      index = 0;
+      pin   = pins[i];
+      if (pin >= PCA9538_GPIO_NPINS)
+        {
+          return -ENXIO;
+        }
+
+      values[i] = (buf[index] >> pin) & 1;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: pca9538_multiwritepin
+ *
+ * Description:
+ *   Set the pin level for multiple pins. This routine may be faster than
+ *   individual pin accesses. Optional.
+ *
+ * Input Parameters:
+ *   dev - Device-specific state data
+ *   pins - The list of pin indexes to alter in this call
+ *   val - The list of pin levels.
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_multiwritepin(FAR struct ioexpander_dev_s *dev,
+                                 FAR uint8_t *pins, FAR bool *values,
+                                 int count)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  uint8_t addr = PCA9538_REG_OUTPUT;
+  uint8_t buf[3];
+  int ret;
+  int i;
+  int index;
+  int pin;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+
+  /* Start by reading both registers, whatever the pins to change. We could
+   * attempt to read one port only if all pins were on the same port, but
+   * this would not save much.
+   */
+
+#ifndef CONFIG_PCA9538_SHADOW_MODE
+  ret = pca9538_writeread(pca, &addr, 1, &buf[1], 2);
+  if (ret < 0)
+    {
+      pca9538_unlock(pca);
+      return ret;
+    }
+#else
+  /* In Shadow-Mode we "read" the pin status from the shadow registers */
+
+  buf[1] = pca->sreg[addr];
+  buf[2] = pca->sreg[addr + 1];
+#endif
+
+  /* Apply the user defined changes */
+
+  for (i = 0; i < count; i++)
+    {
+      index = 1;
+      pin = pins[i];
+      if (pin >= PCA9538_GPIO_NPINS)
+        {
+          pca9538_unlock(pca);
+          return -ENXIO;
+        }
+
+      if (values[i])
+        {
+          buf[index] |= (1 << pin);
+        }
+      else
+        {
+          buf[index] &= ~(1 << pin);
+        }
+    }
+
+  /* Now write back the new pins states */
+
+  buf[0] = addr;
+#ifdef CONFIG_PCA9538_SHADOW_MODE
+  /* Save the new register values in the shadow register */
+
+  pca->sreg[addr] = buf[1];
+  pca->sreg[addr + 1] = buf[2];
+#endif
+  ret = pca9538_write(pca, buf, 3);
+
+  pca9538_unlock(pca);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_multireadpin
+ *
+ * Description:
+ *   Read the actual level for multiple pins. This routine may be faster than
+ *   individual pin accesses. Optional.
+ *
+ * Input Parameters:
+ *   dev    - Device-specific state data
+ *   pin    - The list of pin indexes to read
+ *   valptr - Pointer to a buffer where the pin levels are stored.
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_multireadpin(FAR struct ioexpander_dev_s *dev,
+                                FAR uint8_t *pins, FAR bool *values,
+                                int count)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  int ret;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+  ret = pca9538_getmultibits(pca, PCA9538_REG_INPUT,
+                             pins, values, count);
+  pca9538_unlock(pca);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: pca9538_multireadbuf
+ *
+ * Description:
+ *   Read the buffered level of multiple pins. This routine may be faster
+ *   than individual pin accesses. Optional.
+ *
+ * Input Parameters:
+ *   dev    - Device-specific state data
+ *   pin    - The index of the pin
+ *   valptr - Pointer to a buffer where the buffered levels are stored.
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_multireadbuf(FAR struct ioexpander_dev_s *dev,
+                                FAR uint8_t *pins, FAR bool *values,
+                                int count)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  int ret;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+  ret = pca9538_getmultibits(pca, PCA9538_REG_OUTPUT,
+                             pins, values, count);
+  pca9538_unlock(pca);
+  return ret;
+}
+
+#endif
+
+#ifdef CONFIG_PCA9538_INT_ENABLE
+
+/****************************************************************************
+ * Name: pca9538_attach
+ *
+ * Description:
+ *   Attach and enable a pin interrupt callback function.
+ *
+ * Input Parameters:
+ *   dev      - Device-specific state data
+ *   pinset   - The set of pin events that will generate the callback
+ *   callback - The pointer to callback function.  NULL will detach the
+ *              callback.
+ *   arg      - User-provided callback argument
+ *
+ * Returned Value:
+ *   A non-NULL handle value is returned on success.  This handle may be
+ *   used later to detach and disable the pin interrupt.
+ *
+ ****************************************************************************/
+
+static FAR void *pca9538_attach(FAR struct ioexpander_dev_s *dev,
+                                ioe_pinset_t pinset, ioe_callback_t callback,
+                                FAR void *arg)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  FAR void *handle = NULL;
+  int i;
+
+  /* Get exclusive access to the PCA555 */
+
+  pca9538_lock(pca);
+
+  /* Find and available in entry in the callback table */
+
+  for (i = 0; i < CONFIG_PCA9538_INT_NCALLBACKS; i++)
+    {
+      /* Is this entry available (i.e., no callback attached) */
+
+      if (pca->cb[i].cbfunc == NULL)
+        {
+          /* Yes.. use this entry */
+
+          pca->cb[i].pinset = pinset;
+          pca->cb[i].cbfunc = callback;
+          pca->cb[i].cbarg  = arg;
+          handle            = &pca->cb[i];
+          break;
+        }
+    }
+
+  /* Add this callback to the table */
+
+  pca9538_unlock(pca);
+  return handle;
+}
+
+/****************************************************************************
+ * Name: pca9538_detach
+ *
+ * Description:
+ *   Detach and disable a pin interrupt callback function.
+ *
+ * Input Parameters:
+ *   dev      - Device-specific state data
+ *   handle   - The non-NULL opaque value return by pca9538_attch()
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int pca9538_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
+  FAR struct pca9538_callback_s *cb =
+                                    (FAR struct pca9538_callback_s *)handle;
+
+  DEBUGASSERT(pca != NULL && cb != NULL);
+  DEBUGASSERT((uintptr_t)cb >= (uintptr_t)&pca->cb[0] &&
+    (uintptr_t)cb <= (uintptr_t)&pca->cb[CONFIG_TCA64XX_INT_NCALLBACKS - 1]);
+
+  UNUSED(pca);
+
+  cb->pinset = 0;
+  cb->cbfunc = NULL;
+  cb->cbarg  = NULL;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: pca9538_irqworker
+ *
+ * Description:
+ *   Handle GPIO interrupt events (this function actually executes in the
+ *   context of the worker thread).
+ *
+ ****************************************************************************/
+
+static void pca9538_irqworker(void *arg)
+{
+  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)arg;
+  uint8_t addr = PCA9538_REG_INPUT;
+  uint8_t buf[2];
+  ioe_pinset_t pinset;
+  int ret;
+  int i;
+
+  /* Read inputs */
+
+  ret = pca9538_writeread(pca, &addr, 1, buf, 2);
+  if (ret == OK)
+    {
+#ifdef CONFIG_PCA9538_SHADOW_MODE
+      /* Don't forget to update the shadow registers at this point */
+
+      pca->sreg[addr]   = buf[0];
+      pca->sreg[addr + 1] = buf[1];
+#endif
+      /* Create a 16-bit pinset */
+
+      pinset = ((unsigned int)buf[0] << 8) | buf[1];
+
+      /* Perform pin interrupt callbacks */
+
+      for (i = 0; i < CONFIG_PCA9538_INT_NCALLBACKS; i++)
+        {
+          /* Is this entry valid (i.e., callback attached)?  If so, did
+           * any of the requested pin interrupts occur?
+           */
+
+          if (pca->cb[i].cbfunc != NULL)
+            {
+              /* Did any of the requested pin interrupts occur? */
+
+              ioe_pinset_t match = pinset & pca->cb[i].pinset;
+              if (match != 0)
+                {
+                  /* Yes.. perform the callback */
+
+                  pca->cb[i].cbfunc(&pca->dev, match,
+                                    pca->cb[i].cbarg);
+                }
+            }
+        }
+    }
+
+  /* Re-enable interrupts */
+
+  pca->config->enable(pca->config, TRUE);
+}
+
+/****************************************************************************
+ * Name: pca9538_interrupt
+ *
+ * Description:
+ *   Handle GPIO interrupt events (this function executes in the
+ *   context of the interrupt).
+ *
+ ****************************************************************************/
+
+static int pca9538_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  register FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)arg;
+
+  /* In complex environments, we cannot do I2C transfers from the interrupt
+   * handler because semaphores are probably used to lock the I2C bus.  In
+   * this case, we will defer processing to the worker thread.  This is also
+   * much kinder in the use of system resources and is, therefore, probably
+   * a good thing to do in any event.
+   */
+
+  /* Notice that further GPIO interrupts are disabled until the work is
+   * actually performed.  This is to prevent overrun of the worker thread.
+   * Interrupts are re-enabled in pca9538_irqworker() when the work is
+   * completed.
+   */
+
+  if (work_available(&pca->work))
+    {
+      pca->config->enable(pca->config, FALSE);
+      work_queue(HPWORK, &pca->work, pca9538_irqworker,
+                 (FAR void *)pca, 0);
+    }
+
+  return OK;
+}
+
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: pca9538_initialize
+ *
+ * Description:
+ *   Initialize a PCA9538 I2C device.
+ *
+ ****************************************************************************/
+
+FAR struct ioexpander_dev_s *pca9538_initialize
+      (FAR struct i2c_master_s *i2cdev, FAR struct pca9538_config_s *config)
+{
+  FAR struct pca9538_dev_s *pcadev;
+
+  DEBUGASSERT(i2cdev != NULL && config != NULL &&
+                config->set_nreset_pin != NULL);
+
+  config->set_nreset_pin(true);
+
+#ifdef CONFIG_PCA9538_MULTIPLE
+  /* Allocate the device state structure */
+
+  pcadev = (FAR struct pca9538_dev_s *)kmm_zalloc
+                                            (sizeof(struct pca9538_dev_s));
+  if (!pcadev)
+    {
+      return NULL;
+    }
+
+  /* And save the device structure in the list of PCA9538 so that we can
+   * find it later.
+   */
+
+  pcadev->flink = g_pca9538list;
+  g_pca9538list = pcadev;
+
+#else
+  /* Use the one-and-only PCA9538 driver instance */
+
+  pcadev = &g_pca9538;
+#endif
+
+  /* Initialize the device state structure */
+
+  pcadev->i2c     = i2cdev;
+  pcadev->dev.ops = &g_pca9538_ops;
+  pcadev->config  = config;
+
+#ifdef CONFIG_PCA9538_INT_ENABLE
+  DEBUGASSERT(pcadev->config->attach != NULL &&
+                pcadev->config->enable != NULL);
+
+  pcadev->config->attach(pcadev->config, pca9538_interrupt, pcadev);
+  pcadev->config->enable(pcadev->config, TRUE);
+#endif
+
+  nxsem_init(&pcadev->exclsem, 0, 1);
+  return &pcadev->dev;
+}
+
+#endif /* CONFIG_IOEXPANDER_PCA9538 */
diff --git a/drivers/ioexpander/pca9538.h b/drivers/ioexpander/pca9538.h
new file mode 100644
index 0000000..1c7f55f
--- /dev/null
+++ b/drivers/ioexpander/pca9538.h
@@ -0,0 +1,139 @@
+/****************************************************************************
+ * drivers/ioexpander/pca9538.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_IOEXPANDER_PCA9538_H
+#define __DRIVERS_IOEXPANDER_PCA9538_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <nuttx/wdog.h>
+#include <nuttx/clock.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/ioexpander/ioexpander.h>
+#include <nuttx/ioexpander/pca9538.h>
+
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/irq.h>
+
+#if defined(CONFIG_IOEXPANDER) && defined(CONFIG_IOEXPANDER_PCA9538)
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+/* Prerequisites:
+ *   CONFIG_I2C
+ *     I2C support is required
+ *   CONFIG_IOEXPANDER
+ *     Enables support for the PCA9538 I/O expander
+ *
+ * CONFIG_IOEXPANDER_PCA9538
+ *   Enables support for the PCA9538 driver (Needs CONFIG_INPUT)
+ * CONFIG_PCA9538_MULTIPLE
+ *   Can be defined to support multiple PCA9538 devices on board.
+ * CONFIG_PCA9538_INT_NCALLBACKS
+ *   Maximum number of supported pin interrupt callbacks.
+ */
+
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+#  ifndef CONFIG_PCA9538_INT_NCALLBACKS
+#    define CONFIG_PCA9538_INT_NCALLBACKS 4
+#  endif
+#endif
+
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+#  ifndef CONFIG_SCHED_WORKQUEUE
+#    error Work queue support required.  CONFIG_SCHED_WORKQUEUE must be selected.
+#  endif
+#endif
+
+#undef CONFIG_PCA9538_REFCNT
+
+/* PCA9538 Resources ********************************************************/
+
+#define PCA9538_GPIO_NPINS 8
+
+#ifndef CONFIG_I2C
+#error "CONFIG_I2C is required by pca9538"
+#endif
+
+#define PCA9538_MAXDEVS             4
+
+/* I2C frequency */
+
+#define PCA9538_I2C_MAXFREQUENCY    400000       /* 400KHz */
+
+/* PCA9538 Registers ********************************************************/
+
+#define PCA9538_REG_INPUT  0x00
+#define PCA9538_REG_OUTPUT 0x01
+#define PCA9538_REG_POLINV 0x02
+#define PCA9538_REG_CONFIG 0x03
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+/* This type represents on registered pin interrupt callback */
+
+struct pca9538_callback_s
+{
+  ioe_pinset_t pinset;        /* Set of pin interrupts that will generate
+                                * the callback. */
+  ioe_callback_t cbfunc;      /* The saved callback function pointer */
+  FAR void *cbarg;            /* Callback argument */
+};
+#endif
+
+/* This structure represents the state of the PCA9538 driver */
+
+struct pca9538_dev_s
+{
+  struct ioexpander_dev_s      dev;     /* Nested structure to allow casting
+                                         * as public gpio expander. */
+#ifdef CONFIG_PCA9538_SHADOW_MODE
+  uint8_t sreg[8];                      /* Shadowed registers of the PCA9538 */
+#endif
+#ifdef CONFIG_PCA9538_MULTIPLE
+  FAR struct pca9538_dev_s    *flink;   /* Supports a singly linked list of drivers */
+#endif
+  FAR struct pca9538_config_s *config;  /* Board configuration data */
+  FAR struct i2c_master_s     *i2c;     /* Saved I2C driver instance */
+  sem_t                        exclsem; /* Mutual exclusion */
+
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+  struct work_s work;                   /* Supports the interrupt handling "bottom half" */
+
+  /* Saved callback information for each I/O expander client */
+
+  struct pca9538_callback_s cb[CONFIG_PCA9538_INT_NCALLBACKS];
+#endif
+};
+
+#endif /* CONFIG_IOEXPANDER && CONFIG_IOEXPANDER_PCA9538 */
+#endif /* __DRIVERS_IOEXPANDER_PCA9538_H */
diff --git a/include/nuttx/ioexpander/pca9538.h b/include/nuttx/ioexpander/pca9538.h
new file mode 100644
index 0000000..d9a300a
--- /dev/null
+++ b/include/nuttx/ioexpander/pca9538.h
@@ -0,0 +1,115 @@
+/****************************************************************************
+ * include/nuttx/ioexpander/pca9538.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_IOEXPANDER_PCA9538_H
+#define __INCLUDE_NUTTX_IOEXPANDER_PCA9538_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/i2c/i2c_master.h>
+
+#include <stdbool.h>
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* A reference to a structure of this type must be passed to the pca9538
+ * driver when the driver is instantiated. This structure provides
+ * information about the configuration of the pca9538 and provides some
+ * board-specific hooks.
+ *
+ * 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. The
+ * memory must be writeable because, under certain circumstances, the driver
+ * may modify the frequency.
+ */
+
+struct pca9538_config_s
+{
+  /* Device characterization */
+
+  uint8_t address;     /* 7-bit I2C address (only bits 0-6 used) */
+  uint32_t frequency;  /* I2C or SPI frequency */
+
+  /* Sets the state of the PCA9538's nReset pin */
+
+  CODE void (*set_nreset_pin)(bool state);
+
+#ifdef CONFIG_IOEXPANDER_INT_ENABLE
+  /* If multiple pca9538 devices are supported, then an IRQ number must
+   * be provided for each so that their interrupts can be distinguished.
+   */
+
+  /* IRQ/GPIO access callbacks.  These operations all hidden behind
+   * callbacks to isolate the pca9538 driver from differences in GPIO
+   * interrupt handling by varying boards and MCUs.
+   *
+   * attach  - Attach the pca9538 interrupt handler to the GPIO interrupt
+   * enable  - Enable or disable the GPIO interrupt
+   */
+
+  CODE int  (*attach)(FAR struct pca9538_config_s *state, xcpt_t isr,
+                      FAR void *arg);
+  CODE void (*enable)(FAR struct pca9538_config_s *state, bool enable);
+#endif
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: pca9538_initialize
+ *
+ * Description:
+ *   Instantiate and configure the pca9538 device driver to use the provided
+ *   I2C device
+ *   instance.
+ *
+ * Input Parameters:
+ *   dev     - An I2C driver instance
+ *   minor   - The device i2c address
+ *   config  - Persistent board configuration data
+ *
+ * Returned Value:
+ *   an ioexpander_dev_s instance on success, NULL on failure.
+ *
+ ****************************************************************************/
+
+FAR struct ioexpander_dev_s *pca9538_initialize(FAR struct i2c_master_s *dev,
+                                        FAR struct pca9538_config_s *config);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INCLUDE_NUTTX_IOEXPANDER_PCA9538_H */