/**************************************************************************** * 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 int pca9538_lock(FAR struct pca9538_dev_s *pca) { return 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, bool bitval) { uint8_t buf[2]; int ret; if (pin >= PCA9538_GPIO_NPINS) { return -ENXIO; } buf[0] = addr; #ifdef CONFIG_PCA9538_SHADOW_MODE /* Get the shadowed register value */ buf[1] = pca->sreg[addr]; #else /* Get the register value from the IO-Expander */ ret = pca9538_writeread(pca, &buf[0], 1, &buf[1], 1); if (ret < 0) { return ret; } #endif if (bitval) { buf[1] |= (1 << pin); } else { buf[1] &= ~(1 << pin); } #ifdef CONFIG_PCA9538_SHADOW_MODE /* Save the new register value in the shadow register */ pca->sreg[addr] = buf[1]; #endif ret = pca9538_write(pca, buf, 2); #ifdef CONFIG_PCA9538_RETRY if (ret != OK) { /* Try again (only once) */ ret = pca9538_write(pca, buf, 2); } #endif return ret; } /**************************************************************************** * Name: pca9538_getbit * * Description: * Get a bit from a register pair * ****************************************************************************/ static int pca9538_getbit(FAR struct pca9538_dev_s *pca, uint8_t addr, uint8_t pin, FAR bool *val) { uint8_t buf; int ret; if (pin >= PCA9538_GPIO_NPINS) { return -ENXIO; } ret = pca9538_writeread(pca, &addr, 1, &buf, 1); if (ret < 0) { return ret; } #ifdef CONFIG_PCA9538_SHADOW_MODE /* Save the new register value in the shadow register */ pca->sreg[addr] = buf; #endif *val = (buf >> pin) & 1; return OK; } /**************************************************************************** * Name: pca9538_direction * * Description: * Set the direction of an ioexpander pin. Required. * * Input Parameters: * dev - Device-specific state data * pin - The index of the pin to alter in this call * dir - One of the IOEXPANDER_DIRECTION_ macros * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ static int pca9538_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int direction) { FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev; int ret; if (direction != IOEXPANDER_DIRECTION_IN && direction != IOEXPANDER_DIRECTION_OUT) { return -EINVAL; } /* Get exclusive access to the PCA555 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } 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 *value) { FAR struct pca9538_dev_s *pca = (FAR struct pca9538_dev_s *)dev; int ret = -EINVAL; if (opt == IOEXPANDER_OPTION_INVERT) { /* Get exclusive access to the PCA555 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } ret = pca9538_setbit(pca, PCA9538_REG_POLINV, pin, ((uintptr_t)value == IOEXPANDER_VAL_INVERT)); 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 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } 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 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } 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 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } 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 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } /* Start by reading both registers, whatever the pins to change. We could * attempt to read one port only if all pins were on the same port, but * this would not save much. */ #ifndef CONFIG_PCA9538_SHADOW_MODE ret = pca9538_writeread(pca, &addr, 1, &buf[1], 2); if (ret < 0) { 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 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } 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 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } 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; int ret; /* Get exclusive access to the PCA555 */ ret = pca9538_lock(pca); if (ret < 0) { return ret; } /* Find and available in entry in the callback table */ for (i = 0; i < CONFIG_PCA9538_INT_NCALLBACKS; i++) { /* Is this entry available (i.e., no callback attached) */ if (pca->cb[i].cbfunc == NULL) { /* Yes.. use this entry */ pca->cb[i].pinset = pinset; pca->cb[i].cbfunc = callback; pca->cb[i].cbarg = arg; handle = &pca->cb[i]; break; } } /* Add this callback to the table */ 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) { 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 */