diff options
Diffstat (limited to 'libhw')
-rw-r--r-- | libhw/CMakeLists.txt | 5 | ||||
-rw-r--r-- | libhw/rp2040_gpioirq.c | 75 | ||||
-rw-r--r-- | libhw/rp2040_gpioirq.h | 33 | ||||
-rw-r--r-- | libhw/w5500.c | 25 |
4 files changed, 117 insertions, 21 deletions
diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt index d1767da..bd92e04 100644 --- a/libhw/CMakeLists.txt +++ b/libhw/CMakeLists.txt @@ -1,6 +1,6 @@ # libhw/CMakeLists.txt - Device drivers # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libhw INTERFACE) @@ -14,8 +14,9 @@ if (PICO_PLATFORM STREQUAL "rp2040") libcr_ipc ) target_sources(libhw INTERFACE - rp2040_hwtimer.c + rp2040_gpioirq.c rp2040_hwspi.c + rp2040_hwtimer.c w5500.c ) target_link_libraries(libhw INTERFACE diff --git a/libhw/rp2040_gpioirq.c b/libhw/rp2040_gpioirq.c new file mode 100644 index 0000000..2f0ceac --- /dev/null +++ b/libhw/rp2040_gpioirq.c @@ -0,0 +1,75 @@ +/* libhw/rp2040_gpioirq.c - Utilities for sharing the GPIO IRQ (IO_IRQ_BANK0) + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <hardware/structs/io_bank0.h> /* for io_bank0_hw */ +#include <hardware/irq.h> /* for irq_set_exclusive_handler() */ + +#include <libmisc/macro.h> + +#include "rp2040_gpioirq.h" + +struct gpioirq_handler_entry { + gpioirq_handler_t fn; + void *arg; +}; +struct gpioirq_handler_entry gpioirq_handlers[NUM_BANK0_GPIOS][4] = {0}; + +int gpioirq_core = -1; + +static void gpioirq_handler(void) { + uint core = get_core_num(); + io_bank0_irq_ctrl_hw_t *irq_ctrl_base; + switch (core) { + case 0: irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; break; + case 1: irq_ctrl_base = &io_bank0_hw->proc1_irq_ctrl; break; + default: assert_notreached("invalid core number"); + } + for (uint regnum = 0; regnum < LM_ARRAY_LEN(irq_ctrl_base->ints); regnum++) { + uint32_t regval = irq_ctrl_base->ints[regnum]; + for (uint bit = 0; bit < 32 && (regnum*8)+(bit/4) < NUM_BANK0_GPIOS; bit++) { + if (regval & 1u<<bit) { + uint gpio = (regnum*8)+(bit/4); + uint event_idx = bit%4; + struct gpioirq_handler_entry *handler = &gpioirq_handlers[gpio][event_idx]; + if (handler->fn) + handler->fn(handler->arg, gpio, 1u<<event_idx); + } + } + /* acknowledge irq */ + io_bank0_hw->intr[regnum] = regval; + } +} + +void gpioirq_set_and_enable_exclusive_handler(uint gpio, enum gpio_irq_level event, gpioirq_handler_t fn, void *arg) { + assert(gpio < NUM_BANK0_GPIOS); + assert(event == GPIO_IRQ_LEVEL_LOW || + event == GPIO_IRQ_LEVEL_HIGH || + event == GPIO_IRQ_EDGE_FALL || + event == GPIO_IRQ_EDGE_RISE); + assert(fn); + + uint event_idx = LM_FLOORLOG2(event); + assert(gpioirq_handlers[gpio][event_idx].fn == NULL); + + uint core = get_core_num(); + assert(gpioirq_core == -1 || gpioirq_core == (int)core); + + io_bank0_irq_ctrl_hw_t *irq_ctrl_base; + switch (core) { + case 0: irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; break; + case 1: irq_ctrl_base = &io_bank0_hw->proc1_irq_ctrl; break; + default: assert_notreached("invalid core number"); + } + + gpioirq_handlers[gpio][event_idx].fn = fn; + gpioirq_handlers[gpio][event_idx].arg = arg; + hw_set_bits(&irq_ctrl_base->inte[gpio/8], 1u<<((4*(gpio%8))+event_idx)); + if (gpioirq_core == -1) { + irq_set_exclusive_handler(IO_IRQ_BANK0, gpioirq_handler); + irq_set_enabled(IO_IRQ_BANK0, true); + gpioirq_core = core; + } +} diff --git a/libhw/rp2040_gpioirq.h b/libhw/rp2040_gpioirq.h new file mode 100644 index 0000000..06041c9 --- /dev/null +++ b/libhw/rp2040_gpioirq.h @@ -0,0 +1,33 @@ +/* libhw/rp2040_gpioirq.h - Utilities for sharing the GPIO IRQ (IO_IRQ_BANK0) + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_RP2040_GPIOIRQ_H_ +#define _LIBHW_RP2040_GPIOIRQ_H_ + +#include <hardware/gpio.h> /* for enum gpio_irq_level */ + +typedef void (*gpioirq_handler_t)(void *arg, uint gpio, enum gpio_irq_level event); + +/** + * Register `fn(arg, ...)` to be called when `event` fires on GPIO pin + * `gpio`. + * + * If multiple events happen close together, the handlers will not + * necessarily be called in-order. + * + * Your handler does not need to acknowledge the IRQ; that will be + * done for you after your handler is called. + * + * It is illegal to call gpioirq_set_and_enable_exclusive_handler() + * on multiple cores. + * + * This is better than the Pico-SDK <hardware/gpio.h> IRQ functions + * because their functions call the handlers for every event, and it + * is up to you to de-mux them in your handler. + */ +void gpioirq_set_and_enable_exclusive_handler(uint gpio, enum gpio_irq_level event, gpioirq_handler_t fn, void *arg); + +#endif /* _LIBHW_RP2040_GPIOIRQ_H_ */ diff --git a/libhw/w5500.c b/libhw/w5500.c index 64b373b..b4ad86b 100644 --- a/libhw/w5500.c +++ b/libhw/w5500.c @@ -72,6 +72,7 @@ /* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being * pico-sdk-specific. */ #include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ +#include "rp2040_gpioirq.h" #include <libcr/coroutine.h> /* for cr_yield() */ @@ -95,9 +96,6 @@ #ifndef CONFIG_W5500_LOCAL_PORT_MAX #error config.h must define CONFIG_W5500_LOCAL_PORT_MAX #endif -#ifndef CONFIG_W5500_NUM - #error config.h must define CONFIG_W5500_NUM -#endif #ifndef CONFIG_W5500_DEBUG #error config.h must define CONFIG_W5500_DEBUG #endif @@ -280,13 +278,10 @@ static inline void w5500_socket_close(struct _w5500_socket *socket) { /* init() *********************************************************************/ -static struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0}; - -static void w5500_intrhandler(uint gpio, uint32_t LM_UNUSED(event_mask)) { - debugf("w5500_intrhandler(): interrupt on pin %u", gpio); - for (size_t i = 0; i < LM_ARRAY_LEN(w5500_chips); i++) - if (w5500_chips[i] && w5500_chips[i]->pin_intr == gpio) - cr_sema_signal_from_intrhandler(&w5500_chips[i]->intr); +static void w5500_intrhandler(void *_chip, uint LM_UNUSED(gpio), enum gpio_irq_level LM_UNUSED(event)) { + struct w5500 *chip = _chip; + debugf("w5500_intrhandler()"); + cr_sema_signal_from_intrhandler(&chip->intr); } void _w5500_init(struct w5500 *chip, @@ -318,19 +313,11 @@ void _w5500_init(struct w5500 *chip, } /* Initialize the hardware. */ - gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, w5500_intrhandler); + gpioirq_set_and_enable_exclusive_handler(pin_intr, GPIO_IRQ_EDGE_FALL, w5500_intrhandler, chip); gpio_set_dir(chip->pin_reset, GPIO_OUT); w5500_hard_reset(chip); /* Finally, wire in the interrupt handler. */ - bool saved = cr_save_and_disable_interrupts(); - for (size_t i = 0; i < LM_ARRAY_LEN(w5500_chips); i++) { - if (w5500_chips[i] == NULL) { - w5500_chips[i] = chip; - break; - } - } - cr_restore_interrupts(saved); coroutine_add("w5500_irq", w5500_irq_cr, chip); } |