/**************************************************************************** * drivers/ioexpander/pcf8574.h * * Copyright (C) 2016-2017 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ********************************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include "pcf8574.h" #ifdef CONFIG_IOEXPANDER_PCF8574 /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef MAX # define MAX(a,b) (((a) > (b)) ? (a) : (b)) #endif #ifndef MIN # define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* PCF8574xx Helpers */ static void pcf8574_lock(FAR struct pcf8574_dev_s *priv); static int pcf8574_read(FAR struct pcf8574_dev_s *priv, FAR uint8_t *portval); static int pcf8574_write(struct pcf8574_dev_s *priv, uint8_t portval); /* I/O Expander Methods */ static int pcf8574_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int dir); static int pcf8574_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, void *regval); static int pcf8574_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value); static int pcf8574_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value); #ifdef CONFIG_IOEXPANDER_MULTIPIN static int pcf8574_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count); static int pcf8574_multireadpin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count); #endif #ifdef CONFIG_IOEXPANDER_INT_ENABLE static FAR void *pcf8574_attach(FAR struct ioexpander_dev_s *dev, ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg); static int pcf8574_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle); #endif #ifdef CONFIG_PCF8574_INT_ENABLE static void pcf8574_int_update(void *handle, uint8_t input); static void pcf8574_register_update(FAR struct pcf8574_dev_s *priv); static void pcf8574_irqworker(void *arg); static void pcf8574_interrupt(FAR void *arg); #ifdef CONFIG_PCF8574_INT_POLL static void pcf8574_poll_expiry(int argc, wdparm_t arg1, ...); #endif #endif /**************************************************************************** * Private Data ****************************************************************************/ #ifndef CONFIG_PCF8574_MULTIPLE /* If only a single device is supported, then the driver state structure may * as well be pre-allocated. */ static struct pcf8574_dev_s g_pcf8574; #endif /* I/O expander vtable */ static const struct ioexpander_ops_s g_pcf8574_ops = { pcf8574_direction, pcf8574_option, pcf8574_writepin, pcf8574_readpin, pcf8574_readpin #ifdef CONFIG_IOEXPANDER_MULTIPIN , pcf8574_multiwritepin , pcf8574_multireadpin , pcf8574_multireadpin #endif #ifdef CONFIG_IOEXPANDER_INT_ENABLE , pcf8574_attach , pcf8574_detach #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: pcf8574_lock * * Description: * Get exclusive access to the I/O Expander * ****************************************************************************/ static void pcf8574_lock(FAR struct pcf8574_dev_s *priv) { nxsem_wait_uninterruptible(&priv->exclsem); } #define pcf8574_unlock(p) nxsem_post(&(p)->exclsem) /**************************************************************************** * Name: pcf8574_read * * Description: * Read the PCF8574 8-bit value from a PCF8574xx port * * Primitive I2C read operation for the PCA8574. The PCF8574 is * 'interesting' in that it doesn't really have a data direction register, * but instead the outputs are current-limited when high, so by setting an * IO line high, you are also making it an input. Consequently, before * using this method, you'll need to perform a pca8574_write() setting the * bits you are interested in reading to 1's, then call this method. * ****************************************************************************/ static int pcf8574_read(FAR struct pcf8574_dev_s *priv, FAR uint8_t *portval) { struct i2c_msg_s msg; int ret; DEBUGASSERT(priv != NULL && priv->i2c != NULL && priv->config != NULL); /* Setup for the transfer */ msg.frequency = priv->config->frequency, msg.addr = priv->config->address, msg.flags = I2C_M_READ; msg.buffer = portval; msg.length = 1; /* Then perform the transfer. */ ret = I2C_TRANSFER(priv->i2c, &msg, 1); return (ret >= 0) ? OK : ret; } /**************************************************************************** * Name: pcf8574_write * * Description: * Write an 8-bit value to a PCF8574xx port * * Primitive I2C write operation for the PCA8574. The I2C interface * simply sets the state of the 8 IO lines in the PCA8574 port. * ****************************************************************************/ static int pcf8574_write(struct pcf8574_dev_s *priv, uint8_t portval) { struct i2c_msg_s msg; int ret; DEBUGASSERT(priv != NULL && priv->i2c != NULL && priv->config != NULL); /* Setup for the transfer */ msg.frequency = priv->config->frequency, msg.addr = priv->config->address; msg.flags = 0; msg.buffer = (FAR uint8_t *)&portval; msg.length = 1; /* Then perform the transfer. */ ret = I2C_TRANSFER(priv->i2c, &msg, 1); return (ret >= 0) ? OK : ret; } /**************************************************************************** * Name: pcf8574_direction * * Description: * Set the direction of an ioexpander pin. Required. * * The PCF8574 is 'interesting' in that it doesn't really have a data * direction register, but instead the outputs are current-limited when * high, so by setting an IO line high, you are also making it an input. * Consequently, before using this method, you'll need to perform a * pca8574_write() setting the bits you are interested in reading to 1's, * before calling pca8574_read(). * * 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 pcf8574_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int direction) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; int ret; DEBUGASSERT(priv != NULL && priv->config != NULL && pin < 8 && (direction == IOEXPANDER_DIRECTION_IN || direction == IOEXPANDER_DIRECTION_OUT)); gpioinfo("I2C addr=%02x pin=%u direction=%s\n", priv->config->address, pin, (direction == IOEXPANDER_DIRECTION_IN) ? "IN" : "OUT"); /* Get exclusive access to the I/O Expander */ pcf8574_lock(priv); /* Set a bit in inpins if the pin is an input. Clear the bit in * inpins if the pin is an output. */ if (direction == IOEXPANDER_DIRECTION_IN) { priv->inpins |= (1 << pin); priv->outstate &= ~(1 << pin); } else { priv->inpins &= ~(1 << pin); } /* Write the OR of the set of input pins and the set of output pins. * In order to read input pins, we have to write a '1' to putt he * pin in the current limiting state. */ ret = pcf8574_write(priv, priv->inpins | priv->outstate); pcf8574_unlock(priv); return ret; } /**************************************************************************** * Name: pcf8574_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 pcf8574_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, FAR void *value) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; int ret = -ENOSYS; DEBUGASSERT(priv != NULL && priv->config != NULL); gpioinfo("I2C addr=%02x pin=%u option=%u\n", priv->config->address, pin, opt); #ifdef CONFIG_PCF8574_INT_ENABLE /* Interrupt configuration */ if (opt == IOEXPANDER_OPTION_INTCFG) { unsigned int ival = (unsigned int)((uintptr_t)value); ioe_pinset_t bit = ((ioe_pinset_t)1 << pin); ret = OK; pcf8574_lock(priv); switch (ival) { case IOEXPANDER_VAL_HIGH: /* Interrupt on high level */ priv->trigger &= ~bit; priv->level[0] |= bit; priv->level[1] &= ~bit; break; case IOEXPANDER_VAL_LOW: /* Interrupt on low level */ priv->trigger &= ~bit; priv->level[0] &= ~bit; priv->level[1] |= bit; break; case IOEXPANDER_VAL_RISING: /* Interrupt on rising edge */ priv->trigger |= bit; priv->level[0] |= bit; priv->level[1] &= ~bit; break; case IOEXPANDER_VAL_FALLING: /* Interrupt on falling edge */ priv->trigger |= bit; priv->level[0] &= ~bit; priv->level[1] |= bit; break; case IOEXPANDER_VAL_BOTH: /* Interrupt on both edges */ priv->trigger |= bit; priv->level[0] |= bit; priv->level[1] |= bit; break; case IOEXPANDER_VAL_DISABLE: break; default: ret = -EINVAL; } pcf8574_unlock(priv); } #endif return ret; } /**************************************************************************** * Name: pcf8574_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 pcf8574_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; int ret; DEBUGASSERT(priv != NULL && priv->config != NULL && pin < 8); gpioinfo("I2C addr=%02x pin=%u value=%u\n", priv->config->address, pin, value); /* Get exclusive access to the I/O Expander */ pcf8574_lock(priv); /* Make sure that this is an output pin */ if ((priv->inpins & (1 << pin)) != 0) { gpioerr("ERROR: pin%u is an input\n", pin); pcf8574_unlock(priv); return -EINVAL; } /* Set/clear a bit in outstate. */ if (value) { priv->outstate |= (1 << pin); } else { priv->outstate &= ~(1 << pin); } /* Write the OR of the set of input pins and the set of output pins. * In order to set the new output value. */ ret = pcf8574_write(priv, priv->inpins | priv->outstate); pcf8574_unlock(priv); return ret; } /**************************************************************************** * Name: pcf8574_readpin * * Description: * Read the actual PIN level. This can be different from the last value written * to this pin. Required. * * The PCF8574 is 'interesting' in that it doesn't really have a data * direction register, but instead the outputs are current-limited when * high, so by setting an IO line high, you are also making it an input. * Consequently, before using this method, you'll need to perform a * pca8574_write() setting the bits you are interested in reading to 1's, * before calling pca8574_read(). * * 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 pcf8574_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; uint8_t regval; int ret; DEBUGASSERT(priv != NULL && priv->config != NULL && pin < 8 && value != NULL); gpioinfo("I2C addr=%02x, pin=%u\n", priv->config->address, pin); /* Get exclusive access to the I/O Expander */ pcf8574_lock(priv); /* Is the pin an output? */ if ((priv->inpins & (1 << pin)) == 0) { /* We cannot read the value on pin directly. Just Return the last * value that we wrote to the pin. */ *value = ((priv->outstate & (1 << pin)) != 0); pcf8574_unlock(priv); return OK; } /* It is an input pin. Read the input register for this pin * * The Input Port Register reflects the incoming logic levels of the pins, * regardless of whether the pin is defined as an input or an output by * the Configuration Register. They act only on read operation. */ ret = pcf8574_read(priv, ®val); if (ret < 0) { gpioerr("ERROR: Failed to read port register: %d\n", ret); goto errout_with_lock; } #ifdef CONFIG_PCF8574_INT_ENABLE /* Update the input status with the 8 bits read from the expander */ pcf8574_int_update(priv, regval); #endif /* Return 0 or 1 to indicate the state of pin */ *value = (bool)((regval >> (pin & 7)) & 1); ret = OK; errout_with_lock: pcf8574_unlock(priv); return ret; } /**************************************************************************** * Name: pcf8574_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 * ****************************************************************************/ #ifdef CONFIG_IOEXPANDER_MULTIPIN static int pcf8574_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; uint8_t pin; int ret; int i; DEBUGASSERT(priv != NULL && priv->config != NULL && pins != NULL && values != NULL); gpioinfo("I2C addr=%02x count=%d\n", priv->config->address, count); /* Get exclusive access to the I/O Expander */ pcf8574_lock(priv); /* Process each pin setting */ for (i = 0; i < count; i++) { /* Make sure that this is an output pin */ pin = pins[i]; DEBUGASSERT(pin < 8); gpioinfo("%d. pin=%u value=%u\n", pin, values[i]); if ((priv->inpins & (1 << pin)) != 0) { gpioerr("ERROR: pin%u is an input\n", pin); continue; } /* Set/clear a bit in outstate. */ if (values[i]) { priv->outstate |= (1 << pin); } else { priv->outstate &= ~(1 << pin); } } /* Write the OR of the set of input pins and the set of output pins. * In order to set the new output value. */ ret = pcf8574_write(priv, priv->inpins | priv->outstate); pcf8574_unlock(priv); return ret; } #endif /**************************************************************************** * Name: pcf8574_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 * ****************************************************************************/ #ifdef CONFIG_IOEXPANDER_MULTIPIN static int pcf8574_multireadpin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; uint8_t regval; uint8_t pin; int ret; int i; DEBUGASSERT(priv != NULL && priv->config != NULL && pins != NULL && values != NULL); gpioinfo("I2C addr=%02x, count=%d\n", priv->config->address, count); /* Get exclusive access to the I/O Expander */ pcf8574_lock(priv); /* Read the input register for this pin * * The Input Port Register reflects the incoming logic levels of the pins, * regardless of whether the pin is defined as an input or an output by * the Configuration Register. They act only on read operation. */ ret = pcf8574_read(priv, ®val); if (ret < 0) { gpioerr("ERROR: Failed to read port register: %d\n", ret); goto errout_with_lock; } #ifdef CONFIG_PCF8574_INT_ENABLE /* Update the input status with the 8 bits read from the expander */ pcf8574_int_update(priv, regval); #endif /* Return the requested pin values */ for (i = 0; i < count; i++) { /* Make sure that this is an output pin */ pin = pins[i]; DEBUGASSERT(pin < 8); /* Is the pin an output? */ if ((priv->inpins & (1 << pin)) == 0) { /* We cannot read the value on pin directly. Just Return the last * value that we wrote to the pin. */ values[i] = ((priv->outstate & (1 << pin)) != 0); } else { values[i] = ((regval & (1 << pin)) != 0); } gpioinfo("%d. pin=%u value=%u\n", pin, values[i]); } ret = OK; errout_with_lock: pcf8574_unlock(priv); return ret; } #endif /**************************************************************************** * Name: pcf8574_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. * ****************************************************************************/ #ifdef CONFIG_PCF8574_INT_ENABLE static FAR void *pcf8574_attach(FAR struct ioexpander_dev_s *dev, ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; FAR void *handle = NULL; int i; /* Get exclusive access to the I/O Expander */ pcf8574_lock(priv); /* Find and available in entry in the callback table */ for (i = 0; i < CONFIG_PCF8574_INT_NCALLBACKS; i++) { /* Is this entry available (i.e., no callback attached) */ if (priv->cb[i].cbfunc == NULL) { /* Yes.. use this entry */ priv->cb[i].pinset = pinset; priv->cb[i].cbfunc = callback; priv->cb[i].cbarg = arg; handle = &priv->cb[i]; break; } } pcf8574_unlock(priv); return handle; } #endif /**************************************************************************** * Name: pcf8574_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 pcf8574_attch() * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ #ifdef CONFIG_PCF8574_INT_ENABLE static int pcf8574_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)dev; FAR struct pcf8574_callback_s *cb = (FAR struct pcf8574_callback_s *)handle; DEBUGASSERT(priv != NULL && cb != NULL); DEBUGASSERT((uintptr_t)cb >= (uintptr_t)&priv->cb[0] && (uintptr_t)cb <= (uintptr_t)&priv->cb[CONFIG_PCF8574_INT_NCALLBACKS-1]); UNUSED(priv); cb->pinset = 0; cb->cbfunc = NULL; cb->cbarg = NULL; return OK; } #endif /**************************************************************************** * Name: pcf8574_int_update * * Description: * Check for pending interrupts. * ****************************************************************************/ #ifdef CONFIG_PCF8574_INT_ENABLE static void pcf8574_int_update(void *handle, uint8_t input) { struct pcf8574_dev_s *priv = handle; irqstate_t flags; uint8_t diff; int pin; flags = enter_critical_section(); /* Check the changed bits from last read */ diff = priv->input ^ input; priv->input = input; /* PCF8574 doesn't support irq trigger, we have to do this in software. */ for (pin = 0; pin < 8; pin++) { if (PCF8574_EDGE_SENSITIVE(priv, pin)) { /* Edge triggered. Was there a change in the level? */ if ((diff & 1) != 0) { /* Set interrupt as a function of edge type */ if (((input & 1) == 0 && PCF8574_EDGE_FALLING(priv, pin)) || ((input & 1) != 0 && PCF8574_EDGE_RISING(priv, pin))) { priv->intstat |= 1 << pin; } } } else /* if (PCF8574_LEVEL_SENSITIVE(priv, pin)) */ { /* Level triggered. Set intstat if match in level type. */ if (((input & 1) != 0 && PCF8574_LEVEL_HIGH(priv, pin)) || ((input & 1) == 0 && PCF8574_LEVEL_LOW(priv, pin))) { priv->intstat |= 1 << pin; } } diff >>= 1; input >>= 1; } leave_critical_section(flags); } #endif /**************************************************************************** * Name: tc64_update_registers * * Description: * Read all pin states and update pending interrupts. * * 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 * ****************************************************************************/ #ifdef CONFIG_PCF8574_INT_ENABLE static void pcf8574_register_update(FAR struct pcf8574_dev_s *priv) { uint8_t regval; int ret; /* Read from the PCF8574 port. * * The Input Port Register reflects the incoming logic levels of the pins, * regardless of whether the pin is defined as an input or an output by * the Configuration Register. They act only on read operation. */ ret = pcf8574_read(priv, ®val); if (ret < 0) { gpioerr("ERROR: Failed to read port register: %d\n", ret); } else { /* Update the input status with the 8 bits read from the expander */ pcf8574_int_update(priv, regval); } } #endif /**************************************************************************** * Name: pcf8574_irqworker * * Description: * Handle GPIO interrupt events (this function actually executes in the * context of the worker thread). * ****************************************************************************/ #ifdef CONFIG_PCF8574_INT_ENABLE static void pcf8574_irqworker(void *arg) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)arg; uint8_t pinset; int ret; int i; DEBUGASSERT(priv != NULL && priv->config != NULL); /* Check for pending interrupts */ pcf8574_lock(priv); pcf8574_register_update(priv); /* Sample and clear the pending interrupts. */ pinset = priv->intstat; priv->intstat = 0; pcf8574_unlock(priv); /* Perform pin interrupt callbacks */ for (i = 0; i < CONFIG_PCF8574_INT_NCALLBACKS; i++) { /* Is this entry valid (i.e., callback attached)? */ if (priv->cb[i].cbfunc != NULL) { /* Did any of the requested pin interrupts occur? */ ioe_pinset_t match = pinset & priv->cb[i].pinset; if (match != 0) { /* Yes.. perform the callback */ priv->cb[i].cbfunc(&priv->dev, match, priv->cb[i].cbarg); } } } #ifdef CONFIG_PCF8574_INT_POLL /* Check for pending interrupts */ pcf8574_register_update(priv); /* Re-start the poll timer */ sched_lock(); ret = wd_start(priv->wdog, PCF8574_POLLDELAY, (wdentry_t)pcf8574_poll_expiry, 1, (wdparm_t)priv); if (ret < 0) { gpioerr("ERROR: Failed to start poll timer\n"); } #endif /* Re-enable interrupts */ priv->config->enable(priv->config, true); #ifdef CONFIG_PCF8574_INT_POLL sched_unlock(); #endif } #endif /**************************************************************************** * Name: pcf8574_interrupt * * Description: * Handle GPIO interrupt events (this function executes in the * context of the interrupt). * ****************************************************************************/ #ifdef CONFIG_PCF8574_INT_ENABLE static void pcf8574_interrupt(FAR void *arg) { FAR struct pcf8574_dev_s *priv = (FAR struct pcf8574_dev_s *)arg; DEBUGASSERT(priv != NULL && priv->config != NULL); /* Defer interrupt processing to the worker thread. This is not only * much kinder in the use of system resources but is probably necessary * to access the I/O expander device. * * 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 pcf8574_irqworker() when the work is * completed. */ if (work_available(&priv->work)) { #ifdef CONFIG_PCF8574_INT_POLL /* Cancel the poll timer */ wd_cancel(priv->wdog); #endif /* Disable interrupts */ priv->config->enable(priv->config, false); /* Schedule interrupt related work on the high priority worker thread. */ work_queue(HPWORK, &priv->work, pcf8574_irqworker, (FAR void *)priv, 0); } } #endif /**************************************************************************** * Name: pcf8574_poll_expiry * * Description: * The poll timer has expired; check for missed interrupts * * Input Parameters: * Standard wdog expiration arguments. * ****************************************************************************/ #if defined(CONFIG_PCF8574_INT_ENABLE) && defined(CONFIG_PCF8574_INT_POLL) static void pcf8574_poll_expiry(int argc, wdparm_t arg1, ...) { FAR struct pcf8574_dev_s *priv; DEBUGASSERT(argc == 1); priv = (FAR struct pcf8574_dev_s *)arg1; DEBUGASSERT(priv != NULL && priv->config != NULL); /* Defer interrupt processing to the worker thread. This is not only * much kinder in the use of system resources but is probably necessary * to access the I/O expander device. * * 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 pcf8574_irqworker() when the work is * completed. */ if (work_available(&priv->work)) { /* Disable interrupts */ priv->config->enable(priv->config, false); /* Schedule interrupt related work on the high priority worker thread. */ work_queue(HPWORK, &priv->work, pcf8574_irqworker, (FAR void *)priv, 0); } } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: pcf8574_initialize * * Description: * Instantiate and configure the PCF8574xx device driver to use the provided * I2C device instance. * * Input Parameters: * i2c - 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 *pcf8574_initialize(FAR struct i2c_master_s *i2c, FAR struct pcf8574_config_s *config) { FAR struct pcf8574_dev_s *priv; int ret; #ifdef CONFIG_PCF8574_MULTIPLE /* Allocate the device state structure */ priv = (FAR struct pcf8574_dev_s *)kmm_zalloc(sizeof(struct pcf8574_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_pcf8574; #endif /* Initialize the device state structure */ priv->dev.ops = &g_pcf8574_ops; priv->i2c = i2c; priv->config = config; #ifdef CONFIG_PCF8574_INT_ENABLE /* Initial interrupt state: Edge triggered on both edges */ priv->trigger = 0xff; /* All edge triggered */ priv->level[0] = 0xff; /* All rising edge */ priv->level[1] = 0xff; /* All falling edge */ #ifdef CONFIG_PCF8574_INT_POLL /* Set up a timer to poll for missed interrupts */ priv->wdog = wd_create(); DEBUGASSERT(priv->wdog != NULL); ret = wd_start(priv->wdog, PCF8574_POLLDELAY, (wdentry_t)pcf8574_poll_expiry, 1, (wdparm_t)priv); if (ret < 0) { gpioerr("ERROR: Failed to start poll timer\n"); } #endif /* Attach the I/O expander interrupt handler and enable interrupts */ priv->config->attach(config, pcf8574_interrupt, priv); priv->config->enable(config, true); #endif nxsem_init(&priv->exclsem, 0, 1); return &priv->dev; } #endif /* CONFIG_IOEXPANDER_PCF8574 */