summaryrefslogtreecommitdiff
path: root/libhw
diff options
context:
space:
mode:
Diffstat (limited to 'libhw')
-rw-r--r--libhw/CMakeLists.txt5
-rw-r--r--libhw/rp2040_gpioirq.c75
-rw-r--r--libhw/rp2040_gpioirq.h33
-rw-r--r--libhw/w5500.c25
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);
}