diff --git a/Makefile b/Makefile index ea964ce..ea16e92 100644 --- a/Makefile +++ b/Makefile @@ -21,8 +21,8 @@ ifeq ($(CONFIG_ENABLE_TESTER),1) FILES += tester.asm endif -ifneq ($(CONFIG_UART_AS_STDOUT),1) - FILES += video.asm keyboard.asm +ifeq ($(CONFIG_ENABLE_VIDEO_BOARD),1) + FILES += video.asm video_board.asm keyboard.asm stdout.asm endif define FIND_ADDRESS = diff --git a/include/config.asm b/include/config.asm index a25ab1b..88f6fe2 100644 --- a/include/config.asm +++ b/include/config.asm @@ -5,9 +5,10 @@ IFNDEF CONFIG_H DEFINE CONFIG_H -; When set, the UART will be used as the standard output. When not set or set to 0, the video driver will -; be used. The UART driver will still be used to receive files. -DEFC CONFIG_UART_AS_STDOUT = 1 +; When set, the video board will be used as the default standard output. In that case, +; the UART will still be used for receiving files and sending some info messages. +; If not set, the UART driver will the standard output. +DEFC CONFIG_ENABLE_VIDEO_BOARD = 1 ; When set, the Zeal logo will be shown on screen, this takes more RAM at runtime but won't affect the ; OS or program running after the bootloader as the modified tiles are saved and restored. diff --git a/include/stdout_h.asm b/include/stdout_h.asm index 07cb928..62c7b03 100644 --- a/include/stdout_h.asm +++ b/include/stdout_h.asm @@ -8,7 +8,7 @@ DEFINE STDOUT_H ; It is possible to choose either the UART or video board as the standard output - IF CONFIG_UART_AS_STDOUT + IF !CONFIG_ENABLE_VIDEO_BOARD INCLUDE "uart_h.asm" DEFC stdout_initialize = uart_initialize @@ -35,21 +35,32 @@ DEFM 0x1b, "[0m" ENDM - ELSE ; !CONFIG_UART_AS_STDOUT + ELSE ; CONFIG_ENABLE_VIDEO_BOARD + ; In case we have the video board, it may be possible to switch back to + ; to the UART as standard output if the video board fails to boot. INCLUDE "video_h.asm" INCLUDE "keyboard_h.asm" + INCLUDE "uart_h.asm" - DEFC stdout_initialize = video_initialize + DEFC STDOUT_DRIVER_UART = 0 + DEFC STDOUT_DRIVER_VIDEO = 1 + + ; When the video board is enabled, we can fallback the standard input to UART + ; Parameters: + ; A - New driver to select, STDOUT_DRIVER_UART OR STDOUT_DRIVER_VIDEO + EXTERN std_set_driver + EXTERN stdout_initialize + EXTERN stdout_write + EXTERN stdout_put_char + EXTERN stdout_newline DEFC stdout_autoboot = video_autoboot - DEFC stdout_write = video_write - DEFC stdout_put_char = video_put_char - DEFC stdout_newline = video_newline - DEFC stdin_get_char = keyboard_next_char - DEFC stdin_has_char = keyboard_has_char - DEFC stdin_set_synchronous = keyboard_set_synchronous DEFC stdout_prepare_menu = video_clear_screen + EXTERN stdin_get_char + EXTERN stdin_has_char + EXTERN stdin_set_synchronous + ; Use a prefix for the colors MACRO YELLOW_COLOR _ DEFM 0xFE diff --git a/src/boot.asm b/src/boot.asm index 4e59857..38b72f8 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -36,10 +36,12 @@ bootloader_entry: ldir call sys_table_init - call stdout_initialize ; UART will be used to receive files in all cases call uart_initialize call pio_initialize + IF CONFIG_ENABLE_VIDEO_BOARD + call stdout_initialize + ENDIF ; CONFIG_ENABLE_VIDEO_BOARD ; Ready to send and receive data over UART, show welcome message ld hl, start_message diff --git a/src/keyboard.asm b/src/keyboard.asm index 84aa15b..7ba7c0d 100644 --- a/src/keyboard.asm +++ b/src/keyboard.asm @@ -9,68 +9,99 @@ SECTION BOOTLOADER - ; Receive a character from the keyboard (synchronous, blocking) - ; Parameters: - ; None + ; Receive a character from the keyboard, non-blocking version ; Returns: - ; A - ASCII byte received + ; A - 0 if no valid character was received, character value else ; Alters: - ; A, B, DE - PUBLIC keyboard_next_char -keyboard_next_char: - xor a - ld (received), a - - PUBLIC keyboard_get_char -keyboard_get_char: - ld de, received - -_keyboard_get_char_loop: - ld a, (de) + ; A, B, HL, DE + PUBLIC keyboard_get_char_nonblocking +keyboard_get_char_nonblocking: + ld hl, received + ld b, 0 + ; Atomic test and set + di + ld a, (hl) + ld (hl), b + ei or a - jr z, _keyboard_get_char_loop - ; Check if it's release key + ret z + ; Make HL point to the 'flag' variable + inc hl + ; Set a flag if release key cp KB_RELEASE_SCAN - jr z, _release_scan - ; Character is not a "release command" - ; Check if the character is a printable char - push hl + jr z, _release_scan_received + ; Check if we received a release scan previously + ld b, (hl) + ; Check if flag is 0 + inc b + dec b + jr nz, _flag_not_zero + ; Flag is not 0, we just received a real character, parse it + ; Check if it is a printable character cp KB_PRINTABLE_CNT - 1 jp nc, _special_code ; jp nc <=> A >= KB_PRINTABLE_CNT - 1 ; Save the scan size in B ld hl, base_scan ld b, base_scan_end - base_scan - jr get_from_scan + jr _get_from_scan _special_code: ; Special character is still in A - cp KB_EXTENDED_SCAN - jr z, _pop_ret - add -KB_SPECIAL_START + sub KB_EXTENDED_SCAN + ; Ignore extended scan characters for now, return 0 + ret z + ; We just subtracted KB_EXTENDED_SCAN, add it back + add KB_EXTENDED_SCAN-KB_SPECIAL_START ld hl, special_scan ld b, special_scan_end - special_scan -get_from_scan: +_get_from_scan: ; Check if there would be an overflow cp b - ; If there no carry, A is equal or bigger than the length - jr nc, _overflow_pop_continue + ; If there no carry, A is equal or bigger than the length, consider this + ; an invalid character, return 0 + jr nc, _overflow_ret_zero + ; Get the value of A from the scan table pointed by HL add l ld l, a adc h sub l ld h, a ld a, (hl) -_pop_ret: - pop hl ret -_overflow_pop_continue: - pop hl - jr _keyboard_get_char_loop +_overflow_ret_zero: + xor a + ret +_flag_not_zero: + ; Clear the flag and ignore the current character, return 0 + xor a + ; Fall-through +_release_scan_received: + ; Set the flag to a non-zero value + ld (hl), a + ; Return not-valid character + xor a + ret + -_release_scan: - ; Ignore the next character - call keyboard_next_char - ; Return the next character - jp keyboard_next_char + ; Receive a character from the keyboard (synchronous, blocking) + ; Parameters: + ; None + ; Returns: + ; A - ASCII byte received + ; Alters: + ; A, B, DE + PUBLIC keyboard_next_char +keyboard_next_char: + ; Clear the previously received keys + xor a + ld (received), a + push hl +_keyboard_next_char_loop: + call keyboard_get_char_nonblocking + or a + ; If character is invalid, continue the loop + jr z, _keyboard_next_char_loop + pop hl + ret base_scan: DEFB 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\t', '`', 0 @@ -120,4 +151,5 @@ keyboard_int_handler: SECTION BSS -received: DEFS 1 \ No newline at end of file +received: DEFS 1 +flag: DEFS 1 \ No newline at end of file diff --git a/src/menu.asm b/src/menu.asm index 80ca8f3..f5894ae 100644 --- a/src/menu.asm +++ b/src/menu.asm @@ -13,6 +13,8 @@ EXTERN newline EXTERN test_hardware EXTERN format_eeprom + EXTERN video_board_flash + EXTERN video_wait_recovery SECTION BOOTLOADER @@ -161,9 +163,9 @@ print_bc_hex: ld a, c jp print_a_hex - ; Print in hex the 8-bit value from A on the UART + ; Print, in hex, the 8-bit value in register A ; Parameters: - ; A - Value to print on the UART + ; A - Value to print on the standard output ; Returns: ; None ; Alters: @@ -219,10 +221,12 @@ process_menu_choice: jp z, menu_delete_entry cp 's' jp z, menu_save_new_entry - IF CONFIG_UART_AS_STDOUT cp 'b' jp z, menu_change_baudrate - ENDIF + IF CONFIG_ENABLE_VIDEO_BOARD + cp 'v' + jp z, menu_flash_video_board + ENDIF ; CONFIG_ENABLE_VIDEO_BOARD cp 'f' jp z, menu_flash_rom IF CONFIG_ENABLE_TESTER @@ -292,42 +296,11 @@ _menu_flash_rom_addr: ld e, d ld d, c push de -_menu_flash_ask_size: - ; Ask for the size to load now - PRINT_STR(binary_size_str) - ld hl, buffer - call menu_read_input - ld hl, buffer - call parse_hex +_menu_flash_ask_binary: + ; Receive the binary file (size will be asked) in RAM, at 0x80000 + call receive_binary or a - jr nz, _menu_flash_ask_size - ; Check that the size is not 0! - ld a, c - or d - or e - jr z, _menu_flash_ask_size - ; As we have 512KB of RAM, but 16KB are mapped currently for the bootloader, - ; make sure the given size doesn't exceed 512 - 16 = 496KB <=> CD <= 0x07c0 - ; Even though the flash is 256KB big, the hardware supports flash up to 512KB, - ; so keep the code like this for any NOR flash. - ld h, c - ld l, d - push de - ld de, 0x7c1 - xor a - sbc hl, de - pop de - jp nc, _menu_flash_ask_size - ; Print a message to send a file - push bc - push de - PRINT_STR(binary_send_str) - pop de - pop bc - ; Receive a file in RAM, at physical address 0x80000. - ; Parameters: - ; CDE - Size of the file to receive - call uart_receive_big_file + jr nz, _menu_flash_ask_binary ; Print a message saying flash is in progress push bc push de @@ -365,7 +338,115 @@ _menu_flash_rom_zero: ld d, a ld e, a push de - jr _menu_flash_ask_size + jr _menu_flash_ask_binary + + + IF CONFIG_ENABLE_VIDEO_BOARD + + ; Routine to flash the video board with a binary file. + ; This routine: + ; - Asks for a binary size on the standard input + ; - Asks for the recovery mode to be switched on + ; - Flashes the binary + ; - Waits for a hard reboot/power off + ; Routine called when "Flash the video board" is selected from the menu. + ; Parameters: + ; Z flag - This function is called from the menu, all the messages will be outputed. + ; NZ flag - This function is called from recovery, some messages won't be shown. + PUBLIC menu_flash_video_board +menu_flash_video_board: + push af + ; If called from recovery, do not show the "hexadecimal" warning notice + jr nz, _menu_flash_video_board_binary + PRINT_STR(new_entry_notice_str) + ; Ask for the size of the binary +_menu_flash_video_board_binary: + call receive_binary + or a + jr nz, _menu_flash_video_board_binary + ; Get back the flags from the stack but wait before testing them + pop af + push bc + push de + ; If we are already in the recovery, do not ask to switch + jr nz, _menu_flash_video_board_recovery + PRINT_STR(video_board_switch) + call video_wait_recovery +_menu_flash_video_board_recovery: + PRINT_STR_UART(flashing_flash_str) + pop de + pop bc + ; Flash the received file to the video board + ; Parameters: + ; CDE - Size of the data to flash + call video_board_flash + or a + ; Error if A is not 0 + jr z, _menu_flash_video_board_reboot + PRINT_STR_UART(flash_erase_error_str) +_menu_flash_video_board_reboot: + PRINT_STR_UART(video_board_power_off) +_menu_wait_reboot: + jr _menu_wait_reboot + + PUBLIC video_board_switch + PUBLIC video_board_switch_end +video_board_switch: DEFM "Press the recovery button on the video board to start flashing\r\n" +video_board_switch_end: +video_board_power_off: DEFM "Please power off the board\r\n" +video_board_power_off_end: + + ENDIF + + ; Helper routine to ask for a size between 1 and 496KB, print a message and receive a binary + ; Parameters: + ; None + ; Returns: + ; CDE - 24-bit parsed size + ; A - 0 on success, non-zero on failure + ; Alters: + ; A, BC, HL, DE, `buffer` +receive_binary: + PRINT_STR(binary_size_str) + ld hl, buffer + call menu_read_input + ld hl, buffer + call parse_hex + or a + ret nz + ; Check that the size is not 0! + ld a, c + or d + or e + jr z, _receive_binary_error + ; As we have 512KB of RAM, but 16KB are mapped currently for the bootloader, + ; make sure the given size doesn't exceed 512 - 16 = 496KB <=> CD <= 0x07c0 + ; Even though the flash is 256KB big, the hardware supports flash up to 512KB, + ; so keep the code like this for any NOR flash. + ld h, c + ld l, d + push de + ld de, 0x7c1 + xor a + sbc hl, de + pop de + jr nc, _receive_binary_error + ; Success, print a message and return 0 + ; Print a message to send a file + push bc + push de + PRINT_STR(binary_send_str) + pop de + pop bc + ; Receive a file in RAM, at physical address 0x80000. + ; Parameters: + ; CDE - Size of the file to receive + ; Returns: + ; A - 0 (always) + jp uart_receive_big_file +_receive_binary_error: + inc a + ret ; Routine called when "Change baudrate" is selected. This is useful even if the @@ -816,7 +897,7 @@ flash_erase_success_str: flash_erase_success_str_end: flash_erase_error_str: RED_COLOR() - DEFM "Error while writing to flash" + DEFM "Error while flashing binary" END_COLOR() flash_erase_error_str_end: system_table_full_str: @@ -878,12 +959,11 @@ advanced_msg: DEFM "a - Add a new entry\r\n" DEFM "d - Delete an existing entry\r\n" DEFM "s - Save configuration to flash\r\n" - IF CONFIG_UART_AS_STDOUT - DEFM "b - Change baudrate\r\n" - ELSE ; VIDEO as stdout - DEFM "b - Change UART baudrate (for sending files)\r\n" - ENDIF ; CONFIG_UART_AS_STDOUT + DEFM "b - Change UART baudrate\r\n" DEFM "f - Flash/Program the ROM\r\n" + IF CONFIG_ENABLE_VIDEO_BOARD + DEFM "v - Flash the video board\r\n" + ENDIF DEFM "q - Quick format I2C EEPROM (ZealFS)\r\n" IF CONFIG_ENABLE_TESTER DEFM "t - Test hardware\r\n" diff --git a/src/pio.asm b/src/pio.asm index 7c78823..e7b5b07 100644 --- a/src/pio.asm +++ b/src/pio.asm @@ -5,13 +5,13 @@ INCLUDE "config.asm" INCLUDE "pio_h.asm" - IF CONFIG_UART_AS_STDOUT - DEFC IO_PIO_SYSTEM_INT_MASK = ~(1 << IO_UART_RX_PIN) & 0xff - DEFC int_handler = uart_int_handler - ELSE ; !CONFIG_UART_AS_STDOUT + IF CONFIG_ENABLE_VIDEO_BOARD DEFC IO_PIO_SYSTEM_INT_MASK = ~(1 << IO_KEYBOARD_PIN) & 0xff DEFC int_handler = keyboard_int_handler - ENDIF ; CONFIG_UART_AS_STDOUT + ELSE ; !CONFIG_ENABLE_VIDEO_BOARD + DEFC IO_PIO_SYSTEM_INT_MASK = ~(1 << IO_UART_RX_PIN) & 0xff + DEFC int_handler = uart_int_handler + ENDIF ; CONFIG_ENABLE_VIDEO_BOARD EXTERN int_handlers_table EXTERN uart_int_handler diff --git a/src/rst_vectors.asm b/src/rst_vectors.asm index 993e690..4c80cb9 100644 --- a/src/rst_vectors.asm +++ b/src/rst_vectors.asm @@ -93,7 +93,7 @@ int_handlers_table: ; Section containing the list of systems registered that we can boot ; It must be aligned on 4KB as the SST39SF0x0 NOR Flashes can only erase ; sectors of 4KB. - ; Thus the sections above (code) must not be bigger than 4KB. If more space + ; Thus the sections above (code) must not be bigger than 8KB. If more space ; is needed, move this section further and adapt the code. SECTION SYS_TABLE ORG 0x2000 \ No newline at end of file diff --git a/src/systems.asm b/src/systems.asm index 13b301d..71e09cd 100644 --- a/src/systems.asm +++ b/src/systems.asm @@ -71,7 +71,7 @@ _sys_table_get_first_loop: ; No return PUBLIC sys_table_boot_entry sys_table_boot_entry: - IF !CONFIG_UART_AS_STDOUT + IF CONFIG_ENABLE_VIDEO_BOARD call video_unload_assets ENDIF ; Jump to the physical address directly @@ -231,7 +231,7 @@ _sys_table_get_first_empty_found: ; - PUBLIC sys_boot_from_ram sys_boot_from_ram: - IF !CONFIG_UART_AS_STDOUT + IF CONFIG_ENABLE_VIDEO_BOARD call video_unload_assets ENDIF ; The user's program is in RAM page 0 (0x80000) diff --git a/src/uart.asm b/src/uart.asm index c55714e..d19d2ce 100644 --- a/src/uart.asm +++ b/src/uart.asm @@ -34,8 +34,6 @@ uart_initialize: ret - IF CONFIG_UART_AS_STDOUT - ; Function called everytime the menu is about to be shown, do nothing in the case of the UART. PUBLIC uart_clear_screen uart_clear_screen: @@ -216,8 +214,6 @@ _uart_dequeue_available: pop hl ret - ENDIF ; CONFIG_UART_AS_STDOUT - PUBLIC uart_disable_fifo uart_disable_fifo: @@ -374,7 +370,7 @@ _uart_receive_next_byte: ; Parameters: ; CDE - Size of the file to receive (maximum 496KB) ; Returns: - ; None + ; A - 0 ; Alters: ; A PUBLIC uart_receive_big_file @@ -436,6 +432,7 @@ uart_receive_big_file_end: pop de pop bc pop hl + xor a ret diff --git a/src/video.asm b/src/video.asm index 324949b..6c2c114 100644 --- a/src/video.asm +++ b/src/video.asm @@ -4,15 +4,17 @@ INCLUDE "config.asm" INCLUDE "mmu_h.asm" - INCLUDE "uart_h.asm" + INCLUDE "stdout_h.asm" INCLUDE "pio_h.asm" INCLUDE "video_h.asm" INCLUDE "keyboard_h.asm" SECTION BOOTLOADER - EXTERN keyboard_has_char - EXTERN keyboard_get_char + EXTERN keyboard_get_char_nonblocking + EXTERN pio_disable_interrupt + EXTERN video_is_in_recovery + EXTERN menu_flash_video_board DEFC DEFAULT_VIDEO_MODE = VID_MODE_TEXT_640 DEFC DEFAULT_CURSOR_BLINK = 30 @@ -24,7 +26,6 @@ ENDM - ; Initialize the video card ; Parameters: ; None @@ -34,6 +35,10 @@ ; A PUBLIC video_initialize video_initialize: + ; If the video board is in recovery mode do not initialize anything + call video_is_in_recovery + jr z, _video_initialize_switch + ; Map the text controller to the banked I/O xor a out (IO_MAPPER_BANK), a @@ -69,6 +74,13 @@ video_initialize: out (IO_TEXT_CTRL_REG), a ret + ; Recovery mode, switch to UART mode +_video_initialize_switch: + ; Disable interrupts on the PIO + call pio_disable_interrupt + ld a, STDOUT_DRIVER_UART + jp std_set_driver + ; Print the given bytes on screen ; Parameters: @@ -101,32 +113,34 @@ _video_write_loop: ; Routine showing the autoboot message and waiting for a keypress. + ; If the video board is in recovery mode, propose to flash it, this function won't + ; return in that case. ; Returns: ; A - 0 if autoboot, 1 if key pressed PUBLIC video_autoboot video_autoboot: - ; Print the autoboot message + ; Check if the video board is in recovery mode + call video_is_in_recovery + jr z, video_recovery_flash + ; Not in recovery mode, continue booting normally, print the autoboot message ld hl, boot_message ld bc, boot_message_end - boot_message call video_write - ASSERT(CONFIG_AUTOBOOT_DELAY_SECONDS * 60 < 0x10000) + ASSERT(CONFIG_AUTOBOOT_DELAY_SECONDS * 59 < 0x10000) ; Wait CONFIG_AUTOBOOT_DELAY_SECONDS seconds while checking keyboard input - ; We will wait (CONFIG_AUTOBOOT_DELAY_SECONDS * 60) v-blanks since the refresh rate is 60Hz - ; TODO: Use the V-blank interrupts - ld hl, CONFIG_AUTOBOOT_DELAY_SECONDS * 60 + ; We will wait (CONFIG_AUTOBOOT_DELAY_SECONDS * 59) v-blanks since the refresh rate is 60Hz. + ; Keep one less v-blank to compensate the instructions we are executing. + ld hl, CONFIG_AUTOBOOT_DELAY_SECONDS * 59 ; Save the previous control status register in C ; This will let us check the edge and not the level of the v-blank xor a _video_autoboot_loop: ld c, a ; previous ctrl status in C - ; Check if any character is ready - ; Preserves HL and C - call keyboard_has_char - or a - jr z, _video_autoboot_loop_continue - ; A character was received on the keyboard, check if it's the ESC key - ; Preserves HL and C - call keyboard_get_char + ; Preserves C + push hl + call keyboard_get_char_nonblocking + pop hl + ; If A was not valid, it won't be equal to KB_ESC, so no need to check validity sub KB_ESC ; If it's not the ESC key, ignore it and continue the loop jr nz, _video_autoboot_loop_continue @@ -157,6 +171,22 @@ _video_autoboot_loop_continue: boot_message: DEFM "Booting...\n\nPress ESC key to enter menu" boot_message_end: + ; Function called when the video mode is in recovery mode, the user + ; will be asked to send a binary to flash. + ; Never returns +video_recovery_flash: + ; The UART interrupts are already disabled because...never initialized + PRINT_STR_UART(recovery_detected) + ; Clear the Z flag before jumping to this routine to mark that we are already in + ; recovery mode + or 1 + jp menu_flash_video_board + + +recovery_detected: + DEFM "Recovery mode detected on the video board, switched to UART mode\r\n" + DEFM 0x1b, "[1;33mTo flash the video board firmware, please specify the size in hexadecimal:", 0x1b, "[0m\r\n" +recovery_detected_end: ; Print a single byte on the screen ; Parameters: @@ -294,7 +324,6 @@ video_clear_screen: ld a, DEFAULT_CURSOR_BLINK out (IO_TEXT_CURS_TIME), a - ; Save the current mapping MMU_GET_PAGE_NUMBER(MMU_PAGE_1) push af diff --git a/src/video_board.asm b/src/video_board.asm new file mode 100644 index 0000000..cd3f044 --- /dev/null +++ b/src/video_board.asm @@ -0,0 +1,469 @@ + +; SPDX-FileCopyrightText: 2023 Zeal 8-bit Computer +; +; SPDX-License-Identifier: Apache-2.0 + INCLUDE "mmu_h.asm" + INCLUDE "uart_h.asm" + INCLUDE "stdout_h.asm" + + SECTION BOOTLOADER + + DEFC CPU_FREQ = 10000000 + + ; SPI Flash protocol related constants + DEFC SPI_READ_STA_CMD = 0x05 + DEFC SPI_WRITE_STA_CMD = 0x01 + + DEFC SPI_PAGE_PROG_CMD = 0x02 + + DEFC SPI_WRITE_ENA_CMD = 0x06 + DEFC SPI_WRITE_DIS_CMD = 0x04 + + DEFC SPI_PWR_DOWN_CMD = 0xb9 + DEFC SPI_ERASE_64KB_CMD = 0xd8 + + ; Version register + DEFC VID_VER_REV = 0x80 + DEFC VID_VER_MIN = 0x81 + DEFC VID_VER_MAJ = 0x82 + + ; Features registers + DEFC VID_MAP_DEVICE = 0x8e + DEFC VID_SPI_DEVICE = 0x1 + ; LED control register + DEFC LED_CTRL_REG = 0x8d + DEFC LED_CTRL_OFF = 0 + DEFC LED_CTRL_BLK_SYNC = 1 + DEFC LED_CTRL_BLK_ASYN = 2 + DEFC LED_CTRL_ON = 3 + + + ; SPI controller-related contants + DEFC SPI_REG_BASE = 0xa0 + DEFC REG_VERSION = (SPI_REG_BASE + 0) + DEFC REG_CTRL = (SPI_REG_BASE + 1) + DEFC REG_CTRL_START = 1 << 7 ; Start SPI transaction + DEFC REG_CTRL_RESET = 1 << 6 ; Reset the SPI controller + DEFC REG_CTRL_CS_START = 1 << 5 ; Assert chip select (low) + DEFC REG_CTRL_CS_END = 1 << 4 ; De-assert chip select signal (high) + DEFC REG_CTRL_CS_SEL = 1 << 3 ; Select among two chip selects (0 and 1) + DEFC REG_CTRL_RSV2 = 1 << 2 + DEFC REG_CTRL_RSV1 = 1 << 1 + DEFC REG_CTRL_STATE = 1 << 0 ; SPI controller in IDLE state (no transaction) + DEFC REG_CLK_DIV = (SPI_REG_BASE + 2) + DEFC REG_RAM_LEN = (SPI_REG_BASE + 3) + DEFC REG_CHECKSUM = (SPI_REG_BASE + 4) + ; ... ; + DEFC REG_RAM_FIFO = (SPI_REG_BASE + 7) + DEFC REG_RAM_FROM = (SPI_REG_BASE + 8) + DEFC REG_RAM_TO = (SPI_REG_BASE + 15) + + ; Calculate the contanst to start the transfer and assert Chip-Select + DEFC SPI_START_TRANSFER = REG_CTRL_START | REG_CTRL_CS_START | REG_CTRL_CS_SEL + DEFC SPI_END_TRANSFER = REG_CTRL_CS_END | REG_CTRL_CS_SEL + + + EXTERN video_board_switch + EXTERN video_board_switch_end + + ; Check if the video board is in recovery mode + ; Parameters: + ; None + ; Returns: + ; A - 0 if in recovery mode, positive value else + ; Alters: + ; A + PUBLIC video_is_in_recovery +video_is_in_recovery: + in a, (VID_VER_REV) + sub 0xde + ret nz + in a, (VID_VER_MIN) + cp 0xad + ret nz + in a, (VID_VER_MAJ) + cp 0x23 + ret + + + ; Wait for the recovery mode to be entered by the video board + ; Parameters: + ; None + PUBLIC video_wait_recovery +video_wait_recovery: + ; TODO: refactor to make this cleaner instead of referencing labels from a + ; different file + PRINT_STR_UART(video_board_switch) + ; Wait for 500 milliseconds + ld de, 500 + call sleep_ms + ; Check if we are in the recovery mode + call video_is_in_recovery + ret z + jr video_wait_recovery + + ; Flash the NOR Flash located on the video board through SPI. + ; Parameters: + ; RAM[0x80000]: Data to flash at offset 0 of the flash + ; CDE: 24-bit size of the data + ; Returns: + ; A - 0 on success, error code else + PUBLIC video_board_flash +video_board_flash: + ; To signify a flash is in progress, make the LEDs blink at the same time + ld a, LED_CTRL_BLK_SYNC + out (LED_CTRL_REG), a + ; Enable the SPI component + ld a, VID_SPI_DEVICE + out (VID_MAP_DEVICE), a + ; Configure the SPI clock to 25MHz + ld a, 2 + out (REG_CLK_DIV), a + ; Even though the NOR Flash may support erasing smaller sectors than 64KB ones, + ; let's keep it like this to support as many flashes as possible. + ; Calculate the number of sectors to erase: round_up(CDE/64KB) + ; This is equivalent o C + ((D | E) ? 1 : 0) + ld a, d + or e + ; Pre-load A since it doesn't alter the flags + ld a, c + jr z, _no_inc + inc a +_no_inc: + ; A contains the number of sectors to erase, erase them + call video_board_erase_A_sectors + ; We have to program the data as pages, each page is 256 bytes, so we have + ; CD pages in total. Let's forget about the "incomplete" page for now + push de + ; Store number of pages in BC + ld b, c + ld c, d + ; Store in H the virtual address highest byte + ld h, 0x40 + ; Store in DE the upper 16-bit of physical address + ld de, 0 + ; Map the pages 1 and 2 to physical address 0x80000 + ld a, 0x80000 >> 14 + out (MMU_PAGE_1), a + inc a + out (MMU_PAGE_2), a + inc a + ; Store the next pages to map in L + ld l, a + ; Check if we have any 256-byte page to program + ld a, b + or c + jr z, _no_full_pages +_program_page: + ; Iterate over BC pages + push hl + push de + push bc + call video_board_program_page + pop bc + pop de + pop hl + ; Increment the virtual page, if it reaches (0xC), we have already sent 32KB + ; Go back to 0x40 + inc h + ld a, 0xc0 + cp h + jr nz, _program_page_no_overflow + ; Map the next pages too! + ld a, l + out (MMU_PAGE_1), a + inc a + out (MMU_PAGE_2), a + inc a + ; Store the next pages in L again + ld l, a + ld h, 0x40 +_program_page_no_overflow: + ; In all cases, increment the physical address by 256 + inc de + ; Decrement the total number of pages to write + dec bc + ld a, b + or c + jr nz, _program_page +_no_full_pages: + ; Program the remaining bytes + pop bc + ; A is zero, test C directly + or c + jr z, _video_board_flash_disable_write + ; Program remaining bytes (less than 256), H contains the virtual address MSB + ; A contains the number of bytes to send + dec a ; Decrement as requested by the routine below + call video_board_program_A_bytes +_video_board_flash_disable_write: + ; Issue a WRITE-DISABLE command to the SPI flash + ld a, SPI_WRITE_DIS_CMD + call spi_start_single_byte_command + ; Save returned value in B + ld b, a + ; Notify that the flash is finished by turning LEDs on + ld a, LED_CTRL_ON + out (LED_CTRL_REG), a + ; Restore returned value + ld a, b + ret + + + ; Erase A sectors starting at 0x000000 on the flash + ; Parameters: + ; A - Number of sectors to erase (guaranteed not 0) + ; Returns: + ; None + ; Alters: + ; A +video_board_erase_A_sectors: + push de + push bc + ; Iterate over all the sectors + ld b, a + ; Prepare the hardware buffer with the lowest 16-bit bytes of sector address + xor a + ; Second and third address bytes (always 0) + out (REG_RAM_FROM + 2), a + out (REG_RAM_FROM + 3), a +_erase_sector: + call spi_start_write_enable_cmd + ; Erase command + ld a, SPI_ERASE_64KB_CMD + out (REG_RAM_FROM), a + ; Sector address highest byte (count - 1) + ld a, b + dec a + out (REG_RAM_FROM + 1), a + ; Set the HW buffer length: command + 3 bytes = 4 bytes + ld a, 4 + call spi_start_transfer + ; Save BC since B contains the erase sector count + push bc +_erase_sector_wait_status: + ; The erase command has been started, it is going to take a while, not less than + ; 75ms. Let's not flood the SPI bus with STATUS commands, and stall for a while + ld de, 75 + call sleep_ms + ; Wait for completion + call spi_get_status + rrca + jr c, _erase_sector_wait_status + ; Sector is erased, continue! + pop bc + djnz _erase_sector + ; Restore original BC and DE and return + pop bc + pop de + ret + + + ; Program BC pages into the video board flash. + ; Parameters: + ; H - Upper 8-bit of the virtual address + ; DE - Upper 16-bit of the physical address to write + ; Returns: + ; None + ; Alters: + ; A, BC, DE, HL +video_board_program_page: + ld a, 0xff + ; Fall-through + + ; Program the last page, which is strictly smaller than 256 bytes + ; Parameters: + ; H - Upper 8-bit of the virtual address + ; DE - Upper 16-bit of the physical address to write + ; A - Number of bytes to write MINUS 1! + ; 0x8000-0xC000 - mapped to the actual data to write + ; Returns: + ; A - 0 on success, postiive value else +video_board_program_A_bytes: + ; Save the number of bytes to flash in register B + ld b, a + ; Issue a write-enable command + call spi_start_write_enable_cmd + call spi_start_write_page_command + ; Send the 256 bytes, use HL as the virtual address + ld l, 0 + ; We can now re-use DE registers + ; Store (number_of_bytes % 8) in D + ld a, b + inc a + and 0x7 ; <=> (B+1) % 8 + ld d, a + ; Let E store the number of loops to perform by dividing number_of_bytes by 8 + ld a, b + add 1 ; cannot use `inc a` since we need the carry in case of overflow! + rra + rra + rra + ; Remove the uppest two bits, since we counted CY as the 8th bit + and 0x3f + ; If A is 0, we have less than 8 bytes to write + jr z, _program_less_than_8 + ld e, a + call spi_write_E_pages_8_bytes +_program_less_than_8: + ; Number of bytes to transfer is in D (less than 8) + ; if it is 0, we can return already + ld a, d + or a + jr z, spi_end_transfer_wait_write + ; We still have some bytes to write, let's do this, reset the FIFO + or 0x80 ; clear pseudo-FIFO indexes + out (REG_RAM_LEN), a + ; Store number of bytes to transfer in B, as required by otir instruction + ld b, d + ld c, REG_RAM_FIFO + ; Load the bytes in the hardware RAM thanks to the pseudo-FIFO register + otir + ; Start the transfer + ld a, SPI_START_TRANSFER + out (REG_CTRL), a + ; Wait for the SPI Idle state, the SPI flash has NOT started writing, so no + ; need to wait for it for now + call spi_wait_idle + jp spi_end_transfer_wait_write + + + ; Start an SPI command by asserting the CS low and send the physical page address + ; Parameters: + ; DE - Physical address to write +spi_start_write_page_command: + ; Send the command and the address to write + ld a, SPI_PAGE_PROG_CMD + out (REG_RAM_FROM + 0), a + ; Highest bytes + ld a, d + out (REG_RAM_FROM + 1), a + ld a, e + out (REG_RAM_FROM + 2), a + ; 0 as the lowest byte + xor a + out (REG_RAM_FROM + 3), a + ; Set the RAM length to 4 + ld a, 4 + out (REG_RAM_LEN), a + ; Start the SPI transfer + ld a, SPI_START_TRANSFER + out (REG_CTRL), a + ret + + + ; Start an SPI transfer that writes E pages of 8 bytes + ; Alters: + ; A, BC, E, HL +spi_write_E_pages_8_bytes: + ; RAM Length is never modified by the hardware, so it is persistent. + ; Let's use the pseudo-FIFO mode, reset the indexes by setting the highest bit. + ld a, 0x88 + out (REG_RAM_LEN), a + ; C will contain the address of the register + ld c, REG_RAM_FIFO +_spi_write_E_pages_8_bytes_next_batch: + ; B must contain the number of bytes to transfer (8) + ld b, 8 + ; Load the bytes in the hardware RAM thanks to the pseudo-FIFO register + otir + ld a, SPI_START_TRANSFER + out (REG_CTRL), a + ; Wait for the SPI Idle state, the SPI flash has NOT started writing, so no + ; need to wait for it for now + call spi_wait_idle + ; Continue the loop to transfer the rest of the bytes + dec e + jr nz, _spi_write_E_pages_8_bytes_next_batch + ret + + + ; Wait for the SPI controller to go to Idle state + ; Alters: + ; A +spi_wait_idle: + in a, (REG_CTRL) + rrca ; Check bit 0, must be 0 too + ret nc + jr spi_wait_idle + + + ; End the SPI (write) transfer and wait for the transaction to terminate on the flash end + ; Returns: + ; A - 0 (Success) +spi_end_transfer_wait_write: + ld a, SPI_END_TRANSFER + out (REG_CTRL), a +_spi_end_transfer_wait_write_loop: + call spi_get_status + rrca + ret nc + jr _spi_end_transfer_wait_write_loop + + ; Get the current status of the SPI flash +spi_get_status: + ; Issue a status command + ld a, SPI_READ_STA_CMD + out (REG_RAM_FROM), a + ; Provide a 2-byte size since we need to read a byte (the second byte will be discarded) + ld a, 2 + call spi_start_transfer + ; The transaction is finished since `spi_start_transfer` waits for idle state + ; Read the status reply from the flash + in a, (REG_RAM_FROM + 1) + ; Lowest bit is 1 if write is in progress, 0 else + ret + + + ; Issue a write-enable command +spi_start_write_enable_cmd: + ld a, SPI_WRITE_ENA_CMD + ; Fall-through + + ; Start a sungle byte command + ; Parameters: + ; A - Command to send +spi_start_single_byte_command: + out (REG_RAM_FROM), a + ld a, 1 + ; Fall-through + + ; Start an SPI transfer by setting the buffer length and asserting the chip select + ; Parameters: + ; A - Length of the HW buffer + ; Returns: + ; A - 0 (Success) +spi_start_transfer: + out (REG_RAM_LEN), a + ; Start the command and assert chip select at the same time + ld a, SPI_START_TRANSFER + out (REG_CTRL), a + ; Wait for the transaction to be finished + call spi_wait_idle + ; De-assert the chip-select + ld a, SPI_END_TRANSFER + out (REG_CTRL), a + ; Success, return 0 + xor a + ret + + + ; Sleep for a given amount of milliseconds + ; Parameters: + ; DE - Number of milliseconds to sleep for + ; Returns: + ; None +sleep_ms: + ld bc, CPU_FREQ / 1000 / 24 +_sleep_ms_waste: + ; 24 T-states for the following, until 'jp nz, _sleep_ms_waste' + dec bc + ld a, b + or c + jp nz, _sleep_ms_waste + ; If we are here, a milliseconds has elapsed + dec de + ld a, d + or e + jp nz, sleep_ms + ret