/**************************************************************************** * drivers/ioexpander/pca9555.c * * Copyright (C) 2015 Gregory Nutt. All rights reserved. * Author: Sebastien Lorquet * * References: * "16-bit I2C-bus and SMBus I/O port with interrupt product datasheet", * Rev. 08 - 22 October 2009, NXP * * 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 "pca9555.h" #if defined(CONFIG_IOEXPANDER_PCA9555) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_I2C # warning I2C support is required (CONFIG_I2C) #endif #ifndef CONFIG_I2C_WRITEREAD # warning Support of the I2C writeread() method is required (CONFIG_I2C_WRITEREAD) #endif /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int pca9555_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int dir); static int pca9555_option (FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, void *val); static int pca9555_write (FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value); static int pca9555_readpin (FAR struct ioexpander_dev_s *dev, uint8_t pin, bool *value); static int pca9555_readbuf (FAR struct ioexpander_dev_s *dev, uint8_t pin, bool *value); /**************************************************************************** * Private Data ****************************************************************************/ #ifndef CONFIG_PCA9555_MULTIPLE /* If only a single PCA9555 device is supported, then the driver state * structure may as well be pre-allocated. */ static struct pca9555_dev_s g_pca9555; /* Otherwise, we will need to maintain allocated driver instances in a list */ #else static struct pca9555_dev_s *g_pca9555list; #endif static const struct ioexpander_ops_s g_pca9555_ops = { pca9555_direction, pca9555_option, pca9555_write, pca9555_readpin, pca9555_readbuf, }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: pca9555_setbit * * Description: * Write a bit in a register pair * ****************************************************************************/ static int pca9555_setbit(FAR struct i2c_dev_s *i2c, uint8_t addr, uint8_t pin, int bitval) { int ret; uint8_t buf[2]; buf[0] = addr; if(pin>15) { return -ENXIO; } else if(pin>7) { addr += 1; pin -= 8; } ret = I2C_WRITEREAD(i2c, &addr, 1, &buf[1], 1); if(ret != 0) { return ret; } if(bitval) { buf[1] |= (1<15) { return -ENXIO; } else if(pin>7) { addr += 1; pin -= 8; } ret = I2C_WRITEREAD(i2c, &addr, 1, &buf, 1); if(ret != 0) { return ret; } *val = (buf >> pin) & 1; return 0; } /**************************************************************************** * Name: pca9555_direction * * Description: * See include/nuttx/ioexpander/ioexpander.h * ****************************************************************************/ static int pca9555_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int direction) { FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s*)dev; return pca9555_setbit(pca->i2c, PCA9555_REG_CONFIG, pin, (direction == IOEXPANDER_DIRECTION_IN)); } /**************************************************************************** * Name: pca9555_option * * Description: * See include/nuttx/ioexpander/ioexpander.h * ****************************************************************************/ static int pca9555_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, void *val) { FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s*)dev; int ival = (int)val; if(opt == IOEXPANDER_OPTION_INVERT) { return pca9555_setbit(pca->i2c, PCA9555_REG_POLINV, pin, ival); } return -EINVAL; } /**************************************************************************** * Name: pca9555_write * * Description: * See include/nuttx/ioexpander/ioexpander.h * ****************************************************************************/ static int pca9555_write(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value) { FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s*)dev; return pca9555_setbit(pca->i2c, PCA9555_REG_OUTPUT, pin, value); } /**************************************************************************** * Name: pca9555_readpin * * Description: * See include/nuttx/ioexpander/ioexpander.h * ****************************************************************************/ static int pca9555_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool *value) { FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s*)dev; return pca9555_getbit(pca->i2c, PCA9555_REG_INPUT, pin, value); } /**************************************************************************** * Name: pca9555_readbuf * * Description: * See include/nuttx/ioexpander/ioexpander.h * ****************************************************************************/ static int pca9555_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool *value) { FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s*)dev; return pca9555_getbit(pca->i2c, PCA9555_REG_OUTPUT, pin, value); } #ifndef CONFIG_PCA9555_INT_DISABLE /**************************************************************************** * Name: pca9555_gpioworker * * Description: * See include/nuttx/ioexpander/ioexpander.h * ****************************************************************************/ static int pca9555_attach(FAR struct ioexpander_dev_s *dev, uint8_t pin, ioexpander_handler_t handler) { FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s*)dev; return 0; } /**************************************************************************** * Name: pca9555_irqworker * * Description: * Handle GPIO interrupt events (this function actually executes in the * context of the worker thread). * ****************************************************************************/ static void pca9555_irqworker(FAR struct pca9555_dev_s *priv) { uint8_t regval; uint8_t pinmask; int pin; /* Get the set of pending GPIO interrupts */ /* Look at each pin */ for (pin = 0; pin < PCA9555_GPIO_NPINS; pin++) { /* Check if we have a handler for this interrupt (there should * be one) */ /* Interrupt is pending... dispatch the interrupt to the * callback */ /* Clear the pending GPIO interrupt by writing a '1' to the * pin position in the status register. */ } } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: pca9555_initialize * * Description: * Initialize a PCA9555 I2C device. * * TODO: Actually support more than one device. * ****************************************************************************/ FAR struct ioexpander_dev_s *pca9555_initialize(FAR struct i2c_dev_s *i2cdev, FAR struct pca9555_config_s *config) { FAR struct pca9555_dev_s *pcadev; DEBUGASSERT(i2cdev != NULL && config != NULL); #ifdef CONFIG_PCA9555_MULTIPLE /* Allocate the device state structure */ pcadev = (FAR struct pca9555_dev_s *)kmm_zalloc(sizeof(struct pca9555_dev_s)); if (!pcadev) { return NULL; } /* And save the device structure in the list of PCA9555 so that we can * find it later. */ pcadev->flink = g_pca9555list; g_pca9555list = pcadev; #else /* Use the one-and-only PCA9555 driver instance */ pcadev = &g_pca9555; #endif /* Initialize the device state structure */ pcadev->i2c = i2cdev; pcadev->dev.ops = &g_pca9555_ops; /* Set the I2C address and frequency. REVISIT: This logic would be * insufficient if we share the I2C bus with any other devices that also * modify the address and frequency. */ I2C_SETADDRESS(i2cdev, config->address, 7); I2C_SETFREQUENCY(i2cdev, config->frequency); return &pcadev->dev; } #endif /* CONFIG_IOEXPANDER_PCA9555 */