/**************************************************************************** * drivers/ioexpander/pcf8575.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 "pcf8575.h" #ifdef CONFIG_IOEXPANDER_PCF8575 #ifdef CONFIG_IOEXPANDER_INT_ENABLE # error PCF8575 cannot work with CONFIG_IOEXPANDER_INT_ENABLE #endif /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* PCF8575xx Helpers */ static int pcf8575_read(FAR struct pcf8575_dev_s *priv, FAR uint16_t *portval); static int pcf8575_write(struct pcf8575_dev_s *priv, uint16_t portval); /* I/O Expander Methods */ static int pcf8575_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int dir); static int pcf8575_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, void *regval); static int pcf8575_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value); static int pcf8575_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value); #ifdef CONFIG_IOEXPANDER_MULTIPIN static int pcf8575_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count); static int pcf8575_multireadpin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count); #endif /**************************************************************************** * Private Data ****************************************************************************/ #ifndef CONFIG_PCF8575_MULTIPLE /* If only a single device is supported, then the driver state structure may * as well be pre-allocated. */ static struct pcf8575_dev_s g_pcf8575; #endif /* I/O expander vtable */ static const struct ioexpander_ops_s g_pcf8575_ops = { pcf8575_direction, pcf8575_option, pcf8575_writepin, pcf8575_readpin, pcf8575_readpin #ifdef CONFIG_IOEXPANDER_MULTIPIN , pcf8575_multiwritepin , pcf8575_multireadpin , pcf8575_multireadpin #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: pcf8575_read * * Description: * Read the PCF8575 16-bit value from a PCF8575xx port * * Primitive I2C read operation for the PCF8575. The PCF8575 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 pcf8575_read(FAR struct pcf8575_dev_s *priv, FAR uint16_t *portval) { struct i2c_msg_s msg; uint8_t buffer[2]; 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 = buffer; msg.length = 2; /* Then perform the transfer. */ ret = I2C_TRANSFER(priv->i2c, &msg, 1); *portval = buffer[0] & 0xff; *portval |= (buffer[1] << 8) & 0xff00; return (ret >= 0) ? OK : ret; } /**************************************************************************** * Name: pcf8575_write * * Description: * Write a 16-bit value to a PCF8575xx 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 pcf8575_write(struct pcf8575_dev_s *priv, uint16_t portval) { struct i2c_msg_s msg; uint8_t buffer[2]; int ret; DEBUGASSERT(priv != NULL && priv->i2c != NULL && priv->config != NULL); /* Setup for the transfer */ buffer[0] = (uint8_t)(portval & 0xff); buffer[1] = (uint8_t)((portval & 0xff00) >> 8); msg.frequency = priv->config->frequency, msg.addr = priv->config->address; msg.flags = 0; msg.buffer = buffer; msg.length = 2; /* Then perform the transfer. */ ret = I2C_TRANSFER(priv->i2c, &msg, 1); return (ret >= 0) ? OK : ret; } /**************************************************************************** * Name: pcf8575_direction * * Description: * Set the direction of an ioexpander pin. Required. * * The PCF8575 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 * pcf8575_write() setting the bits you are interested in reading to 1's, * before calling pcf8575_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 pcf8575_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int direction) { FAR struct pcf8575_dev_s *priv = (FAR struct pcf8575_dev_s *)dev; int ret; if (direction != IOEXPANDER_DIRECTION_IN && direction != IOEXPANDER_DIRECTION_OUT) { return -EINVAL; } if (pin > 15) { return -ENXIO; } DEBUGASSERT(priv != NULL && priv->config != NULL); 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 */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* 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 put the * pin in the current limiting state. */ ret = pcf8575_write(priv, priv->inpins | priv->outstate); nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: pcf8575_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 pcf8575_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, FAR void *value) { FAR struct pcf8575_dev_s *priv = (FAR struct pcf8575_dev_s *)dev; int ret = OK; DEBUGASSERT(priv != NULL && priv->config != NULL); gpioinfo("I2C addr=%02x pin=%u option=%u\n", priv->config->address, pin, opt); return ret; } /**************************************************************************** * Name: pcf8575_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 pcf8575_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value) { FAR struct pcf8575_dev_s *priv = (FAR struct pcf8575_dev_s *)dev; int ret; if (pin > 15) { return -ENXIO; } DEBUGASSERT(priv != NULL && priv->config != NULL); gpioinfo("I2C addr=%02x pin=%u value=%u\n", priv->config->address, pin, value); /* Get exclusive access to the I/O Expander */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* Make sure that this is an output pin */ if ((priv->inpins & (1 << pin)) != 0) { gpioerr("ERROR: pin%u is an input\n", pin); nxmutex_unlock(&priv->lock); 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 = pcf8575_write(priv, priv->inpins | priv->outstate); nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: pcf8575_readpin * * Description: * Read the actual PIN level. This can be different from the last value * written to this pin. Required. * * The PCF8575 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 pcf8575_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value) { FAR struct pcf8575_dev_s *priv = (FAR struct pcf8575_dev_s *)dev; uint16_t regval; int ret; if (pin > 15) { return -ENXIO; } DEBUGASSERT(priv != NULL && priv->config != NULL && value != NULL); gpioinfo("I2C addr=%02x, pin=%u\n", priv->config->address, pin); /* Get exclusive access to the I/O Expander */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* 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); nxmutex_unlock(&priv->lock); 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 = pcf8575_read(priv, ®val); if (ret < 0) { gpioerr("ERROR: Failed to read port register: %d\n", ret); goto errout_with_lock; } /* Return 0 or 1 to indicate the state of pin */ *value = (bool)((regval >> (pin & 0xf)) & 1); ret = OK; errout_with_lock: nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: pcf8575_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 pcf8575_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count) { FAR struct pcf8575_dev_s *priv = (FAR struct pcf8575_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 */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* Process each pin setting */ for (i = 0; i < count; i++) { /* Make sure that this is an output pin */ pin = pins[i]; if (pin > 15) { nxmutex_unlock(&priv->lock); return -ENXIO; } 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 = pcf8575_write(priv, priv->inpins | priv->outstate); nxmutex_unlock(&priv->lock); return ret; } #endif /**************************************************************************** * Name: pcf8575_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 pcf8575_multireadpin(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count) { FAR struct pcf8575_dev_s *priv = (FAR struct pcf8575_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 */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* 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 = pcf8575_read(priv, ®val); if (ret < 0) { gpioerr("ERROR: Failed to read port register: %d\n", ret); goto errout_with_lock; } /* Return the requested pin values */ for (i = 0; i < count; i++) { /* Make sure that this is an output pin */ pin = pins[i]; if (pin > 15) { nxmutex_unlock(&priv->lock); return -ENXIO; } /* 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: nxmutex_unlock(&priv->lock); return ret; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: pcf8575_initialize * * Description: * Instantiate and configure the PCF8575xx 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 *pcf8575_initialize(FAR struct i2c_master_s *i2c, FAR struct pcf8575_config_s *config) { FAR struct pcf8575_dev_s *priv; #ifdef CONFIG_PCF8575_MULTIPLE /* Allocate the device state structure */ priv = (FAR struct pcf8575_dev_s *) kmm_zalloc(sizeof(struct pcf8575_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_pcf8575; #endif /* Initialize the device state structure */ priv->dev.ops = &g_pcf8575_ops; priv->i2c = i2c; priv->config = config; nxmutex_init(&priv->lock); return &priv->dev; } #endif /* CONFIG_IOEXPANDER_PCF8575 */