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