From 3b4e543f4e9db7913a71c77785625cf28c7b2c30 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 1 Apr 2023 22:08:48 +0200 Subject: [PATCH] rp2040: use DMA for send-only SPI transfers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This improves slightly. It also is some groundwork for better DMA support in TinyGo in the future. I'm not entirely sure why it improves performance (in theory the old code should already saturate the SPI bus) but it does, so 🤷 --- src/machine/machine_rp2040.go | 21 +++++++++++++ src/machine/machine_rp2040_spi.go | 52 ++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2040.go index ca4ea7f2..e76a85e1 100644 --- a/src/machine/machine_rp2040.go +++ b/src/machine/machine_rp2040.go @@ -4,6 +4,7 @@ package machine import ( "device/rp" + "runtime/volatile" "unsafe" ) @@ -114,3 +115,23 @@ func ChipVersion() uint8 { version := (chipID & SYSINFO_CHIP_ID_REVISION_BITS) >> SYSINFO_CHIP_ID_REVISION_LSB return uint8(version) } + +// Single DMA channel. See rp.DMA_Type. +type dmaChannel struct { + READ_ADDR volatile.Register32 + WRITE_ADDR volatile.Register32 + TRANS_COUNT volatile.Register32 + CTRL_TRIG volatile.Register32 + _ [12]volatile.Register32 // aliases +} + +// Static assignment of DMA channels to peripherals. +// Allocating them statically is good enough for now. If lots of peripherals use +// DMA, these might need to be assigned at runtime. +const ( + spi0DMAChannel = iota + spi1DMAChannel +) + +// DMA channels usable on the RP2040. +var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA)) diff --git a/src/machine/machine_rp2040_spi.go b/src/machine/machine_rp2040_spi.go index 5dc31e8e..07a3745e 100644 --- a/src/machine/machine_rp2040_spi.go +++ b/src/machine/machine_rp2040_spi.go @@ -5,6 +5,7 @@ package machine import ( "device/rp" "errors" + "unsafe" ) // SPI on the RP2040 @@ -288,14 +289,51 @@ func (spi SPI) isBusy() bool { // tx writes buffer to SPI ignoring Rx. func (spi SPI) tx(tx []byte) error { - // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX - // is full, PL022 inhibits RX pushes, and sets a sticky flag on - // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. - for i := range tx { - for !spi.isWritable() { - } - spi.Bus.SSPDR.Set(uint32(tx[i])) + if len(tx) == 0 { + // We don't have to do anything. + // This avoids a panic in &tx[0] when len(tx) == 0. + return nil } + + // Pick the DMA channel reserved for this SPI peripheral. + var ch *dmaChannel + var dreq uint32 + if spi.Bus == rp.SPI0 { + ch = &dmaChannels[spi0DMAChannel] + dreq = 16 // DREQ_SPI0_TX + } else { // SPI1 + ch = &dmaChannels[spi1DMAChannel] + dreq = 18 // DREQ_SPI1_TX + } + + // Configure the DMA peripheral as follows: + // - set read address, write address, and number of transfer units (bytes) + // - increment read address (in memory), don't increment write address (SSPDR) + // - set data size to single bytes + // - set the DREQ so that the DMA will fill the SPI FIFO as needed + // - start the transfer + ch.READ_ADDR.Set(uint32(uintptr(unsafe.Pointer(&tx[0])))) + ch.WRITE_ADDR.Set(uint32(uintptr(unsafe.Pointer(&spi.Bus.SSPDR)))) + ch.TRANS_COUNT.Set(uint32(len(tx))) + ch.CTRL_TRIG.Set(rp.DMA_CH0_CTRL_TRIG_INCR_READ | + rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE<