ioexpander/icjx: add support for change of input interrupts on NINT

iC-JX expander has NINT (not interrupt) pin that goes to logical zero
if interrupt occurs. This commit adds support for iC-JX options settings
that allows to enable the interrupt for defined input pins.

The interrupt is handled in HP worker thread to avoid waiting for SPI
transfers in interrupt context. Board has to configure interrupt event
for GPIO pin connected to NINT.

Signed-off-by: Michal Lenc <michallenc@seznam.cz>
This commit is contained in:
Michal Lenc 2024-04-05 12:21:01 +02:00 committed by Alan Carvalho de Assis
parent 16b30ea659
commit 17e1d43f6d
3 changed files with 294 additions and 5 deletions

View file

@ -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:

View file

@ -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 */

View file

@ -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
};
/****************************************************************************