diff --git a/Documentation/platforms/avr/avrdx/gpio_intr_mux.rst b/Documentation/platforms/avr/avrdx/gpio_intr_mux.rst new file mode 100644 index 00000000000..9c73a4d454f --- /dev/null +++ b/Documentation/platforms/avr/avrdx/gpio_intr_mux.rst @@ -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. diff --git a/arch/avr/src/avrdx/Kconfig b/arch/avr/src/avrdx/Kconfig index ee3081410fb..dd36fada4f7 100644 --- a/arch/avr/src/avrdx/Kconfig +++ b/arch/avr/src/avrdx/Kconfig @@ -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 diff --git a/arch/avr/src/avrdx/Make.defs b/arch/avr/src/avrdx/Make.defs index fab2ae77fc9..8d6741875db 100644 --- a/arch/avr/src/avrdx/Make.defs +++ b/arch/avr/src/avrdx/Make.defs @@ -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 diff --git a/arch/avr/src/avrdx/avrdx.h b/arch/avr/src/avrdx/avrdx.h index c8933d16438..84efae45da9 100644 --- a/arch/avr/src/avrdx/avrdx.h +++ b/arch/avr/src/avrdx/avrdx.h @@ -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) } diff --git a/arch/avr/src/avrdx/avrdx_gpio.h b/arch/avr/src/avrdx/avrdx_gpio.h index e53a7df31dc..27ed5bc67dc 100644 --- a/arch/avr/src/avrdx/avrdx_gpio.h +++ b/arch/avr/src/avrdx/avrdx_gpio.h @@ -28,6 +28,7 @@ ****************************************************************************/ #include +#include /**************************************************************************** * 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 } diff --git a/arch/avr/src/avrdx/avrdx_gpio_isr_mux.c b/arch/avr/src/avrdx/avrdx_gpio_isr_mux.c new file mode 100644 index 00000000000..f47ac0bdacc --- /dev/null +++ b/arch/avr/src/avrdx/avrdx_gpio_isr_mux.c @@ -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 +#include +#include +#include +#include +#include + +#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 +} diff --git a/arch/avr/src/avrdx/avrdx_init.c b/arch/avr/src/avrdx/avrdx_init.c new file mode 100644 index 00000000000..f5e0529e7a9 --- /dev/null +++ b/arch/avr/src/avrdx/avrdx_init.c @@ -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 +#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 +} diff --git a/arch/avr/src/avrdx/avrdx_peripherals.c b/arch/avr/src/avrdx/avrdx_peripherals.c index 62c86ef4c25..92639a73403 100644 --- a/arch/avr/src/avrdx/avrdx_peripherals.c +++ b/arch/avr/src/avrdx/avrdx_peripherals.c @@ -28,6 +28,7 @@ #include "avrdx.h" #include "avrdx_gpio.h" #include +#include /**************************************************************************** * 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 ****************************************************************************/ diff --git a/arch/avr/src/avrdx/iodefs/avr128da28.h b/arch/avr/src/avrdx/iodefs/avr128da28.h index e378425d078..2e63aa0abec 100644 --- a/arch/avr/src/avrdx/iodefs/avr128da28.h +++ b/arch/avr/src/avrdx/iodefs/avr128da28.h @@ -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 | \ diff --git a/arch/avr/src/common/avr_initialize.c b/arch/avr/src/common/avr_initialize.c index b775603d7dd..4a6f62b5e77 100644 --- a/arch/avr/src/common/avr_initialize.c +++ b/arch/avr/src/common/avr_initialize.c @@ -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