arch/avr/src/avrdx: added I/O interrupt multiplexer

AVR DA/DB family chips have single interrupt vector for all changes
on an I/O port. This poses problem when multiple drivers want to claim
the same interrupt (might happen for example with button and discrete
joystick drivers using pins on the same port.)

The I/O multiplexer solves it by providing interface similar
to irq_attach. However, it allows registration of multiple handlers
for the same interrupt vector with additional information recording
which pins should be serviced by each handler.

Only handler for pins that triggered the interrupt is then called.
This commit is contained in:
Kerogit 2025-04-14 23:48:09 +02:00 committed by Xiang Xiao
parent 821fea2c2b
commit 065910c7d3
10 changed files with 682 additions and 1 deletions

View file

@ -0,0 +1,42 @@
==========================
GPIO interrupt multiplexer
==========================
AVR DA/DB family chips have single interrupt vector for all changes
on an I/O port. This poses problem when multiple drivers want to claim
the same interrupt (might happen for example with button and discrete
joystick drivers suing pins on the same port.)
The I/O multiplexer solves it by providing interface similar
to irq_attach. However, it allows registration of multiple handlers
for the same interrupt vector with additional information recording
which pins should be serviced by each handler.
Configuration
=============
The GPIO interrupt multiplexer is enabled in :menuselection:`System Type --> GPIO ISR Multiplexer`. All basic initialization is done automatically.
Usage
=====
Taking button input driver as an example, this driver requires that the board
provides ``board_button_irq`` method which enables and disables servicing
interrupts for given button. Other than ID of the button to be configured,
interrupt handler (function pointer) and opaque argument (void pointer) are provided.
Instead of calling ``irq_attach`` directly, this function may call
``avrdx_irq_attach_gpio_mux``. Handler and argument are among parameters
of this call but added to that are port index, pins and event that should
trigger the interrupt.
The multiplexer then attaches itself as the interrupt handler for the port.
It only runs handlers for pin(s) which triggered the interrupt.
It is possible to have 4 different handlers per port (currently hardcoded.)
Handler is considered to be different even if it is the same function but
with different opaque argument.
Each handler can have multiple pins assigned to it (provided as a bitmask)
but it is not possible to register one pin to multiple handlers.

View file

@ -20,6 +20,19 @@ config AVR_HAS_USART_5
bool
default n
# Available I/O ports (only those not common for all devices)
config AVR_HAS_PORTB
bool
default n
config AVR_HAS_PORTE
bool
default n
config AVR_HAS_PORTG
bool
default n
choice
prompt "Atmel AVR DA/DB chip selection"
default ARCH_CHIP_AVR128DA28
@ -33,6 +46,17 @@ config ARCH_CHIP_AVR128DA28
endchoice # AVR DA/DB Configuration Options
config AVR_AVRDX_GPIO_ISR_MUX
bool "GPIO ISR multiplexer"
default n
---help---
Compile ISR multiplexer code for GPIO. It can be used
instead of direct call of irq_attach when multiple
users (eg. drivers) want to use the same interrupt
vector with different pins on the same port.
Say Y if your board needs this.
menu "AVR DA/DB Peripheral Selections"
config AVR_USART0

View file

@ -51,7 +51,7 @@ endif
# Required AVRnDx files
CHIP_ASRCS = avrdx_exceptions.S
CHIP_CSRCS = avrdx_lowconsole.c avrdx_lowinit.c
CHIP_CSRCS = avrdx_lowconsole.c avrdx_lowinit.c avrdx_init.c
CHIP_CSRCS += avrdx_serial.c avrdx_serial_early.c
CHIP_CSRCS += avrdx_peripherals.c
@ -62,3 +62,7 @@ CHIP_CSRCS += avrdx_timerisr_tickless_alarm.c
else
CHIP_CSRCS += avrdx_timerisr.c
endif
ifeq ($(CONFIG_AVR_AVRDX_GPIO_ISR_MUX),y)
CHIP_CSRCS += avrdx_gpio_isr_mux.c
endif

