/* * Flash.c * * Created on: 20.03.2013 * Author: skuser */ #include "Memory.h" #include "Configuration.h" #include "Common.h" #include "Settings.h" #include "LEDHook.h" #include "System.h" #define USE_DMA #define RECV_DMA DMA.CH0 #define SEND_DMA DMA.CH1 /* Convert defines from Makefile */ #define FLASH_DATA_START FLASH_DATA_ADDR #define FLASH_DATA_END (FLASH_DATA_ADDR + FLASH_DATA_SIZE - 1) /* Definitions for FRAM */ #define FRAM_USART USARTD0 #define FRAM_PORT PORTD #define FRAM_CS PIN4_bm #define FRAM_MOSI PIN3_bm #define FRAM_MISO PIN2_bm #define FRAM_SCK PIN1_bm /* Declarations from assembler file */ uint16_t FlashReadWord(uint32_t Address); void FlashEraseApplicationPage(uint32_t Address); void FlashLoadFlashWord(uint16_t Address, uint16_t Data); void FlashEraseWriteApplicationPage(uint32_t Address); void FlashEraseFlashBuffer(void); void FlashWaitForSPM(void); static uint8_t ScrapBuffer[] = {0}; INLINE uint8_t SPITransferByte(uint8_t Data) { FRAM_USART.DATA = Data; while (!(FRAM_USART.STATUS & USART_RXCIF_bm)); return FRAM_USART.DATA; } #ifdef USE_DMA INLINE void SPIReadBlock(void *Buffer, uint16_t ByteCount) { /* Set up read and write transfers */ RECV_DMA.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_FIXED_gc | DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_INC_gc; RECV_DMA.DESTADDR0 = ((uintptr_t) Buffer >> 0) & 0xFF; RECV_DMA.DESTADDR1 = ((uintptr_t) Buffer >> 8) & 0xFF; RECV_DMA.TRFCNT = ByteCount; SEND_DMA.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_FIXED_gc | DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_FIXED_gc; SEND_DMA.SRCADDR0 = ((uintptr_t) ScrapBuffer >> 0) & 0xFF; SEND_DMA.SRCADDR1 = ((uintptr_t) ScrapBuffer >> 8) & 0xFF; SEND_DMA.TRFCNT = ByteCount; /* Enable read and write transfers */ RECV_DMA.CTRLA |= DMA_CH_ENABLE_bm; SEND_DMA.CTRLA |= DMA_CH_ENABLE_bm; /* Wait for DMA to finish */ while (RECV_DMA.CTRLA & DMA_CH_ENABLE_bm) ; /* Clear Interrupt flag */ RECV_DMA.CTRLB = DMA_CH_TRNIF_bm | DMA_CH_ERRIF_bm; SEND_DMA.CTRLB = DMA_CH_TRNIF_bm | DMA_CH_ERRIF_bm; } #else INLINE void SPIReadBlock(void *Buffer, uint16_t ByteCount) { uint8_t *ByteBuffer = (uint8_t *) Buffer; while (ByteCount-- > 0) { FRAM_USART.DATA = 0; while (!(FRAM_USART.STATUS & USART_RXCIF_bm)); *ByteBuffer++ = FRAM_USART.DATA; } } #endif #ifdef USE_DMA INLINE void SPIWriteBlock(const void *Buffer, uint16_t ByteCount) { /* Set up read and write transfers */ RECV_DMA.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_FIXED_gc | DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_FIXED_gc; RECV_DMA.DESTADDR0 = ((uintptr_t) ScrapBuffer >> 0) & 0xFF; RECV_DMA.DESTADDR1 = ((uintptr_t) ScrapBuffer >> 8) & 0xFF; RECV_DMA.TRFCNT = ByteCount; SEND_DMA.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_FIXED_gc; SEND_DMA.SRCADDR0 = ((uintptr_t) Buffer >> 0) & 0xFF; SEND_DMA.SRCADDR1 = ((uintptr_t) Buffer >> 8) & 0xFF; SEND_DMA.TRFCNT = ByteCount; /* Enable read and write transfers */ RECV_DMA.CTRLA |= DMA_CH_ENABLE_bm; SEND_DMA.CTRLA |= DMA_CH_ENABLE_bm; /* Wait for DMA to finish */ while (RECV_DMA.CTRLA & DMA_CH_ENABLE_bm) ; /* Clear Interrupt flag */ RECV_DMA.CTRLB = DMA_CH_TRNIF_bm | DMA_CH_ERRIF_bm; SEND_DMA.CTRLB = DMA_CH_TRNIF_bm | DMA_CH_ERRIF_bm; } #else INLINE void SPIWriteBlock(const void *Buffer, uint16_t ByteCount) { uint8_t *ByteBuffer = (uint8_t *) Buffer; while (ByteCount-- > 0) { FRAM_USART.DATA = *ByteBuffer++; while (!(FRAM_USART.STATUS & USART_RXCIF_bm)); FRAM_USART.DATA; /* Flush Buffer */ } } #endif INLINE void FRAMRead(void *Buffer, uint16_t Address, uint16_t ByteCount) { FRAM_PORT.OUTCLR = FRAM_CS; SPITransferByte(0x03); /* Read command */ SPITransferByte((Address >> 8) & 0xFF); /* Address hi and lo byte */ SPITransferByte((Address >> 0) & 0xFF); SPIReadBlock(Buffer, ByteCount); FRAM_PORT.OUTSET = FRAM_CS; } INLINE void FRAMWrite(const void *Buffer, uint16_t Address, uint16_t ByteCount) { FRAM_PORT.OUTCLR = FRAM_CS; SPITransferByte(0x06); /* Write Enable */ FRAM_PORT.OUTSET = FRAM_CS; asm volatile("nop"); asm volatile("nop"); FRAM_PORT.OUTCLR = FRAM_CS; SPITransferByte(0x02); /* Write command */ SPITransferByte((Address >> 8) & 0xFF); /* Address hi and lo byte */ SPITransferByte((Address >> 0) & 0xFF); SPIWriteBlock(Buffer, ByteCount); FRAM_PORT.OUTSET = FRAM_CS; } INLINE void FlashRead(void *Buffer, uint32_t Address, uint16_t ByteCount) { uint8_t *BufPtr = (uint8_t *) Buffer; /* We assume that ByteCount is a multiple of 2 */ uint32_t PhysicalAddress = Address + FLASH_DATA_ADDR; if ((PhysicalAddress >= FLASH_DATA_START) && (PhysicalAddress <= FLASH_DATA_END)) { /* Sanity check to limit access to the allocated area */ while (ByteCount > 1) { uint16_t Word = FlashReadWord(PhysicalAddress); *BufPtr++ = (Word >> 0) & 0xFF; *BufPtr++ = (Word >> 8) & 0xFF; PhysicalAddress += 2; ByteCount -= 2; } } } INLINE void FlashWrite(const void *Buffer, uint32_t Address, uint16_t ByteCount) { const uint8_t *BufPtr = (uint8_t *) Buffer; /* We assume that FlashWrite is always called for write actions that are * aligned to APP_SECTION_PAGE_SIZE and a multiple of APP_SECTION_PAGE_SIZE. * Thus only full pages are written into the flash. */ uint16_t PageCount = ByteCount / APP_SECTION_PAGE_SIZE; uint32_t PhysicalAddress = Address + FLASH_DATA_ADDR; if ((PhysicalAddress >= FLASH_DATA_START) && (PhysicalAddress <= FLASH_DATA_END)) { /* Sanity check to limit access to the allocated area */ while (PageCount-- > 0) { /* For each page to program, wait for NVM to get ready, * erase the flash page buffer, program all data to the * flash page buffer and write buffer to flash using * the atomic erase and write operation. */ FlashWaitForSPM(); FlashEraseFlashBuffer(); FlashWaitForSPM(); for (uint16_t i = 0; i < APP_SECTION_PAGE_SIZE; i += 2) { uint16_t Word = 0; Word |= ((uint16_t) * BufPtr++ << 0); Word |= ((uint16_t) * BufPtr++ << 8); FlashLoadFlashWord(i, Word); FlashWaitForSPM(); } FlashEraseWriteApplicationPage(PhysicalAddress); FlashWaitForSPM(); PhysicalAddress += APP_SECTION_PAGE_SIZE; } } } INLINE void FlashErase(uint32_t Address, uint16_t ByteCount) { uint16_t PageCount = ByteCount / APP_SECTION_PAGE_SIZE; uint32_t PhysicalAddress = Address + FLASH_DATA_ADDR; if ((PhysicalAddress >= FLASH_DATA_START) && (PhysicalAddress <= FLASH_DATA_END)) { /* Sanity check to limit access to the allocated area */ while (PageCount-- > 0) { FlashWaitForSPM(); FlashEraseApplicationPage(PhysicalAddress); FlashWaitForSPM(); PhysicalAddress += APP_SECTION_PAGE_SIZE; } } } INLINE void FlashToFRAM(uint32_t Address, uint16_t ByteCount) { /* We assume that ByteCount is a multiple of 2 */ uint32_t PhysicalAddress = Address + FLASH_DATA_ADDR; if ((PhysicalAddress >= FLASH_DATA_START) && (PhysicalAddress <= FLASH_DATA_END)) { /* Sanity check to limit access to the allocated area. * Set up FRAM memory for writing. */ FRAM_PORT.OUTCLR = FRAM_CS; SPITransferByte(0x06); /* Write Enable */ FRAM_PORT.OUTSET = FRAM_CS; asm volatile("nop"); asm volatile("nop"); FRAM_PORT.OUTCLR = FRAM_CS; SPITransferByte(0x02); /* Write command */ SPITransferByte(0); /* Address hi and lo byte */ SPITransferByte(0); /* Loop through bytes, read words from flash and write * double byte into FRAM. */ while (ByteCount > 1) { uint16_t Word = FlashReadWord(PhysicalAddress); SPITransferByte((Word >> 0) & 0xFF); SPITransferByte((Word >> 8) & 0xFF); PhysicalAddress += 2; ByteCount -= 2; } /* End write procedure of FRAM */ FRAM_PORT.OUTSET = FRAM_CS; } } INLINE void FRAMToFlash(uint32_t Address, uint16_t ByteCount) { /* We assume that FlashWrite is always called for write actions that are * aligned to APP_SECTION_PAGE_SIZE and a multiple of APP_SECTION_PAGE_SIZE. * Thus only full pages are written into the flash. */ uint16_t PageCount = ByteCount / APP_SECTION_PAGE_SIZE; uint32_t PhysicalAddress = Address + FLASH_DATA_ADDR; if ((PhysicalAddress >= FLASH_DATA_START) && (PhysicalAddress <= FLASH_DATA_END)) { /* Sanity check to limit access to the allocated area and setup FRAM * read. */ FRAM_PORT.OUTCLR = FRAM_CS; SPITransferByte(0x03); /* Read command */ SPITransferByte(0); /* Address hi and lo byte */ SPITransferByte(0); while (PageCount-- > 0) { /* For each page to program, wait for NVM to get ready, * erase the flash page buffer, program all data to the * flash page buffer and write buffer to flash using * the atomic erase and write operation. */ FlashWaitForSPM(); FlashEraseFlashBuffer(); FlashWaitForSPM(); /* Write one page worth of data into flash buffer */ for (uint16_t i = 0; i < APP_SECTION_PAGE_SIZE; i += 2) { uint16_t Word = 0; Word |= ((uint16_t) SPITransferByte(0) << 0); Word |= ((uint16_t) SPITransferByte(0) << 8); FlashLoadFlashWord(i, Word); FlashWaitForSPM(); } /* Program flash buffer into flash */ FlashEraseWriteApplicationPage(PhysicalAddress); FlashWaitForSPM(); PhysicalAddress += APP_SECTION_PAGE_SIZE; } /* End read procedure of FRAM */ FRAM_PORT.OUTSET = FRAM_CS; } } void MemoryInit(void) { /* Configure FRAM_USART for SPI master mode 0 with maximum clock frequency */ FRAM_PORT.OUTSET = FRAM_CS; FRAM_PORT.OUTCLR = FRAM_SCK; FRAM_PORT.OUTSET = FRAM_MOSI; FRAM_PORT.DIRSET = FRAM_SCK | FRAM_MOSI | FRAM_CS; FRAM_USART.BAUDCTRLA = 0; FRAM_USART.BAUDCTRLB = 0; FRAM_USART.CTRLC = USART_CMODE_MSPI_gc; FRAM_USART.CTRLB = USART_RXEN_bm | USART_TXEN_bm; /* Init DMAs for reading and writing */ RECV_DMA.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_FIXED_gc | DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_FIXED_gc; RECV_DMA.TRIGSRC = DMA_CH_TRIGSRC_USARTD0_RXC_gc; RECV_DMA.TRFCNT = 0; RECV_DMA.SRCADDR0 = ((uintptr_t) &FRAM_USART.DATA >> 0) & 0xFF; RECV_DMA.SRCADDR1 = ((uintptr_t) &FRAM_USART.DATA >> 8) & 0xFF; RECV_DMA.SRCADDR2 = 0; RECV_DMA.DESTADDR0 = 0; RECV_DMA.DESTADDR1 = 0; RECV_DMA.DESTADDR2 = 0; RECV_DMA.CTRLA = DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc; SEND_DMA.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_FIXED_gc | DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_FIXED_gc; SEND_DMA.TRIGSRC = DMA_CH_TRIGSRC_USARTD0_DRE_gc; SEND_DMA.TRFCNT = 0; SEND_DMA.SRCADDR0 = 0; SEND_DMA.SRCADDR1 = 0; SEND_DMA.SRCADDR2 = 0; SEND_DMA.DESTADDR0 = ((uintptr_t) &FRAM_USART.DATA >> 0) & 0xFF; SEND_DMA.DESTADDR1 = ((uintptr_t) &FRAM_USART.DATA >> 8) & 0xFF; SEND_DMA.DESTADDR2 = 0; SEND_DMA.CTRLA = DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc; } void MemoryReadBlock(void *Buffer, uint16_t Address, uint16_t ByteCount) { if (ByteCount == 0) return; FRAMRead(Buffer, Address, ByteCount); } void MemoryWriteBlock(const void *Buffer, uint16_t Address, uint16_t ByteCount) { if (ByteCount == 0) return; FRAMWrite(Buffer, Address, ByteCount); LEDHook(LED_MEMORY_CHANGED, LED_ON); } void MemoryClear(void) { FlashErase((uint32_t) GlobalSettings.ActiveSettingIdx * MEMORY_SIZE_PER_SETTING, MEMORY_SIZE_PER_SETTING); MemoryRecall(); } void MemoryRecall(void) { /* Recall memory from permanent flash */ FlashToFRAM((uint32_t) GlobalSettings.ActiveSettingIdx * MEMORY_SIZE_PER_SETTING, MEMORY_SIZE_PER_SETTING); SystemTickClearFlag(); } void MemoryStore(void) { /* Store current memory into permanent flash */ FRAMToFlash((uint32_t) GlobalSettings.ActiveSettingIdx * MEMORY_SIZE_PER_SETTING, MEMORY_SIZE_PER_SETTING); LEDHook(LED_MEMORY_CHANGED, LED_OFF); LEDHook(LED_MEMORY_STORED, LED_PULSE); SystemTickClearFlag(); } bool MemoryUploadBlock(void *Buffer, uint32_t BlockAddress, uint16_t ByteCount) { if (BlockAddress >= MEMORY_SIZE_PER_SETTING) { /* Prevent writing out of bounds by silently ignoring it */ return true; } else { /* Calculate bytes left in memory and start writing */ uint32_t BytesLeft = MEMORY_SIZE_PER_SETTING - BlockAddress; ByteCount = MIN(ByteCount, BytesLeft); /* Store to local memory */ FRAMWrite(Buffer, BlockAddress, ByteCount); return true; } } bool MemoryDownloadBlock(void *Buffer, uint32_t BlockAddress, uint16_t ByteCount) { if (BlockAddress >= MEMORY_SIZE_PER_SETTING) { /* There are bytes out of bounds to be read. Notify that we are done. */ return false; } else { /* Calculate bytes left in memory and issue reading */ uint32_t BytesLeft = MEMORY_SIZE_PER_SETTING - BlockAddress; ByteCount = MIN(ByteCount, BytesLeft); /* Output local memory contents */ FRAMRead(Buffer, BlockAddress, ByteCount); return true; } } // EEPROM functions static inline void NVM_EXEC(void) { void *z = (void *)&NVM_CTRLA; __asm__ volatile("out %[ccp], %[ioreg]" "\n\t" "st z, %[cmdex]" : : [ccp] "I"(_SFR_IO_ADDR(CCP)), [ioreg] "d"(CCP_IOREG_gc), [cmdex] "r"(NVM_CMDEX_bm), [z] "z"(z) ); } void WaitForNVM(void) { while (NVM.STATUS & NVM_NVMBUSY_bm) { }; } void FlushNVMBuffer(void) { WaitForNVM(); if ((NVM.STATUS & NVM_EELOAD_bm) != 0) { NVM.CMD = NVM_CMD_ERASE_EEPROM_BUFFER_gc; NVM_EXEC(); } } uint16_t ReadEEPBlock(uint16_t Address, void *DestPtr, uint16_t ByteCount) { uint16_t BytesRead = 0; uint8_t *BytePtr = (uint8_t *) DestPtr; NVM.ADDR2 = 0; WaitForNVM(); while (ByteCount > 0) { NVM.ADDR0 = Address & 0xFF; NVM.ADDR1 = (Address >> 8) & 0x1F; NVM.CMD = NVM_CMD_READ_EEPROM_gc; NVM_EXEC(); *BytePtr++ = NVM.DATA0; Address++; ByteCount--; BytesRead++; } return BytesRead; } uint16_t WriteEEPBlock(uint16_t Address, const void *SrcPtr, uint16_t ByteCount) { const uint8_t *BytePtr = (const uint8_t *) SrcPtr; uint8_t ByteAddress = Address % EEPROM_PAGE_SIZE; uint16_t PageAddress = Address - ByteAddress; uint16_t BytesWritten = 0; FlushNVMBuffer(); WaitForNVM(); NVM.CMD = NVM_CMD_LOAD_EEPROM_BUFFER_gc; NVM.ADDR1 = 0; NVM.ADDR2 = 0; while (ByteCount > 0) { NVM.ADDR0 = ByteAddress; NVM.DATA0 = *BytePtr++; ByteAddress++; ByteCount--; if (ByteCount == 0 || ByteAddress >= EEPROM_PAGE_SIZE) { NVM.ADDR0 = PageAddress & 0xFF; NVM.ADDR1 = (PageAddress >> 8) & 0x1F; NVM.CMD = NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc; NVM_EXEC(); PageAddress += EEPROM_PAGE_SIZE; ByteAddress = 0; WaitForNVM(); NVM.CMD = NVM_CMD_LOAD_EEPROM_BUFFER_gc; } BytesWritten++; } return BytesWritten; }