You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by ac...@apache.org on 2024/01/23 20:15:31 UTC

(nuttx) branch master updated: ioexpander: add support for iC-JX expander

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 22110b3b83 ioexpander: add support for iC-JX expander
22110b3b83 is described below

commit 22110b3b83e51ee9a5858223a88d038b87ee2c05
Author: Michal Lenc <mi...@seznam.cz>
AuthorDate: Tue Jan 23 14:46:08 2024 +0100

    ioexpander: add support for iC-JX expander
    
    This commit adds basic support for iC-JX expander in SPI mode. The
    expander is functional and supports both input and output pins. Further
    expander functionalities (filtering, adc, interrupt support) are not
    yet implemented.
    
    Signed-off-by: Michal Lenc <mi...@seznam.cz>
---
 drivers/ioexpander/Kconfig      |  18 +
 drivers/ioexpander/Make.defs    |   4 +
 drivers/ioexpander/icjx.c       | 750 ++++++++++++++++++++++++++++++++++++++++
 drivers/ioexpander/icjx.h       | 102 ++++++
 include/nuttx/ioexpander/icjx.h |  96 +++++
 5 files changed, 970 insertions(+)

diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig
index 77c9b2eb96..f1f6cb9b25 100644
--- a/drivers/ioexpander/Kconfig
+++ b/drivers/ioexpander/Kconfig
@@ -56,6 +56,24 @@ config IOEXPANDER_DUMMY_INT_POLLDELAY
 
 endif # IOEXPANDER_DUMMY
 
+config IOEXPANDER_ICJX
+	bool "iC-JX SPI IO expander"
+	default n
+	depends on SPI
+	---help---
+		Enable support for the iC-JX expander from iC-Haus GmbH manufacturer.
+		SPI peripheral is used for the communication.
+
+if IOEXPANDER_ICJX
+
+config iC-JX_MULTIPLE
+	bool "Multiple iC-JX Devices"
+	default n
+	---help---
+		Can be defined to support multiple iC-JX devices on board.
+
+endif # IOEXPANDER_ICJX
+
 config IOEXPANDER_ISO1H812G
 	bool "ISO1H812G SPI IO expander"
 	default n
diff --git a/drivers/ioexpander/Make.defs b/drivers/ioexpander/Make.defs
index 2777af4415..cdf0921ccd 100644
--- a/drivers/ioexpander/Make.defs
+++ b/drivers/ioexpander/Make.defs
@@ -32,6 +32,10 @@ ifeq ($(CONFIG_IOEXPANDER_DUMMY),y)
   CSRCS += ioe_dummy.c
 endif
 
+ifeq ($(CONFIG_IOEXPANDER_ICJX),y)
+  CSRCS += icjx.c
+endif
+
 ifeq ($(CONFIG_IOEXPANDER_ISO1H812G),y)
   CSRCS += iso1h812g.c
 endif
