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<