View file

@ -126,6 +126,15 @@ void up_consoleinit(void);
void avrdx_boardinitialize(void);
/****************************************************************************
* Name: avrdx_up_initialize
*
* Description:
* Extension of up_initialize for AVR DA/DB cores
****************************************************************************/
void avrdx_up_initialize(void);
#undef EXTERN
#if defined(__cplusplus)
}

View file

@ -28,6 +28,7 @@
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/irq.h>
/****************************************************************************
* Pre-processor Definitions
@ -72,6 +73,8 @@ extern "C"
#define EXTERN extern
#endif
EXTERN const IOBJ uint8_t avrdx_gpio_irq_vectors[];
/****************************************************************************
* Inline Functions
****************************************************************************/
@ -80,6 +83,69 @@ extern "C"
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: avrdx_gpio_isr_mux_init
*
* Description:
* Initialize global variable holding ISR data
*
****************************************************************************/
#ifdef CONFIG_AVR_AVRDX_GPIO_ISR_MUX
void avrdx_gpio_isr_mux_init(void);
#endif
/****************************************************************************
* Name: avrdx_irq_attach_gpio_mux
*
* Description:
* Attaches given handler and arg to the GPIO IRQ multiplexer.
*
* Input Parameters:
* Requested port
* Pins handled by this handler
* Handler itself
* Opaque arg argument
* ISC - input sense configuration (ie. what pin change triggers
* the interrupt. See eg. AVR128DA28 datasheet, chapter PORT
* I/O pin configuration, section Register Description, subsection
* Pin n Control (17.5.16 in rev C of the document)
*
* Returned Value:
* OK or EBUSY if another handler already uses the pin. EINVAL for port
* that does not exist.
*
****************************************************************************/
#ifdef CONFIG_AVR_AVRDX_GPIO_ISR_MUX
int avrdx_irq_attach_gpio_mux(uint8_t port_idx, uint8_t pins,
xcpt_t handler, void *arg,
uint8_t isc);
#endif
/****************************************************************************
* Name: avrdx_irq_detach_gpio_mux
*
* Description:
* Detaches handler from GPIO IRQ multiplexer.
*
* Input Parameters:
* Requested port
* Pins to be detached. Removes handler completely if it no longer
* services any pin. (Except if keep_handler flag is set)
* Keep handler flag. See above.
*
* Returned Value:
* OK. EINVAL when detaching port that was not in use or other
* incorrect input.
*
****************************************************************************/
#ifdef CONFIG_AVR_AVRDX_GPIO_ISR_MUX
int avrdx_irq_detach_gpio_mux(uint8_t port_idx, uint8_t pins,
bool keep_handler);
#endif
#undef EXTERN
#ifdef __cplusplus
}

View file

@ -0,0 +1,418 @@
/****************************************************************************
* arch/avr/src/avrdx/avrdx_gpio_isr_mux.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 <nuttx/config.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <assert.h>
#include <syslog.h>
#include <avr/io.h>
#include "avrdx.h"
#include "avrdx_gpio.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define GPIO_MUX_MAX_CHANNELS 4
/****************************************************************************
* Private Types
****************************************************************************/
struct gpio_pin_isr_s
{
xcpt_t handler; /* handler given by the user */
FAR void *arg; /* arg to be given to the handler */
uint8_t pins; /* pins registered for this handler */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
struct gpio_pin_isr_s **g_gpio_pin_isrs;
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: avrdx_gpio_isr_mux
*
* Description:
* Interrupt handler for multiplexed I/O ports. Executes registered
* handlers if the pin(s) that raised the interrupt match pins
* the handler wanted for this port.
*
* Input Parameters:
* ISR parameters
*
* Assumptions:
* Always running in interrupt context.
*
****************************************************************************/
static int avrdx_gpio_isr_mux(int irq, void *context, FAR void *arg)
{
struct gpio_pin_isr_s *port_isrs;
uint8_t port_index;
uint8_t flags;
uint8_t irq8;
uint8_t i;
/* AVR has less than 256 interrupts, save flash and clock ticks */
irq8 = irq;
/* Look the port up based on IRQ number */
if (irq8 == AVRDX_IRQ_PORTA_PORT)
{
port_index = AVRDX_GPIO_PORTA_IDX;
}
else if (irq8 == AVRDX_IRQ_PORTC_PORT)
{
port_index = AVRDX_GPIO_PORTC_IDX;
}
else if (irq8 == AVRDX_IRQ_PORTD_PORT)
{
port_index = AVRDX_GPIO_PORTD_IDX;
}
else if (irq8 == AVRDX_IRQ_PORTF_PORT)
{
port_index = AVRDX_GPIO_PORTF_IDX;
}
#ifdef CONFIG_AVR_HAS_PORTB
else if (irq8 == AVRDX_IRQ_PORTB_PORT)
{
port_index = AVRDX_GPIO_PORTB_IDX;
}
#endif
#ifdef CONFIG_AVR_HAS_PORTE
else if (irq8 == AVRDX_IRQ_PORTE_PORT)
{
port_index = AVRDX_GPIO_PORTE_IDX;
}
#endif
#ifdef CONFIG_AVR_HAS_PORTG
else if (irq8 == AVRDX_IRQ_PORTG_PORT)
{
port_index = AVRDX_GPIO_PORTG_IDX;
}
#endif
else
{
/* Don't try to convert these to __memx (IPTR), syslog_add_intbuffer
* (write to syslog interrupt buffer) does not support that
*/
syslog(LOG_EMERG,
"avrdx_gpio_isr_mux unknown IRQ %i\n", irq);
PANIC(); /* How did we get here? */
}
port_isrs = g_gpio_pin_isrs[port_index];
if (!port_isrs)
{
syslog(LOG_EMERG,
"avrdx_gpio_isr_mux port handler not allocated\n");
PANIC(); /* IRQ is attached when this is allocated */
}
/* Read pins which triggred the interrupt and clear their flags */
flags = AVRDX_PORT(port_index).INTFLAGS;
AVRDX_PORT(port_index).INTFLAGS = flags;
for (i = 0; i < GPIO_MUX_MAX_CHANNELS; i++, port_isrs++)
{
if ((port_isrs) && (port_isrs->pins & flags))
{
port_isrs->handler(irq, context, port_isrs->arg);
}
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: avrdx_irq_attach_gpio_mux
*
* Description:
* Attaches given handler and arg to the GPIO IRQ multiplexer.
*
* Input Parameters:
* Requested port. Can use a number directly or preprocessor
* constant (one of AVRDX_GPIO_PORTA_IDX etc.)
* Pins handled by this handler. Given as bitmask.
* Handler itself
* Opaque arg argument
* ISC - input sense configuration (ie. what pin change triggers
* the interrupt. See eg. AVR128DA28 datasheet, chapter PORT
* I/O pin configuration, section Register Description, subsection
* Pin n Control (17.5.16 in rev C of the document)
*
* Returned Value:
* OK or EBUSY if another handler already uses the pin. EINVAL for port
* that does not exist.
*
****************************************************************************/
int avrdx_irq_attach_gpio_mux(uint8_t port_idx, uint8_t pins,
xcpt_t handler, void *arg,
uint8_t isc)
{
struct gpio_pin_isr_s *port_isrs;
irqstate_t irqflags;
uint8_t first_free_slot;
uint8_t vector_num;
int ret;
uint8_t i;
first_free_slot = GPIO_MUX_MAX_CHANNELS; /* No free slot was found yet */
#ifdef AVR_HAS_PORTG
if (port_idx > AVRDX_GPIO_PORTG_IDX)
{
return -EINVAL;
}
#else
if (port_idx > AVRDX_GPIO_PORTF_IDX)
{
return -EINVAL;
}
#endif
vector_num = avrdx_gpio_irq_vectors[port_idx];
if (!vector_num)
{
return -EINVAL;
}
irqflags = enter_critical_section();
port_isrs = g_gpio_pin_isrs[port_idx];
if (!port_isrs)
{
/* This port is not in use yet, also not attached. */
g_gpio_pin_isrs[port_idx] = \
(struct gpio_pin_isr_s *) kmm_zalloc \
(sizeof(struct gpio_pin_isr_s) * GPIO_MUX_MAX_CHANNELS);
port_isrs = g_gpio_pin_isrs[port_idx];
if (!port_isrs)
{
ret = -ENOMEM;
goto errout_lcs;
}
irq_attach(vector_num, avrdx_gpio_isr_mux, NULL);
}
/* Search existing handlers to find out if the new one
* is actually new. Parameter arg must match too to reuse
* existing handler.
*/
for (i = 0; i < GPIO_MUX_MAX_CHANNELS; i++, port_isrs++)
{
if (!(port_isrs->handler))
{
if (first_free_slot == GPIO_MUX_MAX_CHANNELS)
{
first_free_slot = i; /* Record it for possible use later */
}
continue;
}
if ((port_isrs->handler == handler) && (port_isrs->arg == arg))
{
port_isrs->pins |= pins; /* Add pins, handler exists. */
break;
}
}
if (i == GPIO_MUX_MAX_CHANNELS)
{
/* Reached end of the array and did not find the handler
* being in use already. Add new record - if there is space
*/
if (first_free_slot == GPIO_MUX_MAX_CHANNELS)
{
ret = -ENOMEM; /* no space */
goto errout_lcs;
}
port_isrs = g_gpio_pin_isrs[port_idx]; /* reset to start */
port_isrs[first_free_slot].handler = handler;
port_isrs[first_free_slot].arg = arg;
port_isrs[first_free_slot].pins = pins;
}
/* One way or the other, if we got here, pins are now serviced
* by an interrupt handler. Except the hardware is not configured
* to to that yet. Do it now, clear existing interrupt flags first
*/
AVRDX_PORT(port_idx).INTFLAGS = pins;
AVRDX_PORT(port_idx).PINCONFIG = PORT_ISC_GM;
AVRDX_PORT(port_idx).PINCTRLCLR = pins;
AVRDX_PORT(port_idx).PINCONFIG = isc;
AVRDX_PORT(port_idx).PINCTRLSET = pins;
leave_critical_section(irqflags);
return OK;
errout_lcs:
leave_critical_section(irqflags);
return ret;
}
/****************************************************************************
* Name: avrdx_irq_detach_gpio_mux
*
* Description:
* Detaches handler from GPIO IRQ multiplexer.
*
* Input Parameters:
* Requested port
* Pins to be detached. Removes handler completely if it no longer
* services any pin. (Except if keep_handler flag is set)
* Keep handler flag. See above.
*
* Returned Value:
* OK. EINVAL when detaching port that was not in use or other
* incorrect input.
*
****************************************************************************/
int avrdx_irq_detach_gpio_mux(uint8_t port_idx, uint8_t pins,
bool keep_handler)
{
struct gpio_pin_isr_s *port_isrs;
irqstate_t irqflags;
int ret;
uint8_t i;
#ifdef AVR_HAS_PORTG
if (port_idx > AVRDX_GPIO_PORTG_IDX)
{
return -EINVAL;
}
#else
if (port_idx > AVRDX_GPIO_PORTF_IDX)
{
return -EINVAL;
}
#endif
irqflags = enter_critical_section();
port_isrs = g_gpio_pin_isrs[port_idx];
if (!port_isrs)
{
ret = -EINVAL;
goto errout_lcs;
}
for (i = 0; i < GPIO_MUX_MAX_CHANNELS; i++, port_isrs++)
{
if (port_isrs->pins & pins)
{
port_isrs->pins &= ~pins;
if (!(port_isrs->pins) && (!keep_handler))
{
port_isrs->handler = 0;
port_isrs->arg = 0;
}
}
/* Continue the loop, pins may touch more than one record */
}
/* Note that pins may contain bits that do not match any handler.
* That's currently silently ignored
*/
/* Disable interrupts for detached pins and clear their
* interrupt flags
*/
AVRDX_PORT(port_idx).INTFLAGS = pins;
AVRDX_PORT(port_idx).PINCONFIG = PORT_ISC_GM;
AVRDX_PORT(port_idx).PINCTRLCLR = pins;
leave_critical_section(irqflags);
return OK;
errout_lcs:
leave_critical_section(irqflags);
return ret;
}
/****************************************************************************
* Name: avrdx_gpio_isr_mux_init
*
* Description:
* Initialize global variable holding ISR data
*
****************************************************************************/
void avrdx_gpio_isr_mux_init(void)
{
#ifdef AVR_HAS_PORTG
g_gpio_pin_isrs = (struct gpio_pin_isr_s **) kmm_zalloc \
(sizeof(struct gpio_pin_isr_s *) * (AVRDX_GPIO_PORTG_IDX + 1));
#else
g_gpio_pin_isrs = (struct gpio_pin_isr_s **) kmm_zalloc \
(sizeof(struct gpio_pin_isr_s *) * (AVRDX_GPIO_PORTF_IDX + 1));
#endif
}

View file

@ -0,0 +1,72 @@
/****************************************************************************
* arch/avr/src/avrdx/avrdx_init.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 <nuttx/config.h>
#include "avrdx_gpio.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: avrdx_up_initialize
*
* Description:
* Extension of up_initialize for AVR DA/DB cores
****************************************************************************/
void avrdx_up_initialize(void)
{
/* Initialize GPIO interrupt multiplexer, if enabled */
#ifdef CONFIG_AVR_AVRDX_GPIO_ISR_MUX
avrdx_gpio_isr_mux_init();
#endif
}

View file

@ -28,6 +28,7 @@
#include "avrdx.h"
#include "avrdx_gpio.h"
#include <avr/io.h>
#include <arch/irq.h>
/****************************************************************************
* Pre-processor Definitions
@ -141,6 +142,39 @@ const IOBJ uint8_t avrdx_usart_tx_pins[] =
#endif
/* Interrupt vector numbers for pin change interrupts
* indexed by port number (with port A being indexed as 0,
* port B as 1 etc.) Does not skip vectors that the chip
* does not have to keep the index identical for all chips.
*/
const IOBJ uint8_t avrdx_gpio_irq_vectors[] =
{
AVRDX_IRQ_PORTA_PORT,
#ifdef CONFIG_AVR_HAS_PORTB
AVRDX_IRQ_PORTB_PORT,
#else
0,
#endif
AVRDX_IRQ_PORTC_PORT,
AVRDX_IRQ_PORTD_PORT,
#ifdef CONFIG_AVR_HAS_PORTE
AVRDX_IRQ_PORTE_PORT,
#else
0,
#endif
AVRDX_IRQ_PORTF_PORT
#ifdef CONFIG_AVR_HAS_PORTG
, AVRDX_IRQ_PORTG_PORT
#else
, 0
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/

View file

@ -37,6 +37,10 @@
# error "Do not include this file directly, use avrdx_iodefs.h instead"
#endif
/* PORT.PINCONFIG */
#define PORT_ISC_GM ( PORT_ISC_0_bm | PORT_ISC_1_bm | PORT_ISC_2_bm )
/* CLKCTRL.MCLKCTRLB */
#define CLKCTRL_PDIV_GM ( CLKCTRL_PDIV_0_bm | CLKCTRL_PDIV_1_bm | \

View file

@ -30,6 +30,10 @@
#include "avr_internal.h"
#ifdef CONFIG_ARCH_CHIP_AVRDX
# include "avrdx.h"
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
@ -153,6 +157,10 @@ void up_initialize(void)
up_pminitialize();
#endif
#ifdef CONFIG_ARCH_CHIP_AVRDX
avrdx_up_initialize();
#endif
#ifdef CONFIG_ARCH_DMA
/* Initialize the DMA subsystem if the weak function avr_dma_initialize has
* been brought into the build