diff --git a/drivers/ioexpander/icjx.c b/drivers/ioexpander/icjx.c
new file mode 100644
index 0000000000..bff1975d18
--- /dev/null
+++ b/drivers/ioexpander/icjx.c
@@ -0,0 +1,750 @@
+/****************************************************************************
+ * drivers/ioexpander/icjx.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 <assert.h>
+#include <errno.h>
+#include <debug.h>
+#include <sys/param.h>
+
+#include <nuttx/kmalloc.h>
+#include <nuttx/arch.h>
+#include <nuttx/spi/spi.h>
+#include <nuttx/ioexpander/ioexpander.h>
+#include <nuttx/ioexpander/icjx.h>
+
+#include "icjx.h"
+
+#ifdef CONFIG_IOEXPANDER_ICJX
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define ICJX_NOP     0x00
+#define ICJX_RNW     0x01
+#define ICJX_NOB     0x0f
+#define ICJX_CONTROL 0x59
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct icjx_dev_s
+{
+  struct ioexpander_dev_s dev;         /* Nested structure to allow casting
+                                        * as public gpio expander. */
+  FAR struct icjx_config_s *config;    /* Board configuration data */
+  FAR struct spi_dev_s *spi;           /* Saved SPI driver instance */
+  uint16_t outpins;
+  uint16_t outstate;
+  mutex_t lock;
+};
+
+/****************************************************************************
+ * Private Function Protototypes
+ ****************************************************************************/
+
+/* SPI helpers */
+
+static void icjx_select(FAR struct spi_dev_s *spi,
+                        FAR struct icjx_config_s *config, int bits);
+static void icjx_deselect(FAR struct spi_dev_s *spi,
+                          FAR struct icjx_config_s *config);
+
+/* Read/Write helpers */
+
+static int icjx_read(FAR struct icjx_dev_s *priv, uint8_t reg,
+                     uint8_t *data);
+static int icjx_write(FAR struct icjx_dev_s *priv, uint8_t reg,
+                      uint8_t data);
+
+/* I/O Expander Methods */
+
+static int icjx_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                          int dir);
+static int icjx_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                       int opt, void *regval);
+static int icjx_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                         bool value);
+static int icjx_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                        FAR bool *value);
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+static int icjx_multiwritepin(FAR struct ioexpander_dev_s *dev,
+                              FAR const uint8_t *pins, FAR bool *values,
+                              int count);
+static int icjx_multireadpin(FAR struct ioexpander_dev_s *dev,
+                             FAR const uint8_t *pins, FAR bool *values,
+                             int count);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+#ifndef CONFIG_ICJX_MULTIPLE
+/* If only a single device is supported, then the driver state structure may
+ * as well be pre-allocated.
+ */
+
+static struct icjx_dev_s g_icjx;
+#endif
+
+/* I/O expander vtable */
+
+static const struct ioexpander_ops_s g_icjx_ops =
+{
+  icjx_direction,
+  icjx_option,
+  icjx_writepin,
+  icjx_readpin,
+  icjx_readpin
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+  , icjx_multiwritepin
+  , icjx_multireadpin
+  , icjx_multireadpin
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: icjx_select
+ *
+ * Description:
+ *   Select the SPI, locking and  re-configuring if necessary
+ *
+ * Input Parameters:
+ *   spi    - Reference to the SPI driver structure
+ *   config - Reference to iC-JX configuration structure
+ *   bits   - Number of SPI bits
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void icjx_select(FAR struct spi_dev_s *spi,
+                        FAR struct icjx_config_s *config, int bits)
+{
+  /* Select iC-JX chip (locking the SPI bus in case there are multiple
+   * devices competing for the SPI bus
+   */
+
+  SPI_LOCK(spi, true);
+  SPI_SELECT(spi, SPIDEV_EXPANDER(config->id), true);
+
+  /* Now make sure that the SPI bus is configured for the iC-JX (it
+   * might have gotten configured for a different device while unlocked)
+   */
+
+  SPI_SETMODE(spi, config->mode);
+  SPI_SETBITS(spi, bits);
+  SPI_SETFREQUENCY(spi, config->frequency);
+}
+
+/****************************************************************************
+ * Name: icjx_deselect
+ *
+ * Description:
+ *   De-select the SPI
+ *
+ * Input Parameters:
+ *   spi    - Reference to the SPI driver structure
+ *   config - Reference to iC-JX configuration structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void icjx_deselect(FAR struct spi_dev_s *spi,
+                          FAR struct icjx_config_s *config)
+{
+  /* De-select iC-JX chip and relinquish the SPI bus. */
+
+  SPI_SELECT(spi, SPIDEV_EXPANDER(config->id), false);
+  SPI_LOCK(spi, false);
+}
+
+/****************************************************************************
+ * Name: icjx_read
+ *
+ * Description:
+ *   Helper function for register read operation.
+ *
+ * Input Parameters:
+ *   priv  - Pointer to icjx_dev_s structure
+ *   reg   - Register offset (see icjx.h)
+ *   data  - Pointer to read data
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int icjx_read(FAR struct icjx_dev_s *priv, uint8_t reg, uint8_t *data)
+{
+  uint8_t startaddr;
+  uint8_t tx_buffer[5];
+  uint8_t rx_buffer[5];
+
+  startaddr =  (priv->config->addr << 6) | (reg << 1) | ICJX_RNW;
+  tx_buffer[0] = startaddr;
+  tx_buffer[1] = ICJX_NOP;
+  tx_buffer[2] = ICJX_NOB;
+
+  icjx_select(priv->spi, priv->config, 8);
+  SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, 3);
+
+  *data = rx_buffer[2];
+
+  if (priv->config->verification)
+    {
+      tx_buffer[0] = rx_buffer[2];
+      tx_buffer[1] = ICJX_CONTROL;
+      SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, 2);
+    }
+
+  icjx_deselect(priv->spi, priv->config);
+
+  if (priv->config->verification)
+    {
+      if (rx_buffer[0] != startaddr)
+        {
+          gpioerr("ERROR: Data verification error for register 0x%x!\n",
+                  reg);
+          return -EIO;
+        }
+
+      if (rx_buffer[1] != ICJX_CONTROL)
+        {
+          gpioerr("ERROR: Control byte verification error for register"
+                  "0x%x!\n", reg);
+          return -EIO;
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: icjx_write
+ *
+ * Description:
+ *   Helper function for register write operation.
+ *
+ * Input Parameters:
+ *   priv  - Pointer to icjx_dev_s structure
+ *   reg   - Register offset (see icjx.h)
+ *   data  - Data to be written
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int icjx_write(FAR struct icjx_dev_s *priv, uint8_t reg, uint8_t data)
+{
+  uint8_t startaddr;
+  uint8_t bytes_to_exchange;
+  uint8_t tx_buffer[5];
+  uint8_t rx_buffer[5];
+
+  bytes_to_exchange = 3;
+  startaddr =  (priv->config->addr << 6) | (reg << 1);
+  tx_buffer[0] = startaddr;
+  tx_buffer[1] = ICJX_NOB;
+  tx_buffer[2] = data;
+
+  if (priv->config->verification)
+    {
+      tx_buffer[3] = startaddr;
+      tx_buffer[4] = ICJX_CONTROL;
+      bytes_to_exchange = 5;
+    }
+
+  icjx_select(priv->spi, priv->config, 8);
+  SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, bytes_to_exchange);
+  icjx_deselect(priv->spi, priv->config);
+
+  if (priv->config->verification)
+    {
+      if (rx_buffer[2] != ICJX_NOB)
+        {
+          gpioerr("ERROR: Start address verification error for register"
+                  "0x%x!\n", reg);
+          return -EIO;
+        }
+
+      if (rx_buffer[3] != data)
+        {
+          gpioerr("ERROR: Data verification error for register 0x%x!\n",
+                  reg);
+          return -EIO;
+        }
+
+      if (rx_buffer[4] != ICJX_CONTROL)
+        {
+          gpioerr("ERROR: Data verification error for register 0x%x!\n",
+                  reg);
+          return -EIO;
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: icjx_direction
+ *
+ * Description:
+ *   iC-JX is only input pin. However interface is provided in order
+ *   to avoid system falls if called.
+ *
+ * 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 icjx_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                          int dir)
+{
+  FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev;
+  uint8_t outpins;
+  uint8_t out_set;
+  uint8_t regaddr;
+  int ret;
+
+  if (dir != IOEXPANDER_DIRECTION_IN &&
+      dir != IOEXPANDER_DIRECTION_OUT)
+    {
+      return -EINVAL;
+    }
+
+  DEBUGASSERT(priv != NULL && priv->config != NULL && pin < 16);
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (dir == IOEXPANDER_DIRECTION_OUT)
+    {
+      priv->outpins |= (1 << pin);
+    }
+  else
+    {
+      priv->outpins &= ~(1 << pin);
+    }
+
+  if (pin < 8)
+    {
+      regaddr = ICJX_CTRL_WORD_2_A;
+      outpins = priv->outpins & 0xff;
+    }
+  else
+    {
+      regaddr = ICJX_CTRL_WORD_2_B;
+      outpins = (priv->outpins >> 8) & 0xff;
+    }
+
+  ret = icjx_read(priv, regaddr, &out_set);
+  if (ret < 0)
+    {
+      nxmutex_unlock(&priv->lock);
+      return ret;
+    }
+
+  /* Enable output generation */
+
+  if ((outpins & 0xf) != 0)
+    {
+      out_set |= ICJX_CTRL_WORD_2_NIOL;
+    }
+  else
+    {
+      out_set &= ~ICJX_CTRL_WORD_2_NIOL;
+    }
+
+  if ((outpins & 0xf0) != 0)
+    {
+      out_set |= ICJX_CTRL_WORD_2_NIOH;
+    }
+  else
+    {
+      out_set &= ~ICJX_CTRL_WORD_2_NIOH;
+    }
+
+  ret = icjx_write(priv, regaddr, out_set);
+  nxmutex_unlock(&priv->lock);
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: icjx_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
+ *   value - The option's value
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int icjx_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                       int opt, FAR void *value)
+{
+  /* TODO: Implementation of iC-JX options should be here. This includes
+   * setup of filters, ADC, interrupts etc. The right way to implement
+   * this would probably be to introduce config structure to
+   * include/nuttx/ioexpanders/icjx.h that the user could use for the
+   * nibbles configuration.
+   */
+
+  gpiowarn("ERROR: iC-JX options are not yet implemted!\n");
+
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: icjx_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
+ *   value - 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 icjx_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                         bool value)
+{
+  FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev;
+  uint8_t outstate;
+  uint8_t regaddr;
+  int ret;
+
+  if (pin > 16)
+    {
+      return -ENXIO;
+    }
+
+  DEBUGASSERT(priv != NULL && priv->config != NULL);
+
+  gpioinfo("Expander id=%02x, pin=%u\n", priv->config->id, pin);
+
+  /* Get exclusive access to the I/O Expander */
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if ((priv->outpins & (1 << pin)) == 0)
+    {
+      gpioerr("ERROR: pin%u is an input\n", pin);
+      nxmutex_unlock(&priv->lock);
+      return -EINVAL;
+    }
+
+  /* Set/clear a bit in outstate. */
+
+  if (value)
+    {
+      priv->outstate |= (1 << pin);
+    }
+  else
+    {
+      priv->outstate &= ~(1 << pin);
+    }
+
+  if (pin < 8)
+    {
+      regaddr = ICJX_OUTPUT_A;
+      outstate = priv->outstate & 0xff;
+    }
+  else
+    {
+      regaddr = ICJX_OUTPUT_B;
+      outstate = (priv->outstate >> 8) & 0xff;
+    }
+
+  ret = icjx_write(priv, regaddr, outstate);
+  nxmutex_unlock(&priv->lock);
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: icjx_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
+ *   value  - Pointer to a buffer where the pin level is stored. Usually
+ *            TRUE if the pin is high, except if OPTION_INVERT has been
+ *            set this pin.
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int icjx_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
+                        FAR bool *value)
+{
+  FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev;
+  uint8_t regaddr;
+  uint8_t data;
+  int ret;
+
+  if (pin > 16)
+    {
+      return -ENXIO;
+    }
+
+  DEBUGASSERT(priv != NULL && priv->config != NULL && value != NULL);
+
+  gpioinfo("Expander addr=%02x, pin=%u\n", priv->config->addr, pin);
+
+  /* Get exclusive access to the I/O Expander */
+
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  /* Make sure that this is an output pin */
+
+  if ((priv->outpins & (1 << pin)) != 0)
+    {
+      *value = ((priv->outstate & (1 << pin)) != 0);
+      nxmutex_unlock(&priv->lock);
+      return OK;
+    }
+
+  regaddr = pin < 8 ? ICJX_INPUT_A : ICJX_INPUT_B;
+
+  ret = icjx_read(priv, regaddr, &data);
+  nxmutex_unlock(&priv->lock);
+  if (ret != OK)
+    {
+      return ret;
+    }
+
+  *value = (bool)((data >> (pin & 0xf)) & 1);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: icjx_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
+ *   values - The list of pin levels.
+ *   count  - Number of pins to be written
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_IOEXPANDER_MULTIPIN
+static int icjx_multiwritepin(FAR struct ioexpander_dev_s *dev,
+                              FAR const uint8_t *pins,
+                              FAR bool *values, int count)
+{
+  gpiowarn("WARNING: iC-JX does not have implemented support for"
+           "multiple pin write operation!\n");
+
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: icjx_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
+ *   values - Pointer to a buffer where the pin levels are stored.
+ *   count  - Number of pins to be read
+ *
+ * Returned Value:
+ *   0 on success, else a negative error code
+ *
+ ****************************************************************************/
+
+static int icjx_multireadpin(FAR struct ioexpander_dev_s *dev,
+                             FAR const uint8_t *pins, FAR bool *values,
+                             int count)
+{
+  gpiowarn("WARNING: iC-JX does not have implemented support for"
+           "multiple pin read operation!\n");
+
+  return -ENOTSUP;
+}
+#endif /* CONFIG_IOEXPANDER_MULTIPIN */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: icjx_initialize
+ *
+ * Description:
+ *   Instantiate and configure the ICJXxx device driver to use the
+ *   provided SPI device instance.
+ *
+ * Input Parameters:
+ *   spi     - A SPI driver instance
+ *   config  - Persistent board configuration data
+ *
+ * Returned Value:
+ *   an ioexpander_dev_s instance on success, NULL on failure.
+ *
+ ****************************************************************************/
+
+FAR struct ioexpander_dev_s *icjx_initialize(FAR struct spi_dev_s *spi,
+                                      FAR struct icjx_config_s *config)
+{
+  FAR struct icjx_dev_s *priv;
+  uint8_t regval;
+  int ret;
+
+#ifdef CONFIG_ICJX_MULTIPLE
+  /* Allocate the device state structure */
+
+  priv = kmm_zalloc(sizeof(struct icjx_dev_s));
+  if (!priv)
+    {
+      gpioerr("ERROR: Failed to allocate driver instance\n");
+      return NULL;
+    }
+#else
+  /* Use the one-and-only I/O Expander driver instance */
+
+  priv = &g_icjx;
+#endif
+
+  /* Initialize the device state structure */
+
+  priv->dev.ops  = &g_icjx_ops;
+  priv->spi      = spi;
+  priv->config   = config;
+  priv->outpins  = 0;
+  priv->outstate = 0;
+
+  nxmutex_init(&priv->lock);
+
+  /* Set pull up/down based on expander configuration. This is the first
+   * time we are accessing the expander therefore we do not have to worry
+   * about reading the register content first.
+   */
+
+  regval = (config->current_src << 4) | config->current_src;
+
+  ret = icjx_write(priv, ICJX_CTRL_WORD_2_A, regval);
+  if (ret < 0)
+    {
+      gpioerr("ERROR: Could write to ICJX_CTRL_WORD_2_A: %d!\n", ret);
+      goto err;
+    }
+
+  ret = icjx_write(priv, ICJX_CTRL_WORD_2_B, regval);
+  if (ret < 0)
+    {
+      gpioerr("ERROR: Could write to ICJX_CTRL_WORD_2_B: %d!\n", ret);
+      goto err;
+    }
+
+  /* Bypass filters as those are not yet supported. */
+
+  regval = ICJX_CTRL_WORD_1_BYP0 | ICJX_CTRL_WORD_1_BYP1;
+  ret = icjx_write(priv, ICJX_CTRL_WORD_1_A, regval);
+  if (ret < 0)
+    {
+      gpioerr("ERROR: Could write to ICJX_CTRL_WORD_1_A: %d!\n", ret);
+      goto err;
+    }
+
+  ret = icjx_write(priv, ICJX_CTRL_WORD_1_B, regval);
+  if (ret < 0)
+    {
+      gpioerr("ERROR: Could write to ICJX_CTRL_WORD_1_B: %d!\n", ret);
+      goto err;
+    }
+
+  return &priv->dev;
+
+err:
+  nxmutex_destroy(&priv->lock);
+#ifdef CONFIG_ICJX_MULTIPLE
+  kmm_free(priv);
+#endif
+  return NULL;
+}
+
+#endif /* CONFIG_IOEXPANDER_ICJX */
diff --git a/drivers/ioexpander/icjx.h b/drivers/ioexpander/icjx.h
new file mode 100644
index 0000000000..f0a736ce0e
--- /dev/null
+++ b/drivers/ioexpander/icjx.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+ * drivers/ioexpander/icjx.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_ICJX_H
+#define __DRIVERS_IOEXPANDER_ICJX_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <nuttx/mutex.h>
+
+#if defined(CONFIG_IOEXPANDER) && defined(CONFIG_IOEXPANDER_ICJX)
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+/* Prerequisites:
+ *   CONFIG_SPI
+ *     SPI support is required
+ *   CONFIG_IOEXPANDER
+ *     Enables I/O expander support
+ *
+ * CONFIG_IOEXPANDER_ICJX
+ *   Enables support for the ICJX driver (Needs CONFIG_INPUT)
+ * CONFIG_ICJX_MULTIPLE
+ *   Can be defined to support multiple ICJX devices on board.
+ */
+
+#ifndef CONFIG_SPI
+#  error "CONFIG_SPI is required by ICJX"
+#endif
+
+/* iC-JX Registers **********************************************************/
+
+#define ICJX_INPUT_A            0x00
+#define ICJX_INPUT_B            0x01
+#define ICJX_CHNG_MSG_A         0x02
+#define ICJX_CNNG_MSG_B         0x03
+#define ICJX_INT_STATUS_A       0x04
+#define ICJX_INT_STATUS_B       0x05
+#define ICJX_OVERCURR_MSG_A     0x06
+#define ICJX_OVERCURR_MSG_B     0x07
+#define ICJX_OVERCURR_STATUS_A  0x08
+#define ICJX_OVERCURR_STATUS_B  0x09
+#define ICJX_ADC_DATA_1         0x0A
+#define ICJX_ADC_DATA_2         0x0B
+#define ICJX_OUTPUT_A           0x0C
+#define ICJX_OUTPUT_B           0x0D
+#define ICJX_FLASH_PULSE_A      0x0E
+#define ICJX_FLASH_PULSE_B      0x0F
+#define ICJX_CHNG_INT_EN_A      0x10
+#define ICJX_CHNG_INT_EN_B      0x11
+#define ICJX_OVERCURR_INT_EN_A  0x12
+#define ICJX_OVERCURR_INT_EN_B  0x13
+#define ICJX_CTRL_WORD_1_A      0x14
+#define ICJX_CTRL_WORD_1_B      0x15
+#define ICJX_CTRL_WORD_2_A      0x16
+#define ICJX_CTRL_WORD_2_B      0x17
+#define ICJX_CTRL_WORD_3_A      0x18
+#define ICJX_CTRL_WORD_3_B      0x19
+#define ICJX_CTRL_WORD_4        0x1A
+#define ICJX_CTRL_WORD_5        0x1B
+#define ICJX_CTRL_WORD_6        0x1C
+#define ICJX_DEV_ID             0x1D
+#define ICJX_TEST_1             0x1E
+#define ICJX_TEST_2             0x1F
+
+/* Control Word 1 */
+
+#define ICJX_CTRL_WORD_1_BYP0   (1 << 3)
+#define ICJX_CTRL_WORD_1_BYP1   (1 << 7)
+
+/* Control Word 2 */
+
+#define ICJX_CTRL_WORD_2_NIOL   (1 << 3)
+#define ICJX_CTRL_WORD_2_NIOH   (1 << 7)
+
+#endif /* CONFIG_IOEXPANDER && CONFIG_IOEXPANDER_ICJX */
+#endif /* __DRIVERS_IOEXPANDER_ICJX_H */
diff --git a/include/nuttx/ioexpander/icjx.h b/include/nuttx/ioexpander/icjx.h
new file mode 100644
index 0000000000..629a048019
--- /dev/null
+++ b/include/nuttx/ioexpander/icjx.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+ * include/nuttx/ioexpander/icjx.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_ICJX_H
+#define __INCLUDE_NUTTX_IOEXPANDER_ICJX_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define ICJX_CTRL_WORD_CURR_SRC_DIS   0   /* Pin current source disabled */
+#define ICJX_CTRL_WORD_PULLDOWN_200U  1   /* 200 uA pull down */
+#define ICJX_CTRL_WORD_PULLDOWN_600U  2   /* 600 uA pull down */
+#define ICJX_CTRL_WORD_PULLDOWN_2M    3   /* 2 mA pull down */
+#define ICJX_CTRL_WORD_PULLUP_200U    5   /* 200 uA pull up */
+#define ICJX_CTRL_WORD_PULLUP_600U    6   /* 600 uA pull up */
+#define ICJX_CTRL_WORD_PULLUP_2M      7   /* 2 mA pull up*/
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* A reference to a structure of this type must be passed to the iC-JX
+ * driver when the driver is instantiated. This structure provides
+ * information about the configuration of the iC-JX 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 icjx_config_s
+{
+  /* Device characterization */
+
+  uint8_t id;           /* Device ID (if more expanders are used) */
+  uint8_t verification; /* True if data verification on MISO line is used */
+  uint8_t current_src;  /* Current sources for pin nibbles (pull up,
+                         * pull down) - see Control Word 2 register
+                         */
+  uint8_t addr;         /* Device address (set by A(1:0) pins) */
+  uint8_t mode;         /* SPI mode */
+  uint32_t frequency;   /* SPI frequency */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: icjx_initialize
+ *
+ * Description:
+ *   Instantiate and configure the iC-JX device driver to use the
+ *   provided SPI device instance.
+ *
+ * Input Parameters:
+ *   spi     - A SPI driver instance
+ *   config  - Persistent board configuration data
+ *
+ * Returned Value:
+ *   an ioexpander_dev_s instance on success, NULL on failure.
+ *
+ ****************************************************************************/
+
+struct spi_dev_s;
+FAR struct ioexpander_dev_s *icjx_initialize(FAR struct spi_dev_s *spi,
+                                      FAR struct icjx_config_s *config);
+
+#endif /* __INCLUDE_NUTTX_IOEXPANDER_ICJX_H */