From 24d718fa056ef11c43870c5872a56e2b0f9e21c4 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 4 Nov 2023 16:14:47 +0100 Subject: [PATCH 1/3] machine: add dummy async SPI methods This prepares all devices for actual async SPI support. --- src/machine/spi-dummy-async.go | 29 +++++++++++++++++++++++++++++ src/machine/spi.go | 3 +++ 2 files changed, 32 insertions(+) create mode 100644 src/machine/spi-dummy-async.go diff --git a/src/machine/spi-dummy-async.go b/src/machine/spi-dummy-async.go new file mode 100644 index 0000000000..c6bc171f6a --- /dev/null +++ b/src/machine/spi-dummy-async.go @@ -0,0 +1,29 @@ +//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) + +package machine + +// This is a non-async implementation of the async SPI calls. +// It is useful for devices that don't support DMA on SPI, or for which it +// hasn't been implemented yet. + +// IsAsync returns whether the SPI supports async operation (usually DMA). +// +// This SPI does not support async operations. +func (s SPI) IsAsync() bool { + return false +} + +// Start a transfer in the background. +// +// Because this SPI implementation doesn't support async operation, it is an +// alias for Tx. +func (s SPI) StartTx(tx, rx []byte) error { + return s.Tx(tx, rx) +} + +// Wait until all active transactions (started by StartTx) have finished. +// +// This is a no-op on this SPI implementation. +func (s SPI) Wait() error { + return nil +} diff --git a/src/machine/spi.go b/src/machine/spi.go index a6fd866d1d..271de7a9ac 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -26,4 +26,7 @@ var _ interface { // 2 Configure(config SPIConfig) error Tx(w, r []byte) error Transfer(w byte) (byte, error) + IsAsync() bool + StartTx(tx, rx []byte) error + Wait() error } = (*SPI)(nil) From cb95259adfd792e63feee6f15647a44d23508d72 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 4 Nov 2023 16:18:45 +0100 Subject: [PATCH 2/3] rp2040: add SPI DMA support --- src/machine/machine_rp2040_spi.go | 43 ++++++++++++++++++++++++++++++- src/machine/spi-dummy-async.go | 2 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/machine/machine_rp2040_spi.go b/src/machine/machine_rp2040_spi.go index cb60fdbcb0..9b460049ed 100644 --- a/src/machine/machine_rp2040_spi.go +++ b/src/machine/machine_rp2040_spi.go @@ -289,7 +289,26 @@ func (spi SPI) isBusy() bool { // tx writes buffer to SPI ignoring Rx. func (spi SPI) tx(tx []byte) error { - if len(tx) == 0 { + err := spi.StartTx(tx, nil) + if err != nil { + return err + } + return spi.Wait() +} + +// IsAsync returns whether the SPI supports async operation (usually DMA). +// +// It returns true on the rp2040. +func (spi SPI) IsAsync() bool { + return true +} + +// Start a transfer in the background. +// +// After this, another StartTx() or Wait() must be called. The provided byte +// slices (tx and rx) may only be accessed again after Wait() was called. +func (spi SPI) StartTx(tx, rx []byte) error { + if len(tx) == 0 && len(rx) == 0 { // We don't have to do anything. // This avoids a panic in &tx[0] when len(tx) == 0. return nil @@ -306,6 +325,15 @@ func (spi SPI) tx(tx []byte) error { dreq = 18 // DREQ_SPI1_TX } + // Wait for the previous transmission to complete. + for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 { + } + + if len(rx) != 0 { + // Fallback. We don't support receiving data using DMA yet. + return spi.Tx(tx, rx) + } + // 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) @@ -319,6 +347,19 @@ func (spi SPI) tx(tx []byte) error { rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE< Date: Sun, 5 Nov 2023 19:21:55 +0100 Subject: [PATCH 3/3] samd51: add support for async SPI using DMA --- src/machine/machine_atsamd51.go | 145 +++++++++++++++++++++++++++++ src/machine/machine_atsamd51g19.go | 21 +++++ src/machine/machine_atsamd51j19.go | 21 +++++ src/machine/machine_atsamd51j20.go | 21 +++++ src/machine/machine_atsamd51p19.go | 25 +++++ src/machine/machine_atsamd51p20.go | 25 +++++ src/machine/machine_atsame51j19.go | 21 +++++ src/machine/machine_atsame54p20.go | 25 +++++ src/machine/spi-dummy-async.go | 2 +- 9 files changed, 305 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 7118792a73..52f7a712bd 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -683,6 +683,56 @@ func (p Pin) getPinGrouping() (uint8, uint8) { return group, pin_in_group } +// Static DMA channel allocation. +// If there are a lot of DMA using peripherals, we might need to switch to +// dynamic allocation instead. +const ( + dmaChannelSERCOM0 = iota + dmaChannelSERCOM1 + dmaChannelSERCOM2 + dmaChannelSERCOM3 + dmaChannelSERCOM4 + dmaChannelSERCOM5 + dmaChannelSERCOM6 + dmaChannelSERCOM7 + dmaNumChannels +) + +// DMA descriptor structure. This structure is defined by the hardware, and is +// described by "22.9 Register Summary - SRAM" in the datasheet. +type dmaDescriptor struct { + btctrl uint16 + btcnt uint16 + srcaddr unsafe.Pointer + dstaddr unsafe.Pointer + descaddr unsafe.Pointer +} + +//go:align 16 +var dmaDescriptorSection [dmaNumChannels]dmaDescriptor + +//go:align 16 +var dmaDescriptorWritebackSection [dmaNumChannels]dmaDescriptor + +// Enable and configure the DMAC peripheral if it hasn't been enabled already. +func enableDMAC() { + if !sam.DMAC.CTRL.HasBits(sam.DMAC_CTRL_DMAENABLE) { + // Init DMAC. + // First configure the clocks, then configure the DMA descriptors. Those + // descriptors must live in SRAM and must be aligned on a 16-byte + // boundary. + // Some examples: + // http://www.lucadavidian.com/2018/03/08/wifi-controlled-neo-pixels-strips/ + // https://svn.larosterna.com/oss/trunk/arduino/zerotimer/zerodma.cpp + sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_DMAC_) + sam.DMAC.BASEADDR.Set(uint32(uintptr(unsafe.Pointer(&dmaDescriptorSection)))) + sam.DMAC.WRBADDR.Set(uint32(uintptr(unsafe.Pointer(&dmaDescriptorWritebackSection)))) + + // Enable peripheral with all priorities. + sam.DMAC.CTRL.SetBits(sam.DMAC_CTRL_DMAENABLE | sam.DMAC_CTRL_LVLEN0 | sam.DMAC_CTRL_LVLEN1 | sam.DMAC_CTRL_LVLEN2 | sam.DMAC_CTRL_LVLEN3) + } +} + // InitADC initializes the ADC. func InitADC() { // ADC Bias Calibration @@ -1652,6 +1702,101 @@ func (spi SPI) txrx(tx, rx []byte) { rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } +// Channel to be used for SPI transfers. +// These channels are currently statically allocated. +func (spi SPI) dmaTxChannel() uint8 { + return dmaChannelSERCOM0 + spi.SERCOM +} + +// IsAsync returns whether the SPI supports async operation (usually DMA). +// +// It returns true on the SAM D5x chips. +func (spi SPI) IsAsync() bool { + return true +} + +// Start a transfer in the background. +// +// After this, another StartTx() or Wait() must be called. The provided byte +// slices (tx and rx) may only be accessed again after Wait() was called. +func (s SPI) StartTx(tx, rx []byte) error { + // Check whether we support doing this transfer using DMA. + if len(rx) != 0 { + return s.Tx(tx, rx) + } + if len(tx) == 0 { + return nil // nothing to send/receive + } + if len(tx) != int(uint16(len(tx))) { + // The transfer size is a 16-bit field. + // TODO: chain multiple transfers in some way when encountering these + // large buffer sizes. They're not currently implemented because + // transferring 64kB of data is less commonly done. + return s.Tx(tx, rx) + } + + // Enable DMAC (if not already enabled). + enableDMAC() + + // Wait until a possible previous transfer has been completed. + s.Wait() + + // Configure the DMA channel, if it hasn't been configured already. + dstaddr := unsafe.Pointer(&s.Bus.DATA.Reg) + if dmaDescriptorSection[s.dmaTxChannel()].dstaddr != dstaddr { + // Configure channel descriptor. + dmaDescriptorSection[s.dmaTxChannel()] = dmaDescriptor{ + btctrl: (1 << 0) | // VALID: Descriptor Valid + (0 << 3) | // BLOCKACT=NOACT: Block Action + (1 << 10) | // SRCINC: Source Address Increment Enable + (0 << 11) | // DSTINC: Destination Address Increment Enable + (1 << 12) | // STEPSEL=SRC: Step Selection + (0 << 13), // STEPSIZE=X1: Address Increment Step Size + dstaddr: dstaddr, + } + + // Reset channel. + sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.ClearBits(sam.DMAC_CHANNEL_CHCTRLA_ENABLE) + sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.SetBits(sam.DMAC_CHANNEL_CHCTRLA_SWRST) + + // Configure channel. + sam.DMAC.CHANNEL[s.dmaTxChannel()].CHPRILVL.Set(0) + sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.Set((sam.DMAC_CHANNEL_CHCTRLA_TRIGACT_BURST << sam.DMAC_CHANNEL_CHCTRLA_TRIGACT_Pos) | + (s.triggerSource() << sam.DMAC_CHANNEL_CHCTRLA_TRIGSRC_Pos) | + (sam.DMAC_CHANNEL_CHCTRLA_BURSTLEN_SINGLE << sam.DMAC_CHANNEL_CHCTRLA_BURSTLEN_Pos)) + } + + // For some reason, you have to provide the address just past the end of the + // array instead of the address of the array. + descriptor := &dmaDescriptorSection[s.dmaTxChannel()] + descriptor.srcaddr = unsafe.Pointer(uintptr(unsafe.Pointer(&tx[0])) + uintptr(len(tx))) + descriptor.btcnt = uint16(len(tx)) // beat count + + // Start the transfer. + sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.SetBits(sam.DMAC_CHANNEL_CHCTRLA_ENABLE) + + return nil +} + +// Wait until all active transactions (started by StartTx) have finished. The +// buffers provided in StartTx will be available after this method returns. +func (spi SPI) Wait() error { + // Wait until the previous SPI transfer completed. + // This is basically the same thing as in SPI.tx. + + // TODO: maybe block (and sleep) until the transfer has completed? + + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_TXC) { + } + + // read to clear RXC register + for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { + spi.Bus.DATA.Get() + } + + return nil +} + // The QSPI peripheral on ATSAMD51 is only available on the following pins const ( QSPI_SCK = PB10 diff --git a/src/machine/machine_atsamd51g19.go b/src/machine/machine_atsamd51g19.go index ade031bb04..64f8732651 100644 --- a/src/machine/machine_atsamd51g19.go +++ b/src/machine/machine_atsamd51g19.go @@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) { } } +// DMA trigger source +func (spi SPI) triggerSource() (tx uint32) { + // See TRIGSRC field of CHCTRLA register for description of these constants. + switch spi.Bus { + case sam.SERCOM0_SPIM: + return 0x05 + case sam.SERCOM1_SPIM: + return 0x07 + case sam.SERCOM2_SPIM: + return 0x09 + case sam.SERCOM3_SPIM: + return 0x0A + case sam.SERCOM4_SPIM: + return 0x0C + case sam.SERCOM5_SPIM: + return 0x0E + default: + return 0 // should be unreachable + } +} + // This chip has three TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) diff --git a/src/machine/machine_atsamd51j19.go b/src/machine/machine_atsamd51j19.go index b41c25c145..98e21e1e8b 100644 --- a/src/machine/machine_atsamd51j19.go +++ b/src/machine/machine_atsamd51j19.go @@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) { } } +// DMA trigger source +func (spi SPI) triggerSource() (tx uint32) { + // See TRIGSRC field of CHCTRLA register for description of these constants. + switch spi.Bus { + case sam.SERCOM0_SPIM: + return 0x05 + case sam.SERCOM1_SPIM: + return 0x07 + case sam.SERCOM2_SPIM: + return 0x09 + case sam.SERCOM3_SPIM: + return 0x0B + case sam.SERCOM4_SPIM: + return 0x0D + case sam.SERCOM5_SPIM: + return 0x0F + default: + return 0 // should be unreachable + } +} + // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) diff --git a/src/machine/machine_atsamd51j20.go b/src/machine/machine_atsamd51j20.go index 2c5391afe7..322bcc0de8 100644 --- a/src/machine/machine_atsamd51j20.go +++ b/src/machine/machine_atsamd51j20.go @@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) { } } +// DMA trigger source +func (spi SPI) triggerSource() (tx uint32) { + // See TRIGSRC field of CHCTRLA register for description of these constants. + switch spi.Bus { + case sam.SERCOM0_SPIM: + return 0x05 + case sam.SERCOM1_SPIM: + return 0x07 + case sam.SERCOM2_SPIM: + return 0x09 + case sam.SERCOM3_SPIM: + return 0x0A + case sam.SERCOM4_SPIM: + return 0x0C + case sam.SERCOM5_SPIM: + return 0x0E + default: + return 0 // should be unreachable + } +} + // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) diff --git a/src/machine/machine_atsamd51p19.go b/src/machine/machine_atsamd51p19.go index 70050c2d6f..9f261723e6 100644 --- a/src/machine/machine_atsamd51p19.go +++ b/src/machine/machine_atsamd51p19.go @@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) { } } +// DMA trigger source +func (spi SPI) triggerSource() (tx uint32) { + // See TRIGSRC field of CHCTRLA register for description of these constants. + switch spi.Bus { + case sam.SERCOM0_SPIM: + return 0x05 + case sam.SERCOM1_SPIM: + return 0x07 + case sam.SERCOM2_SPIM: + return 0x09 + case sam.SERCOM3_SPIM: + return 0x0A + case sam.SERCOM4_SPIM: + return 0x0C + case sam.SERCOM5_SPIM: + return 0x0E + case sam.SERCOM6_SPIM: + return 0x10 + case sam.SERCOM7_SPIM: + return 0x12 + default: + return 0 // should be unreachable + } +} + // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) diff --git a/src/machine/machine_atsamd51p20.go b/src/machine/machine_atsamd51p20.go index 9f52ba257f..92fc947f3b 100644 --- a/src/machine/machine_atsamd51p20.go +++ b/src/machine/machine_atsamd51p20.go @@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) { } } +// DMA trigger source +func (spi SPI) triggerSource() (tx uint32) { + // See TRIGSRC field of CHCTRLA register for description of these constants. + switch spi.Bus { + case sam.SERCOM0_SPIM: + return 0x05 + case sam.SERCOM1_SPIM: + return 0x07 + case sam.SERCOM2_SPIM: + return 0x09 + case sam.SERCOM3_SPIM: + return 0x0A + case sam.SERCOM4_SPIM: + return 0x0C + case sam.SERCOM5_SPIM: + return 0x0E + case sam.SERCOM6_SPIM: + return 0x10 + case sam.SERCOM7_SPIM: + return 0x12 + default: + return 0 // should be unreachable + } +} + // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) diff --git a/src/machine/machine_atsame51j19.go b/src/machine/machine_atsame51j19.go index 8f8294a266..3b412e9f59 100644 --- a/src/machine/machine_atsame51j19.go +++ b/src/machine/machine_atsame51j19.go @@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) { } } +// DMA trigger source +func (spi SPI) triggerSource() (tx uint32) { + // See TRIGSRC field of CHCTRLA register for description of these constants. + switch spi.Bus { + case sam.SERCOM0_SPIM: + return 0x05 + case sam.SERCOM1_SPIM: + return 0x07 + case sam.SERCOM2_SPIM: + return 0x09 + case sam.SERCOM3_SPIM: + return 0x0A + case sam.SERCOM4_SPIM: + return 0x0C + case sam.SERCOM5_SPIM: + return 0x0E + default: + return 0 // should be unreachable + } +} + // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) diff --git a/src/machine/machine_atsame54p20.go b/src/machine/machine_atsame54p20.go index 922ee31474..74445283ad 100644 --- a/src/machine/machine_atsame54p20.go +++ b/src/machine/machine_atsame54p20.go @@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) { } } +// DMA trigger source +func (spi SPI) triggerSource() (tx uint32) { + // See TRIGSRC field of CHCTRLA register for description of these constants. + switch spi.Bus { + case sam.SERCOM0_SPIM: + return 0x05 + case sam.SERCOM1_SPIM: + return 0x07 + case sam.SERCOM2_SPIM: + return 0x09 + case sam.SERCOM3_SPIM: + return 0x0A + case sam.SERCOM4_SPIM: + return 0x0C + case sam.SERCOM5_SPIM: + return 0x0E + case sam.SERCOM6_SPIM: + return 0x10 + case sam.SERCOM7_SPIM: + return 0x12 + default: + return 0 // should be unreachable + } +} + // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) diff --git a/src/machine/spi-dummy-async.go b/src/machine/spi-dummy-async.go index e8eb030f92..0a7112c7f4 100644 --- a/src/machine/spi-dummy-async.go +++ b/src/machine/spi-dummy-async.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || sam || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || atsamd21 || (stm32 && !stm32f7x2 && !stm32l5x2) package machine