From aa125798642029b5e2cf533ea983d5cb74883778 Mon Sep 17 00:00:00 2001 From: John Clark Date: Sun, 8 Jan 2023 07:53:41 -0500 Subject: [PATCH] Add SPI support for for the ESP32-C3 device. Signed-off-by: John Clark --- src/machine/machine_esp32c3_spi.go | 324 +++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/machine/machine_esp32c3_spi.go diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go new file mode 100644 index 00000000..7430df4d --- /dev/null +++ b/src/machine/machine_esp32c3_spi.go @@ -0,0 +1,324 @@ +//go:build esp32c3 +// +build esp32c3 + +package machine + +// On the C3 variant, SPI2 is a general purpose SPI controller. SPI0 and SPI1 +// are used internally to access the ESP32-C3’s attached flash memory. Due to +// different registers between SPI2 and the other SPI ports, this driver +// currently supports only the the general purpose FSPI SPI2 controller. +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/spi_master.html + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +const ( + SPI_MODE0 = uint8(0) + SPI_MODE1 = uint8(1) + SPI_MODE2 = uint8(2) + SPI_MODE3 = uint8(3) + + FSPICLK_IN_IDX = uint32(63) + FSPICLK_OUT_IDX = uint32(63) + FSPIQ_IN_IDX = uint32(64) + FSPIQ_OUT_IDX = uint32(64) + FSPID_IN_IDX = uint32(65) + FSPID_OUT_IDX = uint32(65) + FSPIHD_IN_IDX = uint32(66) + FSPIHD_OUT_IDX = uint32(66) + FSPIWP_IN_IDX = uint32(67) + FSPIWP_OUT_IDX = uint32(67) + FSPICS0_IN_IDX = uint32(68) + FSPICS0_OUT_IDX = uint32(68) + FSPICS1_OUT_IDX = uint32(69) + FSPICS2_OUT_IDX = uint32(70) + FSPICS3_OUT_IDX = uint32(71) + FSPICS4_OUT_IDX = uint32(72) + FSPICS5_OUT_IDX = uint32(73) +) + +var ( + ErrInvalidSPIBus = errors.New("machine: SPI bus is invalid") + ErrInvalidSPIMode = errors.New("machine: SPI mode is invalid") +) + +// Serial Peripheral Interface on the ESP32-C3. +type SPI struct { + Bus *esp.SPI2_Type +} + +var ( + // SPI0 and SPI1 are reserved for use by the caching system etc. + SPI2 = SPI{esp.SPI2} +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin // Serial Clock + SDO Pin // Serial Data Out (MOSI) + SDI Pin // Serial Data In (MISO) + CS Pin // Chip Select (optional) + LSBFirst bool // MSB is default + Mode uint8 // SPI_MODE0 is default +} + +// Compute the SPI bus frequency from the CPU frequency. +func freqToClockDiv(hz uint32) uint32 { + fcpu := CPUFrequency() + if hz >= fcpu { // maximum frequency + return 1 << 31 + } + if hz < (fcpu / (16 * 64)) { // minimum frequency + return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63 + } + + // iterate looking for an exact match + // or iterate all 16 prescaler options + // looking for the smallest error + var bestPre, bestN, bestErr uint32 + bestN = 1 + bestErr = 0xffffffff + q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5)) + for p := uint32(0); p < 16; p++ { + n := q/(p+1) - 1 + if n < 1 { // prescaler became too large, stop enum + break + } + if n > 63 { // prescaler too small, skip to next + continue + } + + freq := fcpu / ((p + 1) * (n + 1)) + if freq == hz { // exact match + return p<<18 | n<<12 | (n/2)<<6 | n + } + + var err uint32 + if freq < hz { + err = hz - freq + } else { + err = freq - hz + } + if err < bestErr { + bestErr = err + bestPre = p + bestN = n + } + } + + return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN +} + +// Configure and make the SPI peripheral ready to use. +func (spi SPI) Configure(config SPIConfig) error { + // right now this is only setup to work for the esp32c3 spi2 bus + if spi.Bus != esp.SPI2 { + return ErrInvalidSPIBus + } + + // periph module reset + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0) + + // periph module enable + esp.SYSTEM.SetPERIP_CLK_EN0_SPI2_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0) + + // init the spi2 bus + spi.Bus.SLAVE.Set(0) + spi.Bus.MISC.Set(0) + spi.Bus.USER.Set(0) + spi.Bus.USER1.Set(0) + spi.Bus.CTRL.Set(0) + spi.Bus.CLK_GATE.Set(0) + spi.Bus.DMA_CONF.Set(0) + spi.Bus.SetDMA_CONF_RX_AFIFO_RST(1) + spi.Bus.SetDMA_CONF_BUF_AFIFO_RST(1) + spi.Bus.CLOCK.Set(0) + + // clear data buf + spi.Bus.SetW0(0) + spi.Bus.SetW1(0) + spi.Bus.SetW2(0) + spi.Bus.SetW3(0) + spi.Bus.SetW4(0) + spi.Bus.SetW5(0) + spi.Bus.SetW6(0) + spi.Bus.SetW7(0) + spi.Bus.SetW8(0) + spi.Bus.SetW9(0) + spi.Bus.SetW10(0) + spi.Bus.SetW11(0) + spi.Bus.SetW12(0) + spi.Bus.SetW13(0) + spi.Bus.SetW14(0) + spi.Bus.SetW15(0) + + // start the spi2 bus + spi.Bus.SetCLK_GATE_CLK_EN(1) + spi.Bus.SetCLK_GATE_MST_CLK_SEL(1) + spi.Bus.SetCLK_GATE_MST_CLK_ACTIVE(1) + spi.Bus.SetDMA_CONF_SLV_TX_SEG_TRANS_CLR_EN(1) + spi.Bus.SetDMA_CONF_SLV_RX_SEG_TRANS_CLR_EN(1) + spi.Bus.SetDMA_CONF_DMA_SLV_SEG_TRANS_EN(0) + spi.Bus.SetUSER_USR_MOSI(1) + spi.Bus.SetUSER_USR_MISO(1) + spi.Bus.SetUSER_DOUTDIN(1) + + // set spi2 data mode + switch config.Mode { + case SPI_MODE0: + spi.Bus.SetMISC_CK_IDLE_EDGE(0) + spi.Bus.SetUSER_CK_OUT_EDGE(0) + case SPI_MODE1: + spi.Bus.SetMISC_CK_IDLE_EDGE(0) + spi.Bus.SetUSER_CK_OUT_EDGE(1) + case SPI_MODE2: + spi.Bus.SetMISC_CK_IDLE_EDGE(1) + spi.Bus.SetUSER_CK_OUT_EDGE(1) + case SPI_MODE3: + spi.Bus.SetMISC_CK_IDLE_EDGE(1) + spi.Bus.SetUSER_CK_OUT_EDGE(0) + default: + return ErrInvalidSPIMode + } + + // set spi2 bit order + if config.LSBFirst { + spi.Bus.SetCTRL_WR_BIT_ORDER(1) // LSB first + spi.Bus.SetCTRL_RD_BIT_ORDER(1) + } else { + spi.Bus.SetCTRL_WR_BIT_ORDER(0) // MSB first + spi.Bus.SetCTRL_RD_BIT_ORDER(0) + } + + // configure SPI bus clock + spi.Bus.CLOCK.Set(freqToClockDiv(config.Frequency)) + + // configure esp32c3 gpio pin matrix + config.SDI.Configure(PinConfig{Mode: PinInput}) + inFunc(FSPIQ_IN_IDX).Set(esp.GPIO_FUNC_IN_SEL_CFG_SIG_IN_SEL | uint32(config.SDI)) + config.SDO.Configure(PinConfig{Mode: PinOutput}) + config.SDO.outFunc().Set(FSPID_OUT_IDX) + config.SCK.Configure(PinConfig{Mode: PinOutput}) + config.SCK.outFunc().Set(FSPICLK_OUT_IDX) + if config.CS != NoPin { + config.CS.Configure(PinConfig{Mode: PinOutput}) + config.CS.outFunc().Set(FSPICS0_OUT_IDX) + } + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. If you need to +// transfer larger amounts of data, Tx will be faster. +func (spi SPI) Transfer(w byte) (byte, error) { + spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7) + + spi.Bus.SetW0(uint32(w)) + + // Send/receive byte. + spi.Bus.SetCMD_UPDATE(1) + for spi.Bus.GetCMD_UPDATE() != 0 { + } + + spi.Bus.SetCMD_USR(1) + for spi.Bus.GetCMD_USR() != 0 { + } + + // The received byte is stored in W0. + return byte(spi.Bus.GetW0()), nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// This is accomplished by sending zero bits if r is bigger than w or discarding +// the incoming data if w is bigger than r. +func (spi SPI) Tx(w, r []byte) error { + toTransfer := len(w) + if len(r) > toTransfer { + toTransfer = len(r) + } + + for toTransfer > 0 { + // Chunk 64 bytes at a time. + chunkSize := toTransfer + if chunkSize > 64 { + chunkSize = 64 + } + + // Fill tx buffer. + transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0)))) + if len(w) >= 64 { + // We can fill the entire 64-byte transfer buffer with data. + // This loop is slightly faster than the loop below. + for i := 0; i < 16; i++ { + word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24 + transferWords[i].Set(word) + } + } else { + // We can't fill the entire transfer buffer, so we need to be a bit + // more careful. + // Note that parts of the transfer buffer that aren't used still + // need to be set to zero, otherwise we might be transferring + // garbage from a previous transmission if w is smaller than r. + for i := 0; i < 16; i++ { + var word uint32 + if i*4+3 < len(w) { + word |= uint32(w[i*4+3]) << 24 + } + if i*4+2 < len(w) { + word |= uint32(w[i*4+2]) << 16 + } + if i*4+1 < len(w) { + word |= uint32(w[i*4+1]) << 8 + } + if i*4+0 < len(w) { + word |= uint32(w[i*4+0]) << 0 + } + transferWords[i].Set(word) + } + } + + // Do the transfer. + spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1) + + spi.Bus.SetCMD_UPDATE(1) + for spi.Bus.GetCMD_UPDATE() != 0 { + } + + spi.Bus.SetCMD_USR(1) + for spi.Bus.GetCMD_USR() != 0 { + } + + // Read rx buffer. + rxSize := 64 + if rxSize > len(r) { + rxSize = len(r) + } + for i := 0; i < rxSize; i++ { + r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8)) + } + + // Cut off some part of the output buffer so the next iteration we will + // only send the remaining bytes. + if len(w) < chunkSize { + w = nil + } else { + w = w[chunkSize:] + } + if len(r) < chunkSize { + r = nil + } else { + r = r[chunkSize:] + } + toTransfer -= chunkSize + } + + return nil +}