/**************************************************************************** * drivers/ioexpander/pca9557.c * * SPDX-License-Identifier: Apache-2.0 * * 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 "pca9557.h" #if defined(CONFIG_IOEXPANDER_PCA9557) /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static inline int pca9557_i2c_write(FAR struct pca9557_dev_s *pca, FAR const uint8_t *wbuffer, int wbuflen); static inline int pca9557_i2c_writeread(FAR struct pca9557_dev_s *pca, FAR const uint8_t *wbuffer, int wbuflen, FAR uint8_t *rbuffer, int rbuflen); static int pca9557_setbit(FAR struct pca9557_dev_s *pca, uint8_t reg, uint8_t pin, bool bitval); static int pca9557_getbit(FAR struct pca9557_dev_s *pca, uint8_t reg, uint8_t pin, FAR bool *val); static int pca9557_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int direction); static int pca9557_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, FAR void *value); static int pca9557_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value); static int pca9557_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value); static int pca9557_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value); #ifdef CONFIG_IOEXPANDER_MULTIPIN static int pca9557_getmultibits(FAR struct pca9557_dev_s *pca, uint8_t reg, FAR uint8_t *pins, FAR bool *values, int count); static int pca9557_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR const bool *values, int count); static int pca9557_multireadpin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR bool *values, int count); static int pca9557_multireadbuf(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count); #endif /**************************************************************************** * Private Data ****************************************************************************/ /* I/O expander vtable */ static const struct ioexpander_ops_s g_pca9557_ops = { .ioe_direction = pca9557_direction, .ioe_option = pca9557_option, .ioe_writepin = pca9557_writepin, .ioe_readpin = pca9557_readpin, .ioe_readbuf = pca9557_readbuf, #ifdef CONFIG_IOEXPANDER_MULTIPIN .ioe_multiwritepin = pca9557_multiwritepin, .ioe_multireadpin = pca9557_multireadpin, .ioe_multireadbuf = pca9557_multireadbuf, #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: pca9557_i2c_write * * Description: * Write to the I2C device. * ****************************************************************************/ static inline int pca9557_i2c_write(FAR struct pca9557_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: pca9557_i2c_writeread * * Description: * Write to then read from the I2C device. * ****************************************************************************/ static inline int pca9557_i2c_writeread(FAR struct pca9557_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: pca9557_setbit * * Description: * Write a bit in a register pair * ****************************************************************************/ static int pca9557_setbit(FAR struct pca9557_dev_s *pca, uint8_t reg, uint8_t pin, bool bitval) { uint8_t buf[2]; int ret; if (pin >= PCA9557_GPIO_NPINS) { return -ENXIO; } ret = nxrmutex_lock(&pca->lock); if (ret < 0) { return ret; } buf[0] = reg; #ifdef CONFIG_PCA9557_SHADOW_MODE /* Get the shadowed register value */ buf[1] = pca->sreg[reg]; #else /* Get the register value from the IO-Expander */ ret = pca9557_i2c_writeread(pca, &buf[0], 1, &buf[1], 1); if (ret < 0) { nxrmutex_unlock(&pca->lock); return ret; } #endif if (bitval) { buf[1] |= (1 << pin); } else { buf[1] &= ~(1 << pin); } ret = pca9557_i2c_write(pca, buf, 2); #ifdef CONFIG_PCA9557_RETRY if (ret != OK) { /* Try again (only once) */ ret = pca9557_i2c_write(pca, buf, 2); } #endif #ifdef CONFIG_PCA9557_SHADOW_MODE if (ret == OK) { /* Save the new register value in the shadow register */ pca->sreg[reg] = buf[1]; } #endif nxrmutex_unlock(&pca->lock); return ret; } /**************************************************************************** * Name: pca9557_getbit * * Description: * Get a bit from a register pair * ****************************************************************************/ static int pca9557_getbit(FAR struct pca9557_dev_s *pca, uint8_t reg, uint8_t pin, FAR bool *val) { uint8_t buf; int ret; if (pin >= PCA9557_GPIO_NPINS) { return -ENXIO; } ret = nxrmutex_lock(&pca->lock); if (ret < 0) { return ret; } ret = pca9557_i2c_writeread(pca, ®, 1, &buf, 1); if (ret < 0) { nxrmutex_unlock(&pca->lock); return ret; } #ifdef CONFIG_PCA9557_SHADOW_MODE /* Save the new register value in the shadow register */ pca->sreg[reg] = buf; #endif *val = (buf >> pin) & 1; nxrmutex_unlock(&pca->lock); return OK; } /**************************************************************************** * Name: pca9557_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 pca9557_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int direction) { if (direction != IOEXPANDER_DIRECTION_IN && direction != IOEXPANDER_DIRECTION_OUT) { return -EINVAL; } return pca9557_setbit((FAR struct pca9557_dev_s *)dev, PCA9557_REG_CONFIG, pin, (direction == IOEXPANDER_DIRECTION_IN)); } /**************************************************************************** * Name: pca9557_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 pca9557_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, FAR void *value) { int ret = -EINVAL; if (opt == IOEXPANDER_OPTION_INVERT) { ret = pca9557_setbit((FAR struct pca9557_dev_s *)dev, PCA9557_REG_POLINV, pin, ((uintptr_t)value == IOEXPANDER_VAL_INVERT)); } return ret; } /**************************************************************************** * Name: pca9557_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 pca9557_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value) { return pca9557_setbit((FAR struct pca9557_dev_s *)dev, PCA9557_REG_OUTPUT, pin, value); } /**************************************************************************** * Name: pca9557_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 pca9557_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value) { return pca9557_getbit((FAR struct pca9557_dev_s *)dev, PCA9557_REG_INPUT, pin, value); } /**************************************************************************** * Name: pca9557_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 pca9557_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value) { return pca9557_getbit((FAR struct pca9557_dev_s *)dev, PCA9557_REG_OUTPUT, pin, value); } #ifdef CONFIG_IOEXPANDER_MULTIPIN /**************************************************************************** * Name: pca9557_getmultibits * * Description: * Read multiple bits from PCA9557 registers. * ****************************************************************************/ static int pca9557_getmultibits(FAR struct pca9557_dev_s *pca, uint8_t reg, FAR uint8_t *pins, FAR bool *values, int count) { uint8_t val; int ret = OK; int i; int pin; ret = nxrmutex_lock(&pca->lock); if (ret < 0) { return ret; } ret = pca9557_i2c_writeread(pca, ®, 1, &val, 1); if (ret < 0) { nxrmutex_unlock(&pca->lock); return ret; } #ifdef CONFIG_PCA9557_SHADOW_MODE /* Save the new register value in the shadow register */ pca->sreg[reg] = val; #endif /* Read the requested bits */ for (i = 0; i < count; i++) { pin = pins[i]; if (pin >= PCA9557_GPIO_NPINS) { nxrmutex_unlock(&pca->lock); return -ENXIO; } values[i] = (val >> pin) & 1; } nxrmutex_unlock(&pca->lock); return OK; } /**************************************************************************** * Name: pca9557_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 pca9557_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR const bool *values, int count) { FAR struct pca9557_dev_s *pca = (FAR struct pca9557_dev_s *)dev; uint8_t reg = PCA9557_REG_OUTPUT; uint8_t buf[2]; int ret; int i; int pin; /* Get exclusive access to the PCA9557 */ ret = nxrmutex_lock(&pca->lock); 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_PCA9557_SHADOW_MODE ret = pca9557_i2c_writeread(pca, ®, 1, &buf[1], 1); if (ret < 0) { nxrmutex_unlock(&pca->lock); return ret; } #else /* In Shadow-Mode we "read" the pin status from the shadow registers */ buf[1] = pca->sreg[reg]; #endif /* Apply the user defined changes */ for (i = 0; i < count; i++) { pin = pins[i]; if (pin >= PCA9557_GPIO_NPINS) { nxrmutex_unlock(&pca->lock); return -ENXIO; } if (values[i]) { buf[1] |= (1 << pin); } else { buf[1] &= ~(1 << pin); } } /* Now write back the new pins states */ buf[0] = reg; #ifdef CONFIG_PCA9557_SHADOW_MODE /* Save the new register values in the shadow register */ pca->sreg[reg] = buf[1]; #endif ret = pca9557_i2c_write(pca, buf, 2); nxrmutex_unlock(&pca->lock); return ret; } /**************************************************************************** * Name: pca9557_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 pca9557_multireadpin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR bool *values, int count) { return pca9557_getmultibits((FAR struct pca9557_dev_s *)dev, PCA9557_REG_INPUT, pins, values, count); } /**************************************************************************** * Name: pca9557_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 pca9557_multireadbuf(FAR struct ioexpander_dev_s *dev, FAR uint8_t *pins, FAR bool *values, int count) { return pca9557_getmultibits((FAR struct pca9557_dev_s *)dev, PCA9557_REG_OUTPUT, pins, values, count); } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: pca9557_initialize * * Description: * Initialize a PCA9557 I2C device. * ****************************************************************************/ FAR struct ioexpander_dev_s * pca9557_initialize(FAR struct i2c_master_s *i2cdev, FAR struct pca9557_config_s *config) { FAR struct pca9557_dev_s *pcadev; DEBUGASSERT(i2cdev != NULL && config != NULL); if (config->set_nreset_pin) { config->set_nreset_pin(true); } /* Allocate the device state structure */ pcadev = kmm_zalloc(sizeof(struct pca9557_dev_s)); if (!pcadev) { return NULL; } /* Initialize the device state structure */ pcadev->i2c = i2cdev; pcadev->dev.ops = &g_pca9557_ops; pcadev->config = config; nxrmutex_init(&pcadev->lock); return &pcadev->dev; } #endif /* CONFIG_IOEXPANDER_PCA9557 */