/****************************************************************************
 * 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 const uint8_t *pins, FAR const bool *values, int count);
static int pca9538_multireadpin(FAR struct ioexpander_dev_s *dev,
             FAR const 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_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, bool 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;

  if (direction != IOEXPANDER_DIRECTION_IN &&
      direction != IOEXPANDER_DIRECTION_OUT)
    {
      return -EINVAL;
    }

  /* Get exclusive access to the PCA555 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  ret = pca9538_setbit(pca, PCA9538_REG_CONFIG, pin,
                       (direction == IOEXPANDER_DIRECTION_IN));
  nxmutex_unlock(&pca->lock);
  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 *value)
{
  FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev;
  int ret = -EINVAL;

  if (opt == IOEXPANDER_OPTION_INVERT)
    {
      /* Get exclusive access to the PCA555 */

      ret = nxmutex_lock(&pca->lock);
      if (ret < 0)
        {
          return ret;
        }

      ret = pca9538_setbit(pca, PCA9538_REG_POLINV, pin,
                           ((uintptr_t)value == IOEXPANDER_VAL_INVERT));
      nxmutex_unlock(&pca->lock);
    }

  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 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  ret = pca9538_setbit(pca, PCA9538_REG_OUTPUT, pin, value);
  nxmutex_unlock(&pca->lock);
  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 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  ret = pca9538_getbit(pca, PCA9538_REG_INPUT, pin, value);
  nxmutex_unlock(&pca->lock);
  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 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  ret = pca9538_getbit(pca, PCA9538_REG_OUTPUT, pin, value);
  nxmutex_unlock(&pca->lock);
  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 const uint8_t *pins,
                                 FAR const 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 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* 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)
    {
      nxmutex_unlock(&pca->lock);
      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)
        {
          nxmutex_unlock(&pca->lock);
          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);

  nxmutex_unlock(&pca->lock);
  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 const 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 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  ret = pca9538_getmultibits(pca, PCA9538_REG_INPUT,
                             pins, values, count);
  nxmutex_unlock(&pca->lock);
  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 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  ret = pca9538_getmultibits(pca, PCA9538_REG_OUTPUT,
                             pins, values, count);
  nxmutex_unlock(&pca->lock);
  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;
  int ret;

  /* Get exclusive access to the PCA555 */

  ret = nxmutex_lock(&pca->lock);
  if (ret < 0)
    {
      return ret;
    }

  /* 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 */

  nxmutex_unlock(&pca->lock);
  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)
{
  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 = 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

  nxmutex_init(&pcadev->lock);
  return &pcadev->dev;
}

#endif /* CONFIG_IOEXPANDER_PCA9538 */