Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

machine: add SPI DMA support for the rp2040 and samd51 #3985

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions src/machine/machine_atsamd51.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions src/machine/machine_atsamd51g19.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions src/machine/machine_atsamd51j19.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions src/machine/machine_atsamd51j20.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions src/machine/machine_atsamd51p19.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions src/machine/machine_atsamd51p20.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions src/machine/machine_atsame51j19.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions src/machine/machine_atsame54p20.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading