/**************************************************************************** * drivers/ioexpander/icjx.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 #include "icjx.h" #ifdef CONFIG_IOEXPANDER_ICJX /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define ICJX_NOP 0x00 #define ICJX_RNW 0x01 #define ICJX_NOB1 0x0f #define ICJX_NOB2 0x1e #define ICJX_CONTROL 0x59 /**************************************************************************** * Private Types ****************************************************************************/ struct icjx_callback_s { ioe_pinset_t pinset; ioe_callback_t function; FAR void *arg; }; struct icjx_dev_s { struct ioexpander_dev_s dev; /* Nested structure to allow casting * as public gpio expander. */ FAR struct icjx_config_s *config; /* Board configuration data */ FAR struct spi_dev_s *spi; /* Saved SPI driver instance */ uint16_t outpins; uint16_t outstate; uint16_t irqpins; mutex_t lock; #ifdef CONFIG_IOEXPANDER_INT_ENABLE struct work_s work; struct icjx_callback_s callback; #endif }; /**************************************************************************** * Private Function Protototypes ****************************************************************************/ /* SPI helpers */ static void icjx_select(FAR struct spi_dev_s *spi, FAR struct icjx_config_s *config, int bits); static void icjx_deselect(FAR struct spi_dev_s *spi, FAR struct icjx_config_s *config); /* Read/Write helpers */ static int icjx_read(FAR struct icjx_dev_s *priv, uint8_t reg, uint16_t *data, int nob); static int icjx_write(FAR struct icjx_dev_s *priv, uint8_t reg, uint16_t data, int nob); /* I/O Expander Methods */ static int icjx_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int dir); static int icjx_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, void *regval); static int icjx_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value); static int icjx_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value); #ifdef CONFIG_IOEXPANDER_MULTIPIN static int icjx_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR const bool *values, int count); static int icjx_multireadpin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR bool *values, int count); #endif #ifdef CONFIG_IOEXPANDER_INT_ENABLE static FAR void *icjx_attach(FAR struct ioexpander_dev_s *dev, ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg); static int icjx_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle); #endif /**************************************************************************** * Private Data ****************************************************************************/ #ifndef CONFIG_ICJX_MULTIPLE /* If only a single device is supported, then the driver state structure may * as well be pre-allocated. */ static struct icjx_dev_s g_icjx; #endif /* I/O expander vtable */ static const struct ioexpander_ops_s g_icjx_ops = { icjx_direction, icjx_option, icjx_writepin, icjx_readpin, icjx_readpin #ifdef CONFIG_IOEXPANDER_MULTIPIN , icjx_multiwritepin , icjx_multireadpin , icjx_multireadpin #endif #ifdef CONFIG_IOEXPANDER_INT_ENABLE , icjx_attach , icjx_detach #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: icjx_select * * Description: * Select the SPI, locking and re-configuring if necessary * * Input Parameters: * spi - Reference to the SPI driver structure * config - Reference to iC-JX configuration structure * bits - Number of SPI bits * * Returned Value: * None * ****************************************************************************/ static void icjx_select(FAR struct spi_dev_s *spi, FAR struct icjx_config_s *config, int bits) { /* Select iC-JX chip (locking the SPI bus in case there are multiple * devices competing for the SPI bus */ SPI_LOCK(spi, true); SPI_SELECT(spi, SPIDEV_EXPANDER(config->id), true); /* Now make sure that the SPI bus is configured for the iC-JX (it * might have gotten configured for a different device while unlocked) */ SPI_SETMODE(spi, config->mode); SPI_SETBITS(spi, bits); SPI_SETFREQUENCY(spi, config->frequency); } /**************************************************************************** * Name: icjx_deselect * * Description: * De-select the SPI * * Input Parameters: * spi - Reference to the SPI driver structure * config - Reference to iC-JX configuration structure * * Returned Value: * None * ****************************************************************************/ static void icjx_deselect(FAR struct spi_dev_s *spi, FAR struct icjx_config_s *config) { /* De-select iC-JX chip and relinquish the SPI bus. */ SPI_SELECT(spi, SPIDEV_EXPANDER(config->id), false); SPI_LOCK(spi, false); } /**************************************************************************** * Name: icjx_read * * Description: * Helper function for register read operation. * * Input Parameters: * priv - Pointer to icjx_dev_s structure * reg - Register offset (see icjx.h) * data - Pointer to read data * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ static int icjx_read(FAR struct icjx_dev_s *priv, uint8_t reg, uint16_t *data, int nob) { uint8_t startaddr; uint8_t tx_buffer[6]; uint8_t rx_buffer[6]; startaddr = (priv->config->addr << 6) | (reg << 1) | ICJX_RNW; tx_buffer[0] = startaddr; tx_buffer[1] = ICJX_NOP; tx_buffer[2] = nob; icjx_select(priv->spi, priv->config, 8); SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, 3); *data = rx_buffer[2]; if (priv->config->verification) { if (nob == ICJX_NOB2) { tx_buffer[0] = rx_buffer[2]; SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, 1); *data |= rx_buffer[0] << 8; tx_buffer[0] = rx_buffer[0]; startaddr = (priv->config->addr << 6) | ((reg + 1) << 1) | ICJX_RNW; } else { tx_buffer[0] = rx_buffer[2]; } tx_buffer[1] = ICJX_CONTROL; SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, 2); } icjx_deselect(priv->spi, priv->config); if (priv->config->verification) { if (rx_buffer[0] != startaddr) { gpioerr("ERROR: Data verification error for register 0x%x!\n", reg); return -EIO; } if (rx_buffer[1] != ICJX_CONTROL) { gpioerr("ERROR: Control byte verification error for register" "0x%x!\n", reg); return -EIO; } } return OK; } /**************************************************************************** * Name: icjx_write * * Description: * Helper function for register write operation. * * Input Parameters: * priv - Pointer to icjx_dev_s structure * reg - Register offset (see icjx.h) * data - Data to be written * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ static int icjx_write(FAR struct icjx_dev_s *priv, uint8_t reg, uint16_t data, int nob) { uint8_t startaddr; uint8_t bytes_to_exchange; uint8_t tx_buffer[6]; uint8_t rx_buffer[6]; int ver_idx; int data_len; data_len = 1; ver_idx = 2; bytes_to_exchange = 0; startaddr = (priv->config->addr << 6) | (reg << 1); tx_buffer[bytes_to_exchange++] = startaddr; tx_buffer[bytes_to_exchange++] = nob; tx_buffer[bytes_to_exchange++] = data & 0xff; if (nob == ICJX_NOB2) { data_len = 2; tx_buffer[bytes_to_exchange++] = (data >> 8) & 0xff; } if (priv->config->verification) { tx_buffer[bytes_to_exchange++] = (priv->config->addr << 6) | ((reg + (data_len - 1)) << 1); tx_buffer[bytes_to_exchange++] = ICJX_CONTROL; } icjx_select(priv->spi, priv->config, 8); SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, bytes_to_exchange); icjx_deselect(priv->spi, priv->config); if (priv->config->verification) { if (rx_buffer[ver_idx++] != nob) { gpioerr("ERROR: Start address verification error for register" "0x%x!\n", reg); return -EIO; } for (int i = 0; i < data_len; i++) { if (rx_buffer[ver_idx++] != ((data >> (i * 8)) & 0xff)) { gpioerr("ERROR: Data verification error for register 0x%x!\n", reg); return -EIO; } } if (rx_buffer[ver_idx++] != ICJX_CONTROL) { gpioerr("ERROR: Control verification error for register 0x%x!\n", reg); return -EIO; } } return OK; } /**************************************************************************** * Name: icjx_direction * * Description: * iC-JX is only input pin. However interface is provided in order * to avoid system falls if called. * * 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 icjx_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, int dir) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; uint8_t outpins; uint16_t out_set; uint8_t regaddr; int ret; if (dir != IOEXPANDER_DIRECTION_IN && dir != IOEXPANDER_DIRECTION_OUT) { return -EINVAL; } DEBUGASSERT(priv != NULL && priv->config != NULL && pin < 16); ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } if (dir == IOEXPANDER_DIRECTION_OUT) { priv->outpins |= (1 << pin); } else { priv->outpins &= ~(1 << pin); } if (pin < 8) { regaddr = ICJX_CTRL_WORD_2_A; outpins = priv->outpins & 0xff; } else { regaddr = ICJX_CTRL_WORD_2_B; outpins = (priv->outpins >> 8) & 0xff; } ret = icjx_read(priv, regaddr, &out_set, ICJX_NOB1); if (ret < 0) { nxmutex_unlock(&priv->lock); return ret; } /* Enable output generation */ if ((outpins & 0xf) != 0) { out_set |= ICJX_CTRL_WORD_2_NIOL; } else { out_set &= ~ICJX_CTRL_WORD_2_NIOL; } if ((outpins & 0xf0) != 0) { out_set |= ICJX_CTRL_WORD_2_NIOH; } else { out_set &= ~ICJX_CTRL_WORD_2_NIOH; } ret = icjx_write(priv, regaddr, out_set, ICJX_NOB1); nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: icjx_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 * value - The option's value * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ static int icjx_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, int opt, FAR void *value) { /* TODO: Implementation of iC-JX options should be here. This includes * setup of filters, ADC etc. The right way to implement * this would probably be to introduce config structure to * include/nuttx/ioexpanders/icjx.h that the user could use for the * nibbles configuration. * * Currently only interrupts are implemented. */ FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; uint8_t data; uint8_t reg; int ret; DEBUGASSERT(priv != NULL && priv->config != NULL); gpioinfo("Expander id=%02x pin=%u option=%u\n", priv->config->id, pin, opt); if (opt == IOEXPANDER_OPTION_INTCFG) { unsigned int ival = (unsigned int)((uintptr_t)value); ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } switch (ival) { case IOEXPANDER_VAL_HIGH: case IOEXPANDER_VAL_LOW: case IOEXPANDER_VAL_RISING: case IOEXPANDER_VAL_FALLING: case IOEXPANDER_VAL_BOTH: priv->irqpins |= 1 << pin; break; case IOEXPANDER_VAL_DISABLE: priv->irqpins &= ~(1 << pin); break; default: nxmutex_unlock(&priv->lock); return -EINVAL; } /* We have to modify ICJX_CHNG_INT_EN_A or ICJX_CHNG_INT_EN_B * register. */ if (pin < 8) { reg = ICJX_CHNG_INT_EN_A; data = priv->irqpins & 0xff; } else { reg = ICJX_CHNG_INT_EN_B; data = (priv->irqpins >> 8) & 0xff; } ret = icjx_write(priv, reg, data, ICJX_NOB1); if (ret < 0) { gpioerr("Cannot write to %s register\n.", reg == ICJX_CHNG_INT_EN_A ? "ICJX_CHNG_INT_EN_A" : "ICJX_CHNG_INT_EN_B"); } nxmutex_unlock(&priv->lock); } else { return -ENOTTY; } return ret; } /**************************************************************************** * Name: icjx_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 * value - 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 icjx_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, bool value) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; uint8_t outstate; uint8_t regaddr; int ret; if (pin > 16) { return -ENXIO; } DEBUGASSERT(priv != NULL && priv->config != NULL); gpioinfo("Expander id=%02x, pin=%u\n", priv->config->id, pin); /* Get exclusive access to the I/O Expander */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } if ((priv->outpins & (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); } if (pin < 8) { regaddr = ICJX_OUTPUT_A; outstate = priv->outstate & 0xff; } else { regaddr = ICJX_OUTPUT_B; outstate = (priv->outstate >> 8) & 0xff; } ret = icjx_write(priv, regaddr, outstate, ICJX_NOB1); nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: icjx_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 * value - Pointer to a buffer where the pin level is stored. Usually * TRUE if the pin is high, except if OPTION_INVERT has been * set this pin. * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ static int icjx_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, FAR bool *value) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; uint8_t regaddr; uint16_t data; int ret; if (pin > 16) { return -ENXIO; } DEBUGASSERT(priv != NULL && priv->config != NULL && value != NULL); gpioinfo("Expander addr=%02x, pin=%u\n", priv->config->addr, pin); /* 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->outpins & (1 << pin)) != 0) { *value = ((priv->outstate & (1 << pin)) != 0); nxmutex_unlock(&priv->lock); return OK; } regaddr = pin < 8 ? ICJX_INPUT_A : ICJX_INPUT_B; ret = icjx_read(priv, regaddr, &data, ICJX_NOB1); nxmutex_unlock(&priv->lock); if (ret != OK) { return ret; } *value = (bool)((data >> (pin & 0xf)) & 1); return OK; } /**************************************************************************** * Name: icjx_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 * values - The list of pin levels. * count - Number of pins to be written * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ #ifdef CONFIG_IOEXPANDER_MULTIPIN static int icjx_multiwritepin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR const bool *values, int count) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; int ret; int pin; int value; if (count >= 16) { return -ENXIO; } DEBUGASSERT(priv != NULL && priv->config != NULL); /* Get exclusive access to the I/O Expander */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } for (int i = 0; i < count; i++) { pin = pins[i]; value = values[i]; gpioinfo("Expander id=%02x, pin=%u\n", priv->config->id, pin); if ((priv->outpins & (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); } } ret = icjx_write(priv, ICJX_OUTPUT_A, priv->outstate, ICJX_NOB2); nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: icjx_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 * values - Pointer to a buffer where the pin levels are stored. * count - Number of pins to be read * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ static int icjx_multireadpin(FAR struct ioexpander_dev_s *dev, FAR const uint8_t *pins, FAR bool *values, int count) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; uint16_t data; int pin; int ret; if (count > 16) { return -ENXIO; } DEBUGASSERT(priv != NULL && priv->config != NULL && values != NULL); /* Get exclusive access to the I/O Expander */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } for (int i = 0; i < count; i++) { pin = pins[i]; gpioinfo("Expander id=%02x, pin=%u\n", priv->config->id, pin); if ((priv->outpins & (1 << pin)) != 0) { values[i] = (bool)((priv->outstate & (1 << pin)) != 0); } } ret = icjx_read(priv, ICJX_INPUT_A, &data, ICJX_NOB2); nxmutex_unlock(&priv->lock); if (ret != OK) { return ret; } for (int i = 0; i < count; i++) { values[i] = (bool)((data >> (pins[i] & 0xf)) & 1); } return OK; } #endif /* CONFIG_IOEXPANDER_MULTIPIN */ /**************************************************************************** * Name: icjx_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_IOEXPANDER_INT_ENABLE static FAR void *icjx_attach(FAR struct ioexpander_dev_s *dev, ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; FAR void *handle = NULL; int ret; /* Get exclusive access */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return NULL; } if (priv->callback.function == NULL) { /* Yes.. use this entry */ priv->callback.pinset = pinset; priv->callback.function = callback; priv->callback.arg = arg; handle = &priv->callback; } /* Add this callback to the table */ nxmutex_unlock(&priv->lock); return handle; } /**************************************************************************** * Name: icjx_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 pca9555_attch() * * Returned Value: * 0 on success, else a negative error code * ****************************************************************************/ static int icjx_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle) { FAR struct icjx_callback_s *cb = (FAR struct icjx_callback_s *)handle; cb->pinset = 0; cb->function = NULL; cb->arg = NULL; return OK; } /**************************************************************************** * Name: icjx_interrupt_worker * * Description: * Handle GPIO interrupt events (this function actually executes in the * context of the worker thread). * ****************************************************************************/ #ifdef CONFIG_ICJX_INT_ENABLE static void icjx_interrupt_worker(void *arg) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)arg; uint16_t change_of_input; uint16_t isr; ioe_pinset_t irq_match; int ret; /* Read interrupt status register */ icjx_read(priv, ICJX_INT_STATUS_A, &isr, ICJX_NOB2); while (isr != 0) { if ((isr & ICJX_ISR_A_DCHI) != 0) { ret = icjx_read(priv, ICJX_CHNG_MSG_A, &change_of_input, ICJX_NOB2); if (ret == OK) { irq_match = change_of_input & priv->callback.pinset; if (irq_match != 0) { /* Change of input.. perform the callback */ priv->callback.function(&priv->dev, irq_match, priv->callback.arg); } } } /* Clear interrupt and check ISR again */ icjx_write(priv, ICJX_CTRL_WORD_4, ICJX_CTRL_WORD_4_EOI, ICJX_NOB1); icjx_read(priv, ICJX_INT_STATUS_A, &isr, ICJX_NOB2); } } /**************************************************************************** * Name: icjx_interrupt * * Description: * Handle GPIO interrupt events (this function executes in the * context of the interrupt). * ****************************************************************************/ static int icjx_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)arg; /* Create HP work to handle the interrupt. We do not want to do * this in the interrupt handler because SPI communication speed. */ DEBUGASSERT(work_available(&priv->work)); DEBUGVERIFY(work_queue(HPWORK, &priv->work, icjx_interrupt_worker, (void *)priv, 0)); return OK; } #endif /* CONFIG_ICJX_INT_ENABLE */ #endif /* CONFIG_IOEXPANDER_INT_ENABLE */ /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: icjx_initialize * * Description: * Instantiate and configure the ICJXxx device driver to use the * provided SPI device instance. * * Input Parameters: * spi - A SPI driver instance * config - Persistent board configuration data * * Returned Value: * an ioexpander_dev_s instance on success, NULL on failure. * ****************************************************************************/ FAR struct ioexpander_dev_s *icjx_initialize(FAR struct spi_dev_s *spi, FAR struct icjx_config_s *config) { FAR struct icjx_dev_s *priv; uint8_t regval; int ret; #ifdef CONFIG_ICJX_MULTIPLE /* Allocate the device state structure */ priv = kmm_zalloc(sizeof(struct icjx_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_icjx; #endif /* Initialize the device state structure */ priv->dev.ops = &g_icjx_ops; priv->spi = spi; priv->config = config; priv->outpins = 0; priv->outstate = 0; priv->irqpins = 0; #ifdef CONFIG_ICJX_INT_ENABLE config->attach(config, icjx_interrupt, priv); config->enable(config, true); #endif nxmutex_init(&priv->lock); /* Set pull up/down based on expander configuration. This is the first * time we are accessing the expander therefore we do not have to worry * about reading the register content first. */ regval = (config->current_src << 4) | config->current_src; ret = icjx_write(priv, ICJX_CTRL_WORD_2_A, regval, ICJX_NOB1); if (ret < 0) { gpioerr("ERROR: Could write to ICJX_CTRL_WORD_2_A: %d!\n", ret); goto err; } ret = icjx_write(priv, ICJX_CTRL_WORD_2_B, regval, ICJX_NOB1); if (ret < 0) { gpioerr("ERROR: Could write to ICJX_CTRL_WORD_2_B: %d!\n", ret); goto err; } /* Bypass filters as those are not yet supported. */ regval = ICJX_CTRL_WORD_1_BYP0 | ICJX_CTRL_WORD_1_BYP1; ret = icjx_write(priv, ICJX_CTRL_WORD_1_A, regval, ICJX_NOB1); if (ret < 0) { gpioerr("ERROR: Could write to ICJX_CTRL_WORD_1_A: %d!\n", ret); goto err; } ret = icjx_write(priv, ICJX_CTRL_WORD_1_B, regval, ICJX_NOB1); if (ret < 0) { gpioerr("ERROR: Could write to ICJX_CTRL_WORD_1_B: %d!\n", ret); goto err; } /* Clear initial interrupts if any */ icjx_write(priv, ICJX_CTRL_WORD_4, ICJX_CTRL_WORD_4_EOI, ICJX_NOB1); return &priv->dev; err: nxmutex_destroy(&priv->lock); #ifdef CONFIG_ICJX_MULTIPLE kmm_free(priv); #endif return NULL; } #endif /* CONFIG_IOEXPANDER_ICJX */