mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
8b3075fdba
Signed-off-by: deadprogram <ron@hybridgroup.com>
248 lines
7.1 KiB
Go
248 lines
7.1 KiB
Go
//go:build atsamd51
|
|
|
|
package flash
|
|
|
|
import (
|
|
"device/sam"
|
|
"machine"
|
|
"runtime/volatile"
|
|
"unsafe"
|
|
)
|
|
|
|
// NewQSPI returns a pointer to a flash device that uses the QSPI peripheral to
|
|
// communicate with a serial memory chip.
|
|
func NewQSPI(cs, sck, d0, d1, d2, d3 machine.Pin) *Device {
|
|
return &Device{
|
|
trans: &qspiTransport{
|
|
cs: cs,
|
|
sck: sck,
|
|
d0: d0,
|
|
d1: d1,
|
|
d2: d2,
|
|
d3: d3,
|
|
},
|
|
}
|
|
}
|
|
|
|
// QSPI address space on SAMD51 is 0x04000000 to 0x05000000
|
|
const (
|
|
// Low address of the QSPI address space on SAMD51
|
|
qspi_AHB_LO = 0x04000000
|
|
|
|
// High address of the QSPI address space on SAMD51
|
|
qspi_AHB_HI = 0x05000000
|
|
|
|
// Instruction frame for running sending a command to the device
|
|
iframeRunCommand = 0x0 |
|
|
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
|
|
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
|
|
sam.QSPI_INSTRFRAME_INSTREN |
|
|
(sam.QSPI_INSTRFRAME_TFRTYPE_READ << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
|
|
|
|
// Instruction frame for running a command that returns data
|
|
iframeReadCommand = 0x0 |
|
|
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
|
|
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
|
|
sam.QSPI_INSTRFRAME_INSTREN |
|
|
sam.QSPI_INSTRFRAME_DATAEN |
|
|
(sam.QSPI_INSTRFRAME_TFRTYPE_READ << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
|
|
|
|
// Instruction frame to set up the device to read from memory
|
|
iframeReadMemory = 0x0 |
|
|
sam.QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT |
|
|
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
|
|
sam.QSPI_INSTRFRAME_INSTREN |
|
|
sam.QSPI_INSTRFRAME_DATAEN |
|
|
sam.QSPI_INSTRFRAME_ADDREN |
|
|
(8 << sam.QSPI_INSTRFRAME_DUMMYLEN_Pos) |
|
|
(sam.QSPI_INSTRFRAME_TFRTYPE_READMEMORY << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
|
|
|
|
// Instruction frame for running a command that requires parameter data
|
|
iframeWriteCommand = 0x0 |
|
|
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
|
|
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
|
|
sam.QSPI_INSTRFRAME_INSTREN |
|
|
(sam.QSPI_INSTRFRAME_TFRTYPE_WRITE << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
|
|
|
|
// Instruction frame to set up the device for writing to memory
|
|
iframeWriteMemory = 0x0 |
|
|
sam.QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT |
|
|
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
|
|
sam.QSPI_INSTRFRAME_INSTREN |
|
|
sam.QSPI_INSTRFRAME_ADDREN |
|
|
sam.QSPI_INSTRFRAME_DATAEN |
|
|
(sam.QSPI_INSTRFRAME_TFRTYPE_WRITEMEMORY << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
|
|
|
|
// Instruction frame for running an erase command that requires and address
|
|
iframeEraseCommand = 0x0 |
|
|
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
|
|
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
|
|
sam.QSPI_INSTRFRAME_INSTREN |
|
|
sam.QSPI_INSTRFRAME_ADDREN |
|
|
(sam.QSPI_INSTRFRAME_TFRTYPE_WRITE << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
|
|
)
|
|
|
|
type qspiTransport struct {
|
|
cs machine.Pin
|
|
sck machine.Pin
|
|
d0 machine.Pin
|
|
d1 machine.Pin
|
|
d2 machine.Pin
|
|
d3 machine.Pin
|
|
}
|
|
|
|
func (q qspiTransport) configure(config *DeviceConfig) {
|
|
|
|
// enable main clocks
|
|
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_QSPI_)
|
|
sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_QSPI_)
|
|
sam.MCLK.AHBMASK.ClearBits(sam.MCLK_AHBMASK_QSPI_2X_)
|
|
|
|
sam.QSPI.CTRLA.SetBits(sam.QSPI_CTRLA_SWRST)
|
|
|
|
// enable all pins to be PinCom
|
|
q.d0.Configure(machine.PinConfig{Mode: machine.PinCom})
|
|
q.d1.Configure(machine.PinConfig{Mode: machine.PinCom})
|
|
q.d2.Configure(machine.PinConfig{Mode: machine.PinCom})
|
|
q.d3.Configure(machine.PinConfig{Mode: machine.PinCom})
|
|
q.cs.Configure(machine.PinConfig{Mode: machine.PinCom})
|
|
q.sck.Configure(machine.PinConfig{Mode: machine.PinCom})
|
|
|
|
// start out with 4Mhz
|
|
// can ignore the error, 4Mhz is always a valid speed
|
|
_ = q.setClockSpeed(4e6)
|
|
|
|
// configure the CTRLB register
|
|
sam.QSPI.CTRLB.Set(sam.QSPI_CTRLB_MODE_MEMORY |
|
|
(sam.QSPI_CTRLB_DATALEN_8BITS << sam.QSPI_CTRLB_DATALEN_Pos) |
|
|
(sam.QSPI_CTRLB_CSMODE_LASTXFER << sam.QSPI_CTRLB_CSMODE_Pos))
|
|
|
|
// enable the peripheral
|
|
sam.QSPI.CTRLA.SetBits(sam.QSPI_CTRLA_ENABLE)
|
|
}
|
|
|
|
func (q qspiTransport) supportQuadMode() bool {
|
|
return true
|
|
}
|
|
|
|
func (q qspiTransport) setClockSpeed(hz uint32) error {
|
|
// The clock speed for the QSPI peripheral is controlled by a divider, so
|
|
// we can't set the requested speed exactly. Instead we will increment the
|
|
// divider until the speed is less than or equal to the speed requested.
|
|
for div, freq := uint32(1), machine.CPUFrequency(); div < 256; div++ {
|
|
if freq/div <= hz {
|
|
sam.QSPI.BAUD.Set(div << sam.QSPI_BAUD_BAUD_Pos)
|
|
return nil
|
|
}
|
|
}
|
|
return ErrInvalidClockSpeed
|
|
}
|
|
|
|
func (q qspiTransport) runCommand(cmd byte) (err error) {
|
|
q.runInstruction(cmd, iframeRunCommand)
|
|
q.endTransfer()
|
|
return
|
|
}
|
|
|
|
func (q qspiTransport) readCommand(cmd byte, buf []byte) (err error) {
|
|
q.disableAndClearCache()
|
|
q.runInstruction(cmd, iframeReadCommand)
|
|
q.readInto(buf, 0)
|
|
q.endTransfer()
|
|
q.enableCache()
|
|
return
|
|
}
|
|
|
|
func (q qspiTransport) readMemory(addr uint32, buf []byte) (err error) {
|
|
if (addr + uint32(len(buf))) > (qspi_AHB_HI - qspi_AHB_LO) {
|
|
return ErrInvalidAddrRange
|
|
}
|
|
q.disableAndClearCache()
|
|
q.runInstruction(cmdQuadRead, iframeReadMemory)
|
|
q.readInto(buf, addr)
|
|
q.endTransfer()
|
|
q.enableCache()
|
|
return
|
|
}
|
|
|
|
func (q qspiTransport) writeCommand(cmd byte, data []byte) (err error) {
|
|
var dataen uint32
|
|
if len(data) > 0 {
|
|
dataen = sam.QSPI_INSTRFRAME_DATAEN
|
|
}
|
|
q.disableAndClearCache()
|
|
q.runInstruction(cmd, iframeWriteCommand|dataen)
|
|
q.writeFrom(data, 0)
|
|
q.endTransfer()
|
|
q.enableCache()
|
|
return
|
|
}
|
|
|
|
func (q qspiTransport) writeMemory(addr uint32, data []byte) (err error) {
|
|
if (addr + uint32(len(data))) > (qspi_AHB_HI - qspi_AHB_LO) {
|
|
return ErrInvalidAddrRange
|
|
}
|
|
q.disableAndClearCache()
|
|
q.runInstruction(cmdQuadPageProgram, iframeWriteMemory)
|
|
q.writeFrom(data, addr)
|
|
q.endTransfer()
|
|
q.enableCache()
|
|
return
|
|
}
|
|
|
|
func (q qspiTransport) eraseCommand(cmd byte, addr uint32) (err error) {
|
|
q.disableAndClearCache()
|
|
sam.QSPI.INSTRADDR.Set(addr)
|
|
q.runInstruction(cmd, iframeEraseCommand)
|
|
q.endTransfer()
|
|
q.enableCache()
|
|
return
|
|
}
|
|
|
|
func (q qspiTransport) runInstruction(cmd byte, iframe uint32) {
|
|
sam.QSPI.INSTRCTRL.Set(uint32(cmd))
|
|
sam.QSPI.INSTRFRAME.Set(iframe)
|
|
sam.QSPI.INSTRFRAME.Get() // dummy read for synchronization, as per datasheet
|
|
}
|
|
|
|
func (q qspiTransport) enableCache() {
|
|
sam.CMCC.CTRL.SetBits(sam.CMCC_CTRL_CEN)
|
|
}
|
|
|
|
func (q qspiTransport) disableAndClearCache() {
|
|
sam.CMCC.CTRL.ClearBits(sam.CMCC_CTRL_CEN)
|
|
for sam.CMCC.SR.HasBits(sam.CMCC_SR_CSTS) {
|
|
}
|
|
sam.CMCC.MAINT0.SetBits(sam.CMCC_MAINT0_INVALL)
|
|
}
|
|
|
|
func (q qspiTransport) endTransfer() {
|
|
sam.QSPI.CTRLA.Set(sam.QSPI_CTRLA_ENABLE | sam.QSPI_CTRLA_LASTXFER)
|
|
for !sam.QSPI.INTFLAG.HasBits(sam.QSPI_INTFLAG_INSTREND) {
|
|
}
|
|
sam.QSPI.INTFLAG.Set(sam.QSPI_INTFLAG_INSTREND)
|
|
}
|
|
|
|
func (q qspiTransport) readInto(buf []byte, addr uint32) {
|
|
var ptr = qspi_AHB_LO + uintptr(addr)
|
|
for i := range buf {
|
|
buf[i] = volatile.LoadUint8((*uint8)(unsafe.Pointer(ptr)))
|
|
ptr++
|
|
}
|
|
/* // NB(bcg): for some reason this reads data that results from commands in
|
|
// a different byte order than the loop above, but works fine for reading
|
|
// from memory. Oddly, the above loop seems to work fine in both cases.
|
|
ln := len(buf)
|
|
sl := (*[1 << 28]byte)(unsafe.Pointer(uintptr(qspi_AHB_LO + addr)))[:ln:ln]
|
|
copy(buf, sl)
|
|
*/
|
|
}
|
|
|
|
func (q qspiTransport) writeFrom(buf []byte, addr uint32) {
|
|
var ptr = qspi_AHB_LO + uintptr(addr)
|
|
for i := range buf {
|
|
volatile.StoreUint8((*uint8)(unsafe.Pointer(ptr)), buf[i])
|
|
ptr++
|
|
}
|
|
}
|