aboutsummaryrefslogtreecommitdiffstats
path: root/lib/bossac/src/D5xNvmFlash.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bossac/src/D5xNvmFlash.cpp')
-rw-r--r--lib/bossac/src/D5xNvmFlash.cpp352
1 files changed, 352 insertions, 0 deletions
diff --git a/lib/bossac/src/D5xNvmFlash.cpp b/lib/bossac/src/D5xNvmFlash.cpp
new file mode 100644
index 00000000..81e5583f
--- /dev/null
+++ b/lib/bossac/src/D5xNvmFlash.cpp
@@ -0,0 +1,352 @@
+///////////////////////////////////////////////////////////////////////////////
+// BOSSA
+//
+// Copyright (c) 2018, ShumaTech
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "D5xNvmFlash.h"
+
+
+#define CMDEX_KEY 0xa500
+
+#define NVM_REG_BASE 0x41004000
+
+#define NVM_REG_CTRLA 0x00
+#define NVM_REG_CTRLB 0x04
+#define NVM_REG_INTFLAG 0x10
+#define NVM_REG_STATUS 0x12
+#define NVM_REG_ADDR 0x14
+#define NVM_REG_RUNLOCK 0x18
+
+#define NVM_CMD_EP 0x00
+#define NVM_CMD_EB 0x01
+#define NVM_CMD_WP 0x03
+#define NVM_CMD_WQW 0x04
+#define NVM_CMD_LR 0x11
+#define NVM_CMD_UR 0x12
+#define NVM_CMD_SSB 0x16
+#define NVM_CMD_PBC 0x15
+
+#define ERASE_BLOCK_PAGES 16 // pages
+
+// NVM User Page
+#define NVM_UP_ADDR 0x804000
+#define NVM_UP_SIZE (_size)
+#define NVM_UP_BOD33_DISABLE_OFFSET 0x0
+#define NVM_UP_BOD33_DISABLE_MASK 0x1
+#define NVM_UP_BOD33_RESET_OFFSET 0x1
+#define NVM_UP_BOD33_RESET_MASK 0x2
+#define NVM_UP_NVM_LOCK_OFFSET 0x8
+
+D5xNvmFlash::D5xNvmFlash(
+ Samba& samba,
+ const std::string& name,
+ uint32_t pages,
+ uint32_t size,
+ uint32_t user,
+ uint32_t stack)
+ :
+ Flash(samba, name, 0, pages, size, 1, 32, user, stack), _eraseAuto(true)
+{
+}
+
+D5xNvmFlash::~D5xNvmFlash()
+{
+}
+
+void
+D5xNvmFlash::erase(uint32_t offset, uint32_t size)
+{
+ uint32_t eraseSize = _size * ERASE_BLOCK_PAGES;
+
+ // Offset must be a multiple of the erase size
+ if (offset % eraseSize)
+ throw FlashEraseError();
+
+ // Offset and size must be in range
+ if (offset + size > totalSize())
+ throw FlashEraseError();
+
+ uint32_t eraseEnd = (offset + size + eraseSize - 1) / eraseSize;
+
+ // Erase each erase size set of pages
+ for (uint32_t eraseNum = offset / eraseSize; eraseNum < eraseEnd; eraseNum++)
+ {
+ // Issue erase command
+ writeRegU32(NVM_REG_ADDR, eraseNum * eraseSize);
+ command(NVM_CMD_EB);
+ }
+}
+
+void
+D5xNvmFlash::eraseAll(uint32_t offset)
+{
+ // Use the extended Samba command if available
+ if (_samba.canChipErase())
+ {
+ _samba.chipErase(offset);
+ }
+ else
+ {
+ erase(offset, totalSize() - offset);
+ }
+}
+
+void
+D5xNvmFlash::waitReady()
+{
+ while ((readRegU16(NVM_REG_STATUS) & 0x1) == 0);
+}
+
+void
+D5xNvmFlash::eraseAuto(bool enable)
+{
+ _eraseAuto = enable;
+}
+
+std::vector<bool>
+D5xNvmFlash::getLockRegions()
+{
+ uint8_t lockBits = 0;
+ uint32_t addr = NVM_UP_ADDR + NVM_UP_NVM_LOCK_OFFSET;
+ std::vector<bool> regions(_lockRegions);
+
+ for (uint32_t region = 0; region < _lockRegions; region++)
+ {
+ if (region % 8 == 0)
+ lockBits = _samba.readByte(addr++);
+ regions[region] = (lockBits & (1 << (region % 8))) == 0;
+ }
+
+ return regions;
+}
+
+bool
+D5xNvmFlash::getSecurity()
+{
+ // There doesn't seem to be a way to read this
+ return false;
+}
+
+bool
+D5xNvmFlash::getBod()
+{
+ uint8_t byte = _samba.readByte(NVM_UP_ADDR + NVM_UP_BOD33_DISABLE_OFFSET);
+
+ return (byte & NVM_UP_BOD33_DISABLE_MASK) == 0;
+}
+
+bool
+D5xNvmFlash::getBor()
+{
+ uint8_t byte = _samba.readByte(NVM_UP_ADDR + NVM_UP_BOD33_RESET_OFFSET);
+
+ return (byte & NVM_UP_BOD33_RESET_MASK) != 0;
+}
+
+bool
+D5xNvmFlash::getBootFlash()
+{
+ return true;
+}
+
+void
+D5xNvmFlash::readUserPage(std::unique_ptr<uint8_t[]>& userPage)
+{
+ if (!userPage)
+ {
+ userPage.reset(new uint8_t[NVM_UP_SIZE]);
+ _samba.read(NVM_UP_ADDR, userPage.get(), NVM_UP_SIZE);
+ }
+}
+
+void
+D5xNvmFlash::writeOptions()
+{
+ std::unique_ptr<uint8_t[]> userPage;
+
+ if (canBor() && _bor.isDirty() && _bor.get() != getBor())
+ {
+ readUserPage(userPage);
+ if (_bor.get())
+ userPage[NVM_UP_BOD33_RESET_OFFSET] |= NVM_UP_BOD33_RESET_MASK;
+ else
+ userPage[NVM_UP_BOD33_RESET_OFFSET] &= ~NVM_UP_BOD33_RESET_MASK;
+ }
+ if (canBod() && _bod.isDirty() && _bod.get() != getBod())
+ {
+ readUserPage(userPage);
+ if (_bod.get())
+ userPage[NVM_UP_BOD33_DISABLE_OFFSET] &= ~NVM_UP_BOD33_DISABLE_MASK;
+ else
+ userPage[NVM_UP_BOD33_DISABLE_OFFSET] |= NVM_UP_BOD33_DISABLE_MASK;
+ }
+ if (_regions.isDirty())
+ {
+ // Check if any lock bits are different from the current set
+ std::vector<bool> current = getLockRegions();
+ if (!equal(_regions.get().begin(), _regions.get().end(), current.begin()))
+ {
+ readUserPage(userPage);
+
+ uint8_t* lockBits = &userPage[NVM_UP_NVM_LOCK_OFFSET];
+ for (uint32_t region = 0; region < _regions.get().size(); region++)
+ {
+ if (_regions.get()[region])
+ lockBits[region / 8] &= ~(1 << (region % 8));
+ else
+ lockBits[region / 8] |= (1 << (region % 8));
+ }
+ }
+ }
+
+ // Erase and write the user page if modified
+ if (userPage)
+ {
+ // Configure manual page write and disable caches
+ writeRegU16(NVM_REG_CTRLA, (readRegU16(NVM_REG_CTRLA) | (0x3 << 14)) & 0xffcf);
+
+ // Erase user page
+ writeRegU32(NVM_REG_ADDR, NVM_UP_ADDR);
+ command(NVM_CMD_EP);
+
+ // Write user page in quad-word chunks
+ for (uint32_t offset = 0; offset < NVM_UP_SIZE; offset += 16)
+ {
+ // Load the buffer with the quad word
+ loadBuffer(&userPage[offset], 16);
+
+ // Clear page buffer
+ command(NVM_CMD_PBC);
+
+ // Copy quad word to page buffer
+ _wordCopy.setDstAddr(NVM_UP_ADDR + offset);
+ _wordCopy.setSrcAddr(_onBufferA ? _pageBufferA : _pageBufferB);
+ _wordCopy.setWords(4);
+ _onBufferA = !_onBufferA;
+ waitReady();
+ _wordCopy.runv();
+
+ // Write the quad word
+ writeRegU32(NVM_REG_ADDR, NVM_UP_ADDR + offset);
+ command(NVM_CMD_WQW);
+ }
+ }
+
+ // Always do security last
+ if (_security.isDirty() && _security.get() == true && _security.get() != getSecurity())
+ {
+ command(NVM_CMD_SSB);
+ }
+}
+
+void
+D5xNvmFlash::writePage(uint32_t page)
+{
+ if (page >= _pages)
+ {
+ throw FlashPageError();
+ }
+
+ // Configure manual page write and disable caches
+ writeRegU16(NVM_REG_CTRLA, (readRegU16(NVM_REG_CTRLA) | (0x3 << 14)) & 0xffcf);
+
+ // Auto-erase if writing at the start of the erase page
+ if (_eraseAuto && page % ERASE_BLOCK_PAGES == 0)
+ {
+ erase(page * _size, ERASE_BLOCK_PAGES * _size);
+ }
+
+ // Clear page bur
+ command(NVM_CMD_PBC);
+
+ uint32_t addr = _addr + (page * _size );
+
+ _wordCopy.setDstAddr(addr);
+ _wordCopy.setSrcAddr(_onBufferA ? _pageBufferA : _pageBufferB);
+ _wordCopy.setWords(_size / sizeof(uint32_t));
+ _onBufferA = !_onBufferA;
+ waitReady();
+ _wordCopy.runv();
+
+ writeRegU32(NVM_REG_ADDR, addr);
+ command(NVM_CMD_WP);
+}
+
+void
+D5xNvmFlash::readPage(uint32_t page, uint8_t* buf)
+{
+ if (page >= _pages)
+ {
+ throw FlashPageError();
+ }
+
+ _samba.read(_addr + (page * _size), buf, _size);
+}
+
+uint16_t
+D5xNvmFlash::readRegU16(uint8_t reg)
+{
+ return (uint16_t) _samba.readByte(NVM_REG_BASE + reg) |
+ (_samba.readByte(NVM_REG_BASE + reg + 1) << 8);
+}
+
+void
+D5xNvmFlash::writeRegU16(uint8_t reg, uint16_t value)
+{
+ _samba.writeByte(NVM_REG_BASE + reg, value & 0xff);
+ _samba.writeByte(NVM_REG_BASE + reg + 1, value >> 8);
+}
+
+uint32_t
+D5xNvmFlash::readRegU32(uint8_t reg)
+{
+ return _samba.readWord(NVM_REG_BASE + reg);
+}
+
+void
+D5xNvmFlash::writeRegU32(uint8_t reg, uint32_t value)
+{
+ _samba.writeWord(NVM_REG_BASE + reg, value);
+}
+
+void
+D5xNvmFlash::command(uint8_t cmd)
+{
+ waitReady();
+
+ writeRegU32(NVM_REG_CTRLB, CMDEX_KEY | cmd);
+
+ waitReady();
+
+ if (readRegU16(NVM_REG_INTFLAG) & 0xce)
+ {
+ // Clear the error bits
+ writeRegU16(NVM_REG_INTFLAG, 0xce);
+ throw FlashCmdError();
+ }
+}
+
+void
+D5xNvmFlash::writeBuffer(uint32_t dst_addr, uint32_t size)
+{
+ // Auto-erase if writing at the start of the erase page
+ if (_eraseAuto && ((dst_addr / _size) % ERASE_BLOCK_PAGES == 0))
+ erase(dst_addr, size);
+
+ // Call the base class method
+ Flash::writeBuffer(dst_addr, size);
+}