From 0f284c3025c1e65438125abcb2754bfbeb22aca4 Mon Sep 17 00:00:00 2001 From: Nicholas Chin Date: Wed, 19 Feb 2020 10:09:52 -0500 Subject: [PATCH] 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(+) create mode 100644 drivers/ioexpander/pca9538.c create mode 100644 drivers/ioexpander/pca9538.h create mode 100644 include/nuttx/ioexpander/pca9538.h diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig index 7108d1c021..f0995241b4 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 8c3107993e..7c421b188a 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 0000000000..0442d7dbc4 --- /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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 0000000000..1c7f55f499 --- /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 + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 0000000000..d9a300a5e7 --- /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 +#include + +#include + +/**************************************************************************** + * 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 */