mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-06 03:53:42 +00:00
42fed60e61
Flash on the STM32L0 series of chips works a bit different and needs a slightly different implementation compared to other STM32 chips.
166 lines
4.9 KiB
Go
166 lines
4.9 KiB
Go
//go:build stm32l0
|
|
|
|
package machine
|
|
|
|
// The STM32L0 series of MCUs has a different type of flash than other STM32
|
|
// series chips. The programming interface is different, and the flash is erased
|
|
// to zero bits instead of one bits as on most flash. So this requires a
|
|
// different implementation.
|
|
|
|
import (
|
|
"device/stm32"
|
|
"runtime/interrupt"
|
|
"runtime/volatile"
|
|
"unsafe"
|
|
)
|
|
|
|
// compile-time check for ensuring we fulfill BlockDevice interface
|
|
var _ BlockDevice = flashBlockDevice{}
|
|
|
|
var Flash flashBlockDevice
|
|
|
|
type flashBlockDevice struct {
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
data := unsafe.Slice((*byte)(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-sized (32 bits) length data can be programmed.
|
|
// If the length of p is not long enough it will be padded with zero 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
|
|
}
|
|
if uintptr(off)%4 != 0 {
|
|
// Offset must be aligned on a word boundary.
|
|
return 0, errFlashCannotWriteData
|
|
}
|
|
|
|
unlockFlash()
|
|
defer lockFlash()
|
|
|
|
// Write words in this area.
|
|
for i := 0; i < len(p); i += 4 {
|
|
// Construct the word to write.
|
|
word := uint32(p[i])
|
|
if i+1 < len(p) {
|
|
word |= uint32(p[i+1]) << 8
|
|
}
|
|
if i+2 < len(p) {
|
|
word |= uint32(p[i+2]) << 16
|
|
}
|
|
if i+3 < len(p) {
|
|
word |= uint32(p[i+3]) << 24
|
|
}
|
|
|
|
// Find the pointer address to write.
|
|
address := FlashDataStart() + uintptr(off) + uintptr(i)
|
|
|
|
// Write the word to flash.
|
|
(*volatile.Register32)(unsafe.Pointer(address)).Set(word)
|
|
|
|
// Check for any errors.
|
|
if stm32.FLASH.SR.Get()&(stm32.Flash_SR_WRPERR|stm32.Flash_SR_NOTZEROERR|stm32.Flash_SR_SIZERR) != 0 {
|
|
return i, errFlashCannotWriteData
|
|
}
|
|
}
|
|
|
|
return len(p), nil
|
|
}
|
|
|
|
// Size returns the number of bytes in this block device.
|
|
func (f flashBlockDevice) Size() int64 {
|
|
return int64(FlashDataEnd() - FlashDataStart())
|
|
}
|
|
|
|
// 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 4
|
|
}
|
|
|
|
func eraseBlockSize() int64 {
|
|
return 128
|
|
}
|
|
|
|
// EraseBlockSize returns the smallest erasable area on this particular chip
|
|
// in bytes. This is used for the block size in EraseBlocks.
|
|
// It must be a power of two, and may be as small as 1. A typical size is 4096.
|
|
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.
|
|
// Note that block 0 should map to the address of FlashDataStart().
|
|
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
|
|
// Flash needs to be unlocked to be able to erase it.
|
|
unlockFlash()
|
|
defer lockFlash()
|
|
|
|
// Set the flash programming mode to erase a page.
|
|
// Note: lockFlash() will reset these flags to 0 so we don't need to
|
|
// explicitly set them to 0.
|
|
stm32.FLASH.PECR.Set(stm32.Flash_PECR_ERASE | stm32.Flash_PECR_PROG)
|
|
|
|
// Erase all pages in this range.
|
|
for i := uintptr(start); i < uintptr(start)+uintptr(len); i++ {
|
|
// Find the pointer address somewhere in the page to erase.
|
|
address := FlashDataStart() + i*uintptr(eraseBlockSize())
|
|
|
|
// To erase, write any value to that address.
|
|
(*volatile.Register32)(unsafe.Pointer(address)).Set(uint32(address))
|
|
|
|
// Check for any errors.
|
|
// The only error (that is not a programming error) that could happen is
|
|
// if a row is in a protected sector.
|
|
if stm32.FLASH.SR.Get()&(stm32.Flash_SR_WRPERR|stm32.Flash_SR_SIZERR) != 0 {
|
|
return errFlashCannotErasePage
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func unlockFlash() {
|
|
// Make sure the flash peripheral clock is enabled.
|
|
stm32.RCC.AHBENR.SetBits(stm32.RCC_AHBENR_MIFEN)
|
|
|
|
// Wait for the flash memory not to be busy.
|
|
for stm32.FLASH.GetSR_BSY() != 0 {
|
|
}
|
|
|
|
// Disable interrupts while writing, since no memory operations may happen
|
|
// while the unlock sequence is ongoing.
|
|
mask := interrupt.Disable()
|
|
|
|
// Remove PELOCK bit.
|
|
stm32.FLASH.PEKEYR.Set(0x89ABCDEF)
|
|
stm32.FLASH.PEKEYR.Set(0x02030405)
|
|
|
|
// Remove PRGLOCK bit.
|
|
stm32.FLASH.PRGKEYR.Set(0x8C9DAEBF)
|
|
stm32.FLASH.PRGKEYR.Set(0x13141516)
|
|
|
|
interrupt.Restore(mask)
|
|
}
|
|
|
|
func lockFlash() {
|
|
// Set PELOCK to 1, which also automatically sets PRGLOCK to 1.
|
|
stm32.FLASH.PECR.Set(stm32.Flash_PECR_PELOCK)
|
|
}
|