/* libhw_cr/rp2040_dma.c - Utilities for sharing the DMA IRQs * * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. * SPDX-License-Identifier: BSD-3-Clause * * Copyright (C) 2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #include /* for irq_set_exclusive_handler() */ #include #include "rp2040_dma.h" /* static_assert("rp2040_dma.h" == ); */ static_assert((uint)DMAIRQ_0 == (uint)DMA_IRQ_0); static_assert((uint)DMAIRQ_1 == (uint)DMA_IRQ_1); /* Borrowed from *********************************************/ dma_channel_hw_t *dma_channel_hw_addr(uint channel) { assert(channel < NUM_DMA_CHANNELS); return &dma_hw->ch[channel]; } /* Our own code ***************************************************************/ typedef uint8_t addr_flag_t; #define ADDR_FLAG_UNMAPPED ((addr_flag_t)(1<<0)) #define ADDR_FLAG_UNSAFE ((addr_flag_t)(1<<1)) #define ADDR_FLAG_RD_OK ((addr_flag_t)(1<<2)) #define ADDR_FLAG_WR_OK ((addr_flag_t)(1<<3)) #define ADDR_FLAG_NEEDS_DREQ ((addr_flag_t)(1<<4)) static addr_flag_t dma_classify_addr(volatile const void *_addr) { uintptr_t addr = (uintptr_t)_addr; switch (addr >> 28) { case 0x0: /* ROM */ if (addr < 0x4000) return ADDR_FLAG_RD_OK; return ADDR_FLAG_UNMAPPED; case 0x1: /* XIP */ switch ((addr >> 24)&0xf) { case 0x0: case 0x1: case 0x2: case 0x3: /* not safe for DMA */ return ADDR_FLAG_UNSAFE; case 0x4: /* CTRL registers */ if (addr < 0x14000020) return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; return ADDR_FLAG_UNMAPPED; case 0x5: /* SRAM */ if (addr < 0x15004000) return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; return ADDR_FLAG_UNMAPPED; case 0x8: /* SSI registers */ if (addr < 0x18000064) return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; if (0x180000f0 <= addr && addr < 0x180000fc) return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; return ADDR_FLAG_UNMAPPED; } return ADDR_FLAG_UNMAPPED; case 0x2: /* SRAM */ if ((addr & 0xfeffffff) < 0x20040000) /* banks 0-3 striped/unstriped depending on bit */ return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; if (addr < 0x20042000) /* banks 4-5 */ return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; return ADDR_FLAG_UNMAPPED; case 0x4: /* APB Peripherals */ /* TODO */ return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; case 0x5: /* AHB-Lite Peripherals */ /* TODO */ return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; case 0xd: /* IOPORT Registers */ /* TODO */ return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; case 0xe: /* Cortex-M0+ internal registers */ /* TODO */ return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; } return ADDR_FLAG_UNMAPPED; } bool dma_is_unsafe(volatile const void *addr) { return dma_classify_addr(addr) & ADDR_FLAG_UNSAFE; } #ifndef NDEBUG void dma_assert_addrs(volatile void *dst, volatile const void *src) { addr_flag_t dst_flags = dma_classify_addr(dst); addr_flag_t src_flags = dma_classify_addr(src); bool bad = false; if (!(dst_flags & ADDR_FLAG_WR_OK)) { log_n_errorln(ASSERT, "dma_assert_addrs(", (ptr, dst), ", ", (ptr, src), "): invalid destination"); bad = true; } if (!(src_flags & ADDR_FLAG_RD_OK)) { log_n_errorln(ASSERT, "dma_assert_addrs(", (ptr, dst), ", ", (ptr, src), "): invalid source"); bad = true; } if (!bad && (dst_flags & ADDR_FLAG_NEEDS_DREQ && src_flags & ADDR_FLAG_NEEDS_DREQ) ) { log_n_errorln(ASSERT, "dma_assert_addrs(", (ptr, dst), ", ", (ptr, src), "): source and destination both required DREQs"); bad = true; } if (bad) __lm_abort(); } #endif struct dmairq_handler_entry { dmairq_handler_t fn; void *arg; }; struct dmairq_handler_entry dmairq_handlers[NUM_DMA_CHANNELS] = {}; bool dmairq_initialized[NUM_DMA_IRQS] = {}; static void dmairq_handler(void) { enum dmairq irq = __get_current_exception() - VTABLE_FIRST_IRQ; size_t irq_idx = irq - DMAIRQ_0; assert(irq_idx < NUM_DMA_IRQS); uint32_t regval = dma_hw->irq_ctrl[irq_idx].ints; dma_hw->intr = regval; /* acknowledge irq */ for (uint channel = 0; channel < NUM_DMA_CHANNELS; channel++) { if (regval & 1u<fn) handler->fn(handler->arg, irq, channel); } } } void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg) { assert(irq == DMAIRQ_0 || irq == DMAIRQ_1); assert(channel < NUM_DMA_CHANNELS); assert(fn); assert(dmairq_handlers[channel].fn == NULL); dmairq_handlers[channel].fn = fn; dmairq_handlers[channel].arg = arg; size_t irq_idx = irq - DMAIRQ_0; hw_set_bits(&dma_hw->irq_ctrl[irq_idx].inte, 1u<