From a9e6e44a2a3cce8082bcca329689da56d03d15e0 Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Thu, 1 Oct 2026 12:53:34 -0400 Subject: [PATCH 1/2] feat(dmac): add shared hardware CRC channel support Configure CRC16 or CRC32 for an allocated DMA channel, preserve interrupt state, and release ownership after completion or abort. --- cores/arduino/DmacCrc.cpp | 90 +++++++++++++++++++++++++++++++++++++++ cores/arduino/DmacCrc.h | 13 ++++++ 2 files changed, 103 insertions(+) create mode 100644 cores/arduino/DmacCrc.cpp create mode 100644 cores/arduino/DmacCrc.h diff --git a/cores/arduino/DmacCrc.cpp b/cores/arduino/DmacCrc.cpp new file mode 100644 index 000000000..020054cac --- /dev/null +++ b/cores/arduino/DmacCrc.cpp @@ -0,0 +1,90 @@ +#include "DmacCrc.h" +#include + +namespace { +uint8_t owner = 0xff; +class CriticalSection { + uint32_t mask; +public: + CriticalSection() : mask(__get_PRIMASK()) { __disable_irq(); } + ~CriticalSection() { __set_PRIMASK(mask); } +}; +#if defined(__SAME53__) || defined(__SAME54__) +#define CRC_CONTROL DMAC_REGS->DMAC_CRCCTRL +#define CRC_CHECKSUM DMAC_REGS->DMAC_CRCCHKSUM +#define CRC_STATUS DMAC_REGS->DMAC_CRCSTATUS +#define CRC_BUSY DMAC_CRCSTATUS_CRCBUSY_Msk +#define DMA_CONTROL DMAC_REGS->DMAC_CTRL +#else +#define CRC_CONTROL DMAC->CRCCTRL.reg +#define CRC_CHECKSUM DMAC->CRCCHKSUM.reg +#define CRC_STATUS DMAC->CRCSTATUS.reg +#define CRC_BUSY DMAC_CRCSTATUS_CRCBUSY +#define DMA_CONTROL DMAC->CTRL.reg +#endif + +bool channelEnabled(uint8_t channel) { +#if defined(__SAME53__) || defined(__SAME54__) + return (DMAC_REGS->CHANNEL[channel].DMAC_CHCTRLA & DMAC_CHCTRLA_ENABLE_Msk) != 0; +#elif defined(__SAMD51__) + return (DMAC->Channel[channel].CHCTRLA.reg & DMAC_CHCTRLA_ENABLE) != 0; +#else + const uint8_t selected = DMAC->CHID.reg; + DMAC->CHID.reg = channel; + const bool enabled = (DMAC->CHCTRLA.reg & DMAC_CHCTRLA_ENABLE) != 0; + DMAC->CHID.reg = selected; + return enabled; +#endif +} +void disableCrc() { +#if !defined(__SAMD51__) && !defined(__SAME53__) && !defined(__SAME54__) + DMA_CONTROL &= ~DMAC_CTRL_CRCENABLE; +#endif + CRC_CONTROL = 0; +} +} + +DmacCrc::Status DmacCrc::begin(uint8_t channel, Polynomial polynomial, uint32_t seed) { + if (channel >= DMAC_CH_NUM || + (polynomial != Polynomial::Crc16 && polynomial != Polynomial::Crc32)) + return Status::InvalidArgument; + CriticalSection critical; + if (owner != 0xff || CRC_CONTROL != 0 || (CRC_STATUS & CRC_BUSY) != 0) + return Status::Busy; +#if defined(__SAME53__) || defined(__SAME54__) + if ((DMA_CONTROL & DMAC_CTRL_DMAENABLE_Msk) == 0) +#else + if ((DMA_CONTROL & DMAC_CTRL_DMAENABLE) == 0) +#endif + return Status::NotConfigured; +#if !defined(__SAMD51__) && !defined(__SAME53__) && !defined(__SAME54__) + if ((DMA_CONTROL & DMAC_CTRL_CRCENABLE) != 0) + return Status::Busy; +#endif + if (channelEnabled(channel)) + return Status::Busy; + disableCrc(); + CRC_CHECKSUM = polynomial == Polynomial::Crc16 ? (seed & 0xffffu) : seed; + CRC_CONTROL = DMAC_CRCCTRL_CRCPOLY(polynomial == Polynomial::Crc32 ? 1u : 0u) | + DMAC_CRCCTRL_CRCSRC(0x20u + channel); +#if !defined(__SAMD51__) && !defined(__SAME53__) && !defined(__SAME54__) + DMA_CONTROL |= DMAC_CTRL_CRCENABLE; +#endif + owner = channel; + return Status::Ok; +} + +DmacCrc::Status DmacCrc::finish(uint8_t channel, bool transferSucceeded, uint32_t& checksum) { + CriticalSection critical; + if (owner == 0xff || owner != channel) + return Status::NotOwner; + if (channelEnabled(channel) || (CRC_STATUS & CRC_BUSY) != 0) + return Status::Busy; + // Read while CRC is configured and idle: CRC32 readout then includes the + // hardware's bit reversal and complement (D21 20.6.3, D5x/E5x 22.6.3.8). + if (transferSucceeded) + checksum = CRC_CHECKSUM; + disableCrc(); + owner = 0xff; + return transferSucceeded ? Status::Ok : Status::TransferFailed; +} diff --git a/cores/arduino/DmacCrc.h b/cores/arduino/DmacCrc.h new file mode 100644 index 000000000..c422856f1 --- /dev/null +++ b/cores/arduino/DmacCrc.h @@ -0,0 +1,13 @@ +#pragma once +#include +// Observes an existing DMA channel; its owner retains descriptors and callbacks. +class DmacCrc { +public: + enum class Polynomial : uint8_t { Crc16, Crc32 }; + enum class Status : uint8_t { Ok, Busy, InvalidArgument, NotConfigured, NotOwner, TransferFailed }; + // Call after allocation/configuration, before enabling the DMA channel. + static Status begin(uint8_t channel, Polynomial polynomial, uint32_t seed); + // Call after completion or abort. Busy retains ownership until the channel stops. + // Only the successful DMA completion path may pass transferSucceeded=true. + static Status finish(uint8_t channel, bool transferSucceeded, uint32_t& checksum); +}; From 5d6fd59d92e40aba6891a1d7d9b71135c51d47cc Mon Sep 17 00:00:00 2001 From: Cal Abel Date: Sun, 4 Oct 2026 20:55:05 -0400 Subject: [PATCH 2/2] feat(dmac): support hardware CRC for interrupt-driven bytes --- cores/arduino/DmacCrc.cpp | 65 +++++++++++++++++++++++++++++++++++++-- cores/arduino/DmacCrc.h | 5 +++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/cores/arduino/DmacCrc.cpp b/cores/arduino/DmacCrc.cpp index 020054cac..1af7cdaf4 100644 --- a/cores/arduino/DmacCrc.cpp +++ b/cores/arduino/DmacCrc.cpp @@ -2,6 +2,8 @@ #include namespace { +enum class Source : uint8_t { None, Dma, Input }; +Source source = Source::None; uint8_t owner = 0xff; class CriticalSection { uint32_t mask; @@ -13,12 +15,14 @@ class CriticalSection { #define CRC_CONTROL DMAC_REGS->DMAC_CRCCTRL #define CRC_CHECKSUM DMAC_REGS->DMAC_CRCCHKSUM #define CRC_STATUS DMAC_REGS->DMAC_CRCSTATUS +#define CRC_INPUT DMAC_REGS->DMAC_CRCDATAIN #define CRC_BUSY DMAC_CRCSTATUS_CRCBUSY_Msk #define DMA_CONTROL DMAC_REGS->DMAC_CTRL #else #define CRC_CONTROL DMAC->CRCCTRL.reg #define CRC_CHECKSUM DMAC->CRCCHKSUM.reg #define CRC_STATUS DMAC->CRCSTATUS.reg +#define CRC_INPUT DMAC->CRCDATAIN.reg #define CRC_BUSY DMAC_CRCSTATUS_CRCBUSY #define DMA_CONTROL DMAC->CTRL.reg #endif @@ -49,7 +53,7 @@ DmacCrc::Status DmacCrc::begin(uint8_t channel, Polynomial polynomial, uint32_t (polynomial != Polynomial::Crc16 && polynomial != Polynomial::Crc32)) return Status::InvalidArgument; CriticalSection critical; - if (owner != 0xff || CRC_CONTROL != 0 || (CRC_STATUS & CRC_BUSY) != 0) + if (source != Source::None || CRC_CONTROL != 0 || (CRC_STATUS & CRC_BUSY) != 0) return Status::Busy; #if defined(__SAME53__) || defined(__SAME54__) if ((DMA_CONTROL & DMAC_CTRL_DMAENABLE_Msk) == 0) @@ -71,12 +75,13 @@ DmacCrc::Status DmacCrc::begin(uint8_t channel, Polynomial polynomial, uint32_t DMA_CONTROL |= DMAC_CTRL_CRCENABLE; #endif owner = channel; + source = Source::Dma; return Status::Ok; } DmacCrc::Status DmacCrc::finish(uint8_t channel, bool transferSucceeded, uint32_t& checksum) { CriticalSection critical; - if (owner == 0xff || owner != channel) + if (source != Source::Dma || owner != channel) return Status::NotOwner; if (channelEnabled(channel) || (CRC_STATUS & CRC_BUSY) != 0) return Status::Busy; @@ -86,5 +91,61 @@ DmacCrc::Status DmacCrc::finish(uint8_t channel, bool transferSucceeded, uint32_ checksum = CRC_CHECKSUM; disableCrc(); owner = 0xff; + source = Source::None; + return transferSucceeded ? Status::Ok : Status::TransferFailed; +} + +DmacCrc::Status DmacCrc::beginInput(uint8_t client, Polynomial polynomial, uint32_t seed) { + if (polynomial != Polynomial::Crc16 && polynomial != Polynomial::Crc32) + return Status::InvalidArgument; + CriticalSection critical; +#if defined(__SAME53__) || defined(__SAME54__) + MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_DMAC_Msk; +#elif defined(__SAMD51__) + MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC; +#else + PM->AHBMASK.reg |= PM_AHBMASK_DMAC; + PM->APBBMASK.reg |= PM_APBBMASK_DMAC; +#endif + if (source != Source::None || CRC_CONTROL != 0 || (CRC_STATUS & CRC_BUSY) != 0) + return Status::Busy; +#if !defined(__SAMD51__) && !defined(__SAME53__) && !defined(__SAME54__) + if ((DMA_CONTROL & DMAC_CTRL_CRCENABLE) != 0) + return Status::Busy; +#endif + disableCrc(); + CRC_CHECKSUM = polynomial == Polynomial::Crc16 ? (seed & 0xffffu) : seed; + CRC_CONTROL = DMAC_CRCCTRL_CRCPOLY(polynomial == Polynomial::Crc32 ? 1u : 0u) | + DMAC_CRCCTRL_CRCSRC(1u); +#if !defined(__SAMD51__) && !defined(__SAME53__) && !defined(__SAME54__) + DMA_CONTROL |= DMAC_CTRL_CRCENABLE; +#endif + owner = client; + source = Source::Input; + return Status::Ok; +} + +DmacCrc::Status DmacCrc::writeInput(uint8_t client, uint8_t byte) { + CriticalSection critical; + if (source != Source::Input || owner != client) + return Status::NotOwner; + // I/O CRCBUSY is sticky, cleared by W1C before another byte (D21 20.8.5, + // D5x/E5x 22.8.5). It must not be polled for hardware self-clear. + CRC_STATUS = CRC_BUSY; + CRC_INPUT = byte; + return Status::Ok; +} + +DmacCrc::Status DmacCrc::finishInput(uint8_t client, bool transferSucceeded, + uint32_t& checksum) { + CriticalSection critical; + if (source != Source::Input || owner != client) + return Status::NotOwner; + CRC_STATUS = CRC_BUSY; + if (transferSucceeded) + checksum = CRC_CHECKSUM; + disableCrc(); + owner = 0xff; + source = Source::None; return transferSucceeded ? Status::Ok : Status::TransferFailed; } diff --git a/cores/arduino/DmacCrc.h b/cores/arduino/DmacCrc.h index c422856f1..c41d4bcda 100644 --- a/cores/arduino/DmacCrc.h +++ b/cores/arduino/DmacCrc.h @@ -10,4 +10,9 @@ class DmacCrc { // Call after completion or abort. Busy retains ownership until the channel stops. // Only the successful DMA completion path may pass transferSucceeded=true. static Status finish(uint8_t channel, bool transferSucceeded, uint32_t& checksum); + // Uses the hardware CRC input register when bytes are transferred without DMA. + // Enables the DMAC clock without allocating a DMA channel; owner retains its token. + static Status beginInput(uint8_t owner, Polynomial polynomial, uint32_t seed); + static Status writeInput(uint8_t owner, uint8_t byte); + static Status finishInput(uint8_t owner, bool transferSucceeded, uint32_t& checksum); };