diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 7118792a7..52f7a712b 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 ade031bb0..64f873265 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 b41c25c14..98e21e1e8 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 2c5391afe..322bcc0de 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 70050c2d6..9f261723e 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 9f52ba257..92fc947f3 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 8f8294a26..3b412e9f5 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 922ee3147..74445283a 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 e8eb030f9..0a7112c7f 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