#include "compiler.h" #include "common.h" #include "console.h" #include "io.h" #include "spiflash.h" #include "ff.h" #include "boardinfo_fpga.h" #include #define SPIROM_DUAL_MODE 1 struct board_info board_info; __dram_noinit union board_info_block board_info_raw; static __dram_noinit union board_info_block board_info_rom; volatile bool do_update_boardinfo; static void boardinfo_init(void); static void __hot romcopy_download_absolute(void *dst, size_t offset, size_t len) { unsigned int cmd; unsigned int flags = ROMCOPY_SPI_CMDLEN(5) | ROMCOPY_WRITE_RAM; if (!len) return; if (SPIROM_DUAL_MODE) { cmd = ROM_FAST_READ_DUAL; flags |= ROMCOPY_SPI_DUAL; } else { cmd = ROM_FAST_READ; } ROMCOPY_RAMADDR = (size_t)dst; ROMCOPY_ROMCMD = offset + (cmd << 24); ROMCOPY_DATALEN = len | flags; } void __hot romcopy_download(void *dst, size_t offset, size_t len) { romcopy_download_absolute(dst, __rom_offset + offset, len); } void __hot romcopy_bzero(void *dst, size_t len) { if (!len) return; ROMCOPY_RAMADDR = (size_t)dst; ROMCOPY_ROMCMD = 0; ROMCOPY_DATALEN = len | ROMCOPY_ZERO_BUFFER | ROMCOPY_WRITE_RAM; } static void __hot romcopy_download_boardinfo(void *dst) { romcopy_download_absolute(dst, BOARDINFO_ADDR, BOARDINFO_SIZE); } /* * Convert the MAC address in board_info_raw into a hexadecimal string * and set it as the USB serial number. */ static inline void rom_mangle_serial(void) { volatile uint32_t *udp = &usbdesc_rom[2]; const uint8_t *mac = board_info.mac[0]; for (int i = 0; i < 6; i++) { unsigned int v = *mac++; unsigned int c; c = (v >> 4)+'0'; if (c > '9') c += 'A'-'9'-1; udp[0] = c; c = (v & 15)+'0'; if (c > '9') c += 'A'-'9'-1; udp[2] = c; /* Skip dash between third and fourth byte */ udp += (i == 2) ? 6 : 4; } } static inline __hot void romcopy_config_flash(void) { /* Enable writing volatile status register bits */ ROMCOPY_ROMCMD = ROM_VOLATILE_SR_WRITE_ENABLE << 24; ROMCOPY_DATALEN = ROMCOPY_SPI_CMDLEN(1); waitfor(ROMCOPY_IRQ); /* Write SR3 = 0; this sets the drive strength to maximum */ ROMCOPY_ROMCMD = ROM_WRITE_SR3 << 24; ROMCOPY_DATALEN = ROMCOPY_SPI_CMDLEN(2); waitfor(ROMCOPY_IRQ); } IRQHANDLER(romcopy,0) { static __sbss unsigned int romcopy_state; size_t len; switch (romcopy_state++) { case 0: /* Clear esplink as quickly as possible */ len = __esplink_end - __esplink_start; romcopy_bzero(__esplink_start, len); break; case 1: /* Condition flash ROM */ romcopy_config_flash(); /* Start copy DRAM data */ len = __dram_init_end - __dram_init_start; romcopy_download(__dram_init_start, 0, len); break; case 2: /* Copy board_info from ROM into board_info_raw */ romcopy_download_boardinfo(&board_info_raw); break; case 3: /* Copy reference copy of board_info ROM block into board_info_rom */ romcopy_download_boardinfo(&board_info_rom); /* Create sanitized board_info structure */ boardinfo_init(); break; case 4: /* Zero .dram.bss */ len = __dram_bss_end - __dram_bss_start; romcopy_bzero(__dram_bss_start, len); break; default: mask_irq(ROMCOPY_IRQ); break; } } /* * SPI flash parameters and routines (for spiflash.c) */ static const struct spiflash_ops max80_spiflash_ops; static const struct spiflash_param max80_spiflash_param; /* * SPI flash operations */ static int max80_spi_write_buffer(const void *buf, unsigned int buflen, bool more) { const uint8_t *p = buf; uint32_t cmd = 0; unsigned int bytecmd = 0; unsigned int bitpos = 24; while (buflen) { cmd |= *p++ << bitpos; buflen--; bitpos -= 8; bytecmd += ROMCOPY_SPI_CMDLEN(1); if (!(buflen & 3)) { if (buflen || more) bytecmd |= ROMCOPY_SPI_MORE; waitfor(ROMCOPY_IRQ); ROMCOPY_ROMCMD = cmd; ROMCOPY_DATALEN = bytecmd; bytecmd = cmd = 0; bitpos = 24; } } return 0; } static int max80_spi_read_buffer(void *buf, unsigned int buflen, bool more) { uint8_t *p = buf; unsigned int bytecmd; uint32_t v; if (!buflen) return 0; waitfor(ROMCOPY_IRQ); ROMCOPY_ROMCMD = 0; while (buflen > 4) { ROMCOPY_DATALEN = ROMCOPY_SPI_CMDLEN(4) | ROMCOPY_SPI_MORE; waitfor(ROMCOPY_IRQ); v = ROMCOPY_INPUT; p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v; p += 4; buflen -= 4; } bytecmd = ROMCOPY_SPI_CMDLEN(buflen); if (more) bytecmd |= ROMCOPY_SPI_MORE; ROMCOPY_DATALEN = bytecmd; waitfor(ROMCOPY_IRQ); v = ROMCOPY_INPUT; while (buflen) { p[--buflen] = v; v >>= 8; } return 0; } static int max80_spi_write(void *cookie, const void *cmd, unsigned int cmd_len, const void *data, unsigned int data_len, int tshsl) { (void)cookie; (void)tshsl; /* Enforced in hardware */ waitfor(ROMCOPY_IRQ); udelay(1); if (cmd_len) max80_spi_write_buffer(cmd, cmd_len, !!data_len); if (data_len) max80_spi_write_buffer(data, data_len, false); return 0; } static int max80_spi_read(void *cookie, const void *cmd, unsigned int cmd_len, void *data, unsigned int data_len, int tshsl) { (void)cookie; (void)tshsl; /* Enforced in hardware */ waitfor(ROMCOPY_IRQ); udelay(1); if (cmd_len) max80_spi_write_buffer(cmd, cmd_len, !!data_len); if (data_len) max80_spi_read_buffer(data, data_len, false); return 0; } static const struct spiflash_ops max80_spiflash_ops = { .spi_write = max80_spi_write, .spi_read = max80_spi_read, .yield = NULL /* Nothing to yield to... */ }; /* Winbond W25Q128JV @ 3.3V */ #define NS(x) (((x)*(unsigned long long)CPU_HZ + 999999999ULL)/1000000000ULL) #define US(x) (((x)*(unsigned long long)CPU_HZ + 999999ULL)/1000000ULL) #define MS(x) (((x)*(unsigned long long)CPU_HZ + 999ULL)/1000ULL) static const struct spiflash_param max80_spiflash_param = { /* * For W25Q128JV _DYNAMIC is equivalent to _24BIT, but specifying * it as dynamic would most of the time support larger parts * transparently. */ .addr = SPIFLASH_ADDR_DYNAMIC, .tshsl = NS(3), .tshsl1 = NS(10), .tshsl2 = NS(50), .trst = NS(30000), .tw = MS(10), /* persistent; typ, max = 15 */ .tpp = NS(400), /* typ, max = 3000 */ .tse = MS(45), /* typ, max = 400 */ .tbe1 = MS(120), /* typ, max = 1600 */ .tbe2 = MS(150), /* typ, max = 2000 */ .tce = MS(40000), /* typ, max = 200000 */ }; static void rom_spiflash_init(struct spiflash *flash) { memset(flash, 0, sizeof *flash); flash->target = board_info.version_str; flash->param = &max80_spiflash_param; flash->ops = &max80_spiflash_ops; } /* * Flash an image into SPI flash, and reload the FPGA if successful, * returns on failure only. */ void rom_flash_from_memory(void *buf, size_t buflen) { struct spiflash max80_flash; rom_spiflash_init(&max80_flash); if (spiflash_flash_file(&max80_flash, buf, buflen)) { con_puts("update: flash update data invalid\n"); return; } /* Now do it for real */ max80_flash.ops = &max80_spiflash_ops; if (spiflash_flash_file(&max80_flash, buf, buflen)) { con_puts("update: flash update failed\n"); return; } con_puts("update: flash complete, restarting in 500 ms...\n"); udelay(500000); shutdown(SYS_RESET_RECONFIG); } static int rom_sdcard_read_data(void *cookie, void *buf, unsigned int bufsize) { unsigned int bytesread; FRESULT fr = f_read(cookie, buf, bufsize, &bytesread); return fr == FR_OK ? bytesread : 0; } /* * Flash an image from an SD card, and reload the FPGA if successful */ static int rom_flash_from_sdcard_file(const char *filename) { struct spiflash max80_flash; char *fw_file_name = NULL; FRESULT fr = FR_NO_FILE; FILINFO fno; FIL f; fr = f_stat(filename, &fno); if (fr != FR_OK || (fno.fattrib & AM_DIR)) return 0; /* No such firmware file found but no error */ con_printf("update: firmware update file %s found\n", filename); /* Rename the firmware file to avoid repeated updates */ size_t l = strlen(filename); fw_file_name = malloc(l+5); if (!fw_file_name) goto err; memcpy(fw_file_name, filename, l); for (unsigned int i = 1; i < 1000; i++) { snprintf(fw_file_name+l, 5, ".%03u", i); fr = f_rename(filename, fw_file_name); if (fr != FR_EXIST) break; } if (fr != FR_OK) { con_puts("update: unable to rename update file, skipping\n"); goto err; } con_printf("update: renamed %s -> %s\n", filename, fw_file_name); rom_spiflash_init(&max80_flash); max80_flash.read_data = rom_sdcard_read_data; max80_flash.cookie = &f; fr = f_open(&f, fw_file_name, FA_READ); if (fr != FR_OK) { con_puts("update: cannot open file, terminating\n"); goto err; } if (spiflash_flash_file(&max80_flash, NULL, 0)) { con_puts("update: flash update data invalid\n"); goto err; } /* Now do it for real */ f_rewind(&f); max80_flash.ops = &max80_spiflash_ops; if (spiflash_flash_file(&max80_flash, NULL, 0)) { con_puts("update: flash update failed\n"); goto err; } f_close(&f); con_puts("update: flash complete, restarting in 500 ms...\n"); udelay(500000); shutdown(SYS_RESET_RECONFIG); err: if (fr == FR_OK) f_close(&f); if (fw_file_name) free(fw_file_name); return -1; } /* * Check for a firmware upgrade file on the sd card */ void rom_flash_from_sdcard(void) { static char versioned_filename[] = "/max80/v?.fw"; /* Look for version-specific firmware file */ versioned_filename[8] = SYS_BOARDFPGA + '0'; rom_flash_from_sdcard_file(versioned_filename); /* Look for universal firmware file */ rom_flash_from_sdcard_file("/max80/max80.fw"); } static bool boardinfo_valid(const struct board_info *bi) { size_t len = bi->len; /* XXX: check CRC */ return len >= 16 && len <= BOARDINFO_SIZE && bi->magic[0] == BOARDINFO_MAGIC_1 && bi->magic[1] == BOARDINFO_MAGIC_2; } /* * Initialize the board_info structure from board_info_raw */ static void boardinfo_init(void) { const struct board_info *src = &board_info_raw.i; size_t len = src->len; if (!boardinfo_valid(src)) len = 0; /* Bad structure */ size_t rlen = Min(len, sizeof board_info); memcpy(&board_info, src, rlen); /* Erase any fields not included in ROM */ if (rlen < len) memset((char *)&board_info + rlen, 0, sizeof board_info - rlen); /* * If no board version is specified, use the FPGA version as a * resonable guess. This might be useful if the FPGA firmware has * been uploaded via JTAG. */ if (!board_info.version_str[0]) { snprintf(board_info.version_str, sizeof board_info.version_str, "MAX80 v%u", SYS_BOARDFPGA); } /* Convert serial number and export to USB */ rom_mangle_serial(); } /* * Update board_info if board_info_raw has changed, and write it to flash * in that case. */ void rom_update_boardinfo(void) { struct spiflash max80_flash; union board_info_block *src = &board_info_raw; do_update_boardinfo = false; if (!boardinfo_valid(&src->i)) { con_printf("rom: bad board_info received\n"); return; } if (!memcmp(&board_info_rom, src, src->i.len)) { con_printf("rom: board_info in flash unchanged\n"); return; } con_printf("rom: updating board_info in flash\n"); memset(&src->b[src->i.len], 0xff, BOARDINFO_SIZE - src->i.len); rom_spiflash_init(&max80_flash); spiflash_flash_data(&max80_flash, BOARDINFO_ADDR, src, BOARDINFO_SIZE); romcopy_download_boardinfo(&board_info_rom); boardinfo_init(); waitfor(ROMCOPY_IRQ); }