diff options
author | Ron Evans <[email protected]> | 2023-03-08 06:06:49 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2023-03-08 06:06:49 +0100 |
commit | fc28f513c7cb60c4c1f25d12caa6ae381f6f0308 (patch) | |
tree | 095a5228ee4101afa89b7f974d7fb0863d2c12f4 /src/machine/machine_atsamd21.go | |
parent | 34ba0c1be1e5adc4b5095fa9d1e15a7a26e2f519 (diff) | |
download | tinygo-fc28f513c7cb60c4c1f25d12caa6ae381f6f0308.tar.gz tinygo-fc28f513c7cb60c4c1f25d12caa6ae381f6f0308.zip |
machine/samd21: implement Flash interface (#3496)
machine/samd21: implement Flash interface
Diffstat (limited to 'src/machine/machine_atsamd21.go')
-rw-r--r-- | src/machine/machine_atsamd21.go | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 7a5a20c7e..2f4f7338e 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -7,8 +7,11 @@ package machine import ( + "bytes" "device/arm" "device/sam" + "encoding/binary" + "errors" "runtime/interrupt" "unsafe" ) @@ -1788,3 +1791,167 @@ func syncDAC() { for sam.DAC.STATUS.HasBits(sam.DAC_STATUS_SYNCBUSY) { } } + +// Flash related code +const memoryStart = 0x0 + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { + initComplete bool +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + f.ensureInitComplete() + + waitWhileFlashBusy() + + data := unsafe.Slice((*byte)(unsafe.Add(unsafe.Pointer(FlashDataStart()), uintptr(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Only word (32 bits) length data can be programmed. +// See Atmel-42181G–SAM-D21_Datasheet–09/2015 page 359. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + f.ensureInitComplete() + + address := FlashDataStart() + uintptr(off) + padded := f.pad(p) + + waitWhileFlashBusy() + + for j := 0; j < len(padded); j += int(f.WriteBlockSize()) { + // write word + *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(padded[j : j+int(f.WriteBlockSize())]) + + sam.NVMCTRL.SetADDR(uint32(address >> 1)) + sam.NVMCTRL.CTRLA.Set(sam.NVMCTRL_CTRLA_CMD_WP | (sam.NVMCTRL_CTRLA_CMDEX_KEY << sam.NVMCTRL_CTRLA_CMDEX_Pos)) + + waitWhileFlashBusy() + + if err := checkFlashError(); err != nil { + return j, err + } + + address += uintptr(f.WriteBlockSize()) + } + + return len(padded), nil +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 4 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +const eraseBlockSizeValue = 256 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (f flashBlockDevice) EraseBlocks(start, len int64) error { + f.ensureInitComplete() + + address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) + waitWhileFlashBusy() + + for i := start; i < start+len; i++ { + sam.NVMCTRL.SetADDR(uint32(address >> 1)) + sam.NVMCTRL.CTRLA.Set(sam.NVMCTRL_CTRLA_CMD_ER | (sam.NVMCTRL_CTRLA_CMDEX_KEY << sam.NVMCTRL_CTRLA_CMDEX_Pos)) + + waitWhileFlashBusy() + + if err := checkFlashError(); err != nil { + return err + } + + address += uintptr(f.EraseBlockSize()) + } + + return nil +} + +// pad data if needed so it is long enough for correct byte alignment on writes. +func (f flashBlockDevice) pad(p []byte) []byte { + paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize()) + if paddingNeeded == 0 { + return p + } + + padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded)) + return append(p, padding...) +} + +func (f flashBlockDevice) ensureInitComplete() { + if f.initComplete { + return + } + + sam.NVMCTRL.SetCTRLB_READMODE(sam.NVMCTRL_CTRLB_READMODE_NO_MISS_PENALTY) + sam.NVMCTRL.SetCTRLB_SLEEPPRM(sam.NVMCTRL_CTRLB_SLEEPPRM_WAKEONACCESS) + + waitWhileFlashBusy() + + f.initComplete = true +} + +func waitWhileFlashBusy() { + for sam.NVMCTRL.GetINTFLAG_READY() != sam.NVMCTRL_INTFLAG_READY { + } +} + +var ( + errFlashPROGE = errors.New("errFlashPROGE") + errFlashLOCKE = errors.New("errFlashLOCKE") + errFlashNVME = errors.New("errFlashNVME") +) + +func checkFlashError() error { + switch { + case sam.NVMCTRL.GetSTATUS_PROGE() != 0: + return errFlashPROGE + case sam.NVMCTRL.GetSTATUS_LOCKE() != 0: + return errFlashLOCKE + case sam.NVMCTRL.GetSTATUS_NVME() != 0: + return errFlashNVME + } + + return nil +} |