/* * SPI Flash library * Contains functions to interact with the Winbond W25Q128 SPI Flash chip */ // Sets SPI0_CS low void spiflash_begin_transfer() { asm( "; Backup regs\n" "push r1\n" "push r2\n" "load32 0xC02729 r2 ; r2 = 0xC02729\n" "load 0 r1 ; r1 = 0 (enable)\n" "write 0 r2 r1 ; write to SPI0_CS\n" "; Restore regs\n" "pop r2\n" "pop r1\n" ); } // Sets SPI0_CS high void spiflash_end_transfer() { asm( "; Backup regs\n" "push r1\n" "push r2\n" "load32 0xC02729 r2 ; r2 = 0xC02729\n" "load 1 r1 ; r1 = 1 (disable)\n" "write 0 r2 r1 ; write to SPI0_CS\n" "; Restore regs\n" "pop r2\n" "pop r1\n" ); } // Write dataByte and return read value // Write 0x00 for a read // Writes byte over SPI0 word spiflash_transfer(word dataByte) { word retval = 0; asm( "load32 0xC02728 r2 ; r2 = 0xC02728\n" "write 0 r2 r4 ; write r4 over SPI0\n" "read 0 r2 r2 ; read return value\n" "write -4 r14 r2 ; write to stack to return\n" ); return retval; } // Initialize manual operation of SPI flash by enabling SPI0 and resetting the chip // This is needed to get out of continuous read mode and allow manual commands void spiflash_init() { // Already set CS high before enabling SPI0 spiflash_end_transfer(); // Enable SPI0 (connects simpleSPI.v and disconnects SPIreader.v) word *p = (word *) 0xC0272A; // Set address (SPI0 enable) *p = 1; // Write value delay(10); // Reset to get out of continuous read mode spiflash_begin_transfer(); spiflash_transfer(0x66); spiflash_end_transfer(); delay(1); spiflash_begin_transfer(); spiflash_transfer(0x99); spiflash_end_transfer(); delay(1); spiflash_begin_transfer(); spiflash_transfer(0x66); spiflash_end_transfer(); delay(1); spiflash_begin_transfer(); spiflash_transfer(0x99); spiflash_end_transfer(); delay(1); } // Reads manufacturer and device IDs and prints them over UART // Should print 239 as Winbond manufacturer, and 23 for W25Q128 device ID void spiflash_read_chip_ids() { spiflash_begin_transfer(); spiflash_transfer(0x90); spiflash_transfer(0); spiflash_transfer(0); spiflash_transfer(0); word manufacturer = spiflash_transfer(0); word deviceID = spiflash_transfer(0); spiflash_end_transfer(); uprint("Manufacturer ID: "); uprintDec(manufacturer); uprint("\n"); uprint("Device ID: "); uprintDec(deviceID); uprint("\n"); } // Enables write operation on the chip void spiflash_enable_write() { spiflash_begin_transfer(); spiflash_transfer(0x06); spiflash_end_transfer(); } // Reads len bytes from addr and stores them in output // If bytes_to_word is 1, then each 4 bytes are shifted into one word in output void spiflash_read_from_address(word* output, word addr, word len, word bytes_to_word) { spiflash_begin_transfer(); spiflash_transfer(0x03); spiflash_transfer(addr >> 16); spiflash_transfer(addr >> 8); spiflash_transfer(addr); if (bytes_to_word) { len = len << 2; word i; for (i = 0; i < len; i++) { if ((i & 3) == 0) { output[i >> 2] = 0; } output[i >> 2] |= spiflash_transfer(0) << (24 - ((i & 3) << 3)); } } else { word i; for (i = 0; i < len; i++) { output[i] = spiflash_transfer(0); } } spiflash_end_transfer(); } // Writes up to 64 words as 256 bytes to the given address // Address must be aligned to a page boundary void spiflash_write_page_in_words(word* input, word addr, word len) { // Check if length exceeds page size if (len > 64) { uprintln("Error: cannot write more than a page!"); return; } // Check if address is aligned to a page boundary if (addr & 0x3F) { uprintln("Error: address must be aligned to a page boundary!"); return; } spiflash_enable_write(); word status = spiflash_read_status_reg(1); // Check if write is enabled if ((status & 0x2) == 0) { uprintln("WE disabled!"); return; } spiflash_begin_transfer(); spiflash_transfer(0x02); spiflash_transfer(addr >> 16); spiflash_transfer(addr >> 8); spiflash_transfer(addr); word i; for (i = 0; i < len; i++) { spiflash_transfer(input[i]) >> 24; spiflash_transfer(input[i]) >> 16; spiflash_transfer(input[i]) >> 8; spiflash_transfer(input[i]); } spiflash_end_transfer(); // Wait for busy bit to be 0 status = 1; while((status & 0x1) == 1) { status = spiflash_read_status_reg(1); } } // Reads status register 1, 2, or 3 word spiflash_read_status_reg(word reg_idx) { if (reg_idx < 1 || reg_idx > 3) return 0; spiflash_begin_transfer(); word command; switch (reg_idx) { case 1: command = 0x05; break; case 2: command = 0x35; break; case 3: command = 0x15; break; } spiflash_transfer(command); word status = spiflash_transfer(0); spiflash_end_transfer(); return status; } // Executes sector erase operation // Erases the 4KiB sector of the given address // (e.g. addr 4096 erases the second sector) // This is the smallest unit that can be erased // Returns 1 on success word spiflash_sector_erase(word addr) { spiflash_enable_write(); word status = spiflash_read_status_reg(1); // Check if write is enabled if ((status & 0x2) == 0) { uprintln("WE disabled!"); return 0; } // Send command spiflash_begin_transfer(); spiflash_transfer(0x20); spiflash_transfer(addr >> 16); spiflash_transfer(addr >> 8); spiflash_transfer(addr); spiflash_end_transfer(); // Wait for busy bit to be 0 status = 1; while((status & 0x1) == 1) { status = spiflash_read_status_reg(1); } return 1; } // Executes chip erase operation // TAKES AT LEAST 20 SECONDS! // Returns 1 on success word spiflash_chip_erase() { spiflash_enable_write(); word status = spiflash_read_status_reg(1); // Check if write is enabled if ((status & 0x2) == 0) { uprintln("WE disabled!"); return 0; } // Send erase chip command spiflash_begin_transfer(); spiflash_transfer(0xC7); spiflash_end_transfer(); // Wait for busy bit to be 0 status = 1; while((status & 0x1) == 1) { delay(100); status = spiflash_read_status_reg(1); } return 1; }