diff --git a/drivers/ioexpander/icjx.c b/drivers/ioexpander/icjx.c index 792e2dacb77..90fc94d3117 100644 --- a/drivers/ioexpander/icjx.c +++ b/drivers/ioexpander/icjx.c @@ -53,15 +53,29 @@ * 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 }; /**************************************************************************** @@ -100,6 +114,12 @@ 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 @@ -127,6 +147,10 @@ static const struct ioexpander_ops_s g_icjx_ops = , icjx_multireadpin , icjx_multireadpin #endif +#ifdef CONFIG_IOEXPANDER_INT_ENABLE + , icjx_attach + , icjx_detach +#endif }; /**************************************************************************** @@ -463,15 +487,81 @@ 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, interrupts etc. The right way to implement + * 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. */ - gpiowarn("ERROR: iC-JX options are not yet implemted!\n"); + FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)dev; + uint8_t data; + uint8_t reg; + int ret; - return -ENOTSUP; + 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; } /**************************************************************************** @@ -742,7 +832,7 @@ static int icjx_multireadpin(FAR struct ioexpander_dev_s *dev, if ((priv->outpins & (1 << pin)) != 0) { - values[i] = (priv->outstate & (1 << pin)) != 0; + values[i] = (bool)((priv->outstate & (1 << pin)) != 0); } } @@ -755,13 +845,164 @@ static int icjx_multireadpin(FAR struct ioexpander_dev_s *dev, for (int i = 0; i < count; i++) { - values[i] = (data >> (pins[i] & 0xf)) & 1; + 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). + * + ****************************************************************************/ + +static void icjx_interrupt_worker(void *arg) +{ + FAR struct icjx_dev_s *priv = (FAR struct icjx_dev_s *)arg; + uint8_t regaddr; + 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 + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -811,6 +1052,12 @@ FAR struct ioexpander_dev_s *icjx_initialize(FAR struct spi_dev_s *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); @@ -852,6 +1099,10 @@ FAR struct ioexpander_dev_s *icjx_initialize(FAR struct spi_dev_s *spi, 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: diff --git a/drivers/ioexpander/icjx.h b/drivers/ioexpander/icjx.h index f0a736ce0e0..550924394c0 100644 --- a/drivers/ioexpander/icjx.h +++ b/drivers/ioexpander/icjx.h @@ -98,5 +98,29 @@ #define ICJX_CTRL_WORD_2_NIOL (1 << 3) #define ICJX_CTRL_WORD_2_NIOH (1 << 7) +/* Control Word 4 */ + +#define ICJX_CTRL_WORD_4_EOI (1 << 7) + +/* Interrupt Status Register A */ + +#define ICJX_ISR_A_SCS (1 << 0) +#define ICJX_ISR_A_ET1 (1 << 1) +#define ICJX_ISR_A_ET2 (1 << 2) +#define ICJX_ISR_A_ISCI (1 << 4) +#define ICJX_ISR_A_IET1 (1 << 5) +#define ICJX_ISR_A_IET2 (1 << 6) +#define ICJX_ISR_A_DCHI (1 << 7) + +/* Interrupt Status Register B */ + +#define ICJX_ISR_B_USA (1 << 0) +#define ICJX_ISR_B_USD (1 << 1) +#define ICJX_ISR_B_EOC (1 << 2) +#define ICJX_ISR_B_IUSA (1 << 4) +#define ICJX_ISR_B_IUSD (1 << 5) +#define ICJX_ISR_B_ISD (1 << 6) +#define ICJX_ISR_B_IOEC (1 << 7) + #endif /* CONFIG_IOEXPANDER && CONFIG_IOEXPANDER_ICJX */ #endif /* __DRIVERS_IOEXPANDER_ICJX_H */ diff --git a/include/nuttx/ioexpander/icjx.h b/include/nuttx/ioexpander/icjx.h index 629a048019d..aa133a4495d 100644 --- a/include/nuttx/ioexpander/icjx.h +++ b/include/nuttx/ioexpander/icjx.h @@ -67,6 +67,20 @@ struct icjx_config_s uint8_t addr; /* Device address (set by A(1:0) pins) */ uint8_t mode; /* SPI mode */ uint32_t frequency; /* SPI frequency */ + +#ifdef CONFIG_IOEXPANDER_INT_ENABLE + /* IRQ/GPIO access callbacks. These operations all hidden behind + * callbacks to isolate the iC-JX driver from differences in GPIO + * interrupt handling by varying boards and MCUs. + * + * attach - Attach the iC-JX interrupt handler to the GPIO interrupt + * enable - Enable or disable the GPIO interrupt + */ + + CODE int (*attach)(FAR struct icjx_config_s *config, xcpt_t handler, + FAR void *arg); + CODE void (*enable)(FAR struct icjx_config_s *config, bool enable); +#endif }; /****************************************************************************