diff --git a/README.md b/README.md index 6f189ae..3324551 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ The following cartridge types are currently supported: * Super Snapshot v5 * Comal-80 * EasyFlash +* Expert Cartridge +* Georam expansion module (only the first 64K is available) ## Supported File Types The following file types are currently supported: @@ -71,6 +73,16 @@ Kung Fu Flash will work with the PAL version of the Commodore 64 or Commodore 12 Disk drive emulation is using kernal vectors and will not work with fast loaders or software that uses direct hardware access which a lot of games does. Currently REL files are not supported and there is only limited write support. +Georam expansion only has 64kbyte due to microcontroller limitations. + +## Expansion + +Currently 2 expansions can be selected in the setting menu (F5). Doing a kill (F8) will start this cart. + +Expert cartridge starts in 'PRG' mode, after the first reset it is switched to 'ON' mode. Jumping into the menu (using the Menu button) will reset the mode to 'PRG' (if F8 is pressed there). To start the payload use the 'RESTORE' key (freeze button does nothing) + +Georam only has 64kbyte of memory for now. + ## Thanks Kung Fu Flash was based on or uses other open source projects: diff --git a/firmware/cartridges/cartridge.c b/firmware/cartridges/cartridge.c index ccd5ef7..591b2a2 100644 --- a/firmware/cartridges/cartridge.c +++ b/firmware/cartridges/cartridge.c @@ -31,6 +31,8 @@ #include "super_snapshot_5.c" #include "easyflash.c" #include "comal80.c" +//#include "georam.c" +#include "expert.c" static void (*crt_get_handler(uint16_t cartridge_type, bool vic_support)) (void) { @@ -83,6 +85,13 @@ static void (*crt_get_handler(uint16_t cartridge_type, bool vic_support)) (void) { return ef_sdio_handler; } + + case CRT_EXPERT_CARTRIDGE: + return expert_handler; + + // expansions like expert and georam + case EXP_EXPERT: + return expert_handler; } return NULL; @@ -119,6 +128,14 @@ static void (*crt_get_init(uint16_t cartridge_type)) (void) case CRT_EASYFLASH: return ef_init; + + case CRT_EXPERT_CARTRIDGE: + return expert_init; + + // expansions + case EXP_EXPERT: + return expert_expansion_init; + } return NULL; diff --git a/firmware/cartridges/expert.c b/firmware/cartridges/expert.c new file mode 100644 index 0000000..e5d0d1b --- /dev/null +++ b/firmware/cartridges/expert.c @@ -0,0 +1,283 @@ +/* + * KungFuFlash Copyright (c) 2019-2021 Kim Jørgensen + * + * Expert cart support written and (c) 2020-2021 Chris van Dongen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#define EXPERT_PROGRAM 0 // RAM can be programmed +#define EXPERT_IDLE 1 // waiting for button +#define EXPERT_ACTIVE 2 // button pressed +#define EXPERT_ULTIMAX 3 // ultimax +#define EXPERT_OFF 4 // expert is hidden/off + +static uint32_t expert_mode; + +static uint32_t const expert_modes[5] = +{ + STATUS_LED_OFF|CRT_PORT_NONE, // Orogram + STATUS_LED_ON|CRT_PORT_NONE, // Idle + STATUS_LED_ON|CRT_PORT_NONE, // Active + STATUS_LED_ON|CRT_PORT_ULTIMAX, // Memacces at 0xe000 or 0x8000 + STATUS_LED_OFF|CRT_PORT_NONE // Hidden/Off +}; + + +// we need some extra cycles to switch out ultimax mode +#define PHI2_CPU_END_EXPERT (PHI2_CPU_END-3) + +// we need a special cart handler +#define C64_VIC_BUS_HANDLER_EXPERT(name) \ + C64_VIC_BUS_HANDLER_EXPERT_(name##_handler, name##_vic_read_handler, \ + name##_read_handler, name##_early_write_handler, \ + name##_write_handler, C64_VIC_DELAY) + + +// This supports VIC-II reads from the cartridge (i.e. character and sprite data) +// but uses 100% CPU - other interrupts are not served due to the interrupt priority +#define C64_VIC_BUS_HANDLER_EXPERT_(name, vic_read_handler, read_handler, \ + early_write_handler, write_handler, vic_delay) \ +void name(void) \ +{ \ + /* As we don't return from this handler, we need to do this here */ \ + c64_reset(false); \ + /* Use debug cycle counter which is faster to access than timer */ \ + DWT->CYCCNT = TIM1->CNT; \ + __DMB(); \ + while (true) \ + { \ + /* Wait for CPU cycle */ \ + while (DWT->CYCCNT < PHI2_CPU_START); \ + uint32_t addr = c64_addr_read(); \ + COMPILER_BARRIER(); \ + uint32_t control = c64_control_read(); \ + /* Check if CPU has the bus (no bad line) */ \ + if ((control & (C64_BA|C64_WRITE)) == (C64_BA|C64_WRITE)) \ + { \ + if (read_handler(control, addr)) \ + { \ + /* Release bus when phi2 is going low */ \ + while (DWT->CYCCNT < PHI2_CPU_END_EXPERT); \ + c64_data_input(); \ + } \ + } \ + else if (!(control & C64_WRITE)) \ + { \ + early_write_handler(); \ + uint32_t data = c64_data_read(); \ + write_handler(control, addr, data); \ + while (DWT->CYCCNT < PHI2_CPU_END_EXPERT); \ + } \ + /* VIC-II has the bus */ \ + else \ + { \ + /* Wait for the control bus to become stable */ \ + C64_CPU_VIC_DELAY() \ + control = c64_control_read(); \ + if (vic_read_handler(control, addr)) \ + { \ + /* Release bus when phi2 is going low */ \ + while (DWT->CYCCNT < PHI2_CPU_END_EXPERT); \ + c64_data_input(); \ + } \ + } \ + c64_crt_control(expert_modes[expert_mode]); /* reset ultimax */ \ + if (control & MENU_BTN) \ + { \ + /* Allow the menu button interrupt handler to run */ \ + c64_interface(false); \ + break; \ + } \ + /* Wait for VIC-II cycle */ \ + while (TIM1->CNT >= 80); \ + DWT->CYCCNT = TIM1->CNT; \ + __DMB(); \ + while (DWT->CYCCNT < PHI2_VIC_START); \ + addr = c64_addr_read(); \ + COMPILER_BARRIER(); \ + /* Ideally, we would always wait until PHI2_VIC_DELAY here which is */ \ + /* required when the VIC-II has the bus, but we need more cycles */ \ + /* in C128 2 MHz mode where data is read from flash */ \ + vic_delay(); \ + control = c64_control_read(); \ + if (vic_read_handler(control, addr)) \ + { \ + /* Release bus when phi2 is going high */ \ + while (DWT->CYCCNT < PHI2_VIC_END); \ + c64_data_input(); \ + } \ + } \ + TIM1->SR = ~TIM_SR_CC3IF; \ + __DMB(); \ +} + + +/************************************************* +* Make it programmable the first boot; next boot +* it is activated +*************************************************/ + +#define EXPERT_SIGNATURE "Expert:booted" + +// we use scratch_buf+10 as scratch_buff seems to be overwritten +// probably we need a better way? + +static inline void set_expert_signature(void) +{ + memcpy(scratch_buf+10, EXPERT_SIGNATURE, sizeof(EXPERT_SIGNATURE)); +} + +static inline bool expert_signature(void) +{ + return memcmp(scratch_buf+10, EXPERT_SIGNATURE, sizeof(EXPERT_SIGNATURE)) == 0; +} + +static inline void invalidate_expert_signature(void) +{ + scratch_buf[10] = 0; +} + +/************************************************* +* C64 bus read callback (VIC-II cycle) +*************************************************/ +static inline bool expert_vic_read_handler(uint32_t control, uint32_t addr) +{ + // no vic read needed + + return false; +} + +/************************************************* +* C64 bus read callback (CPU cycle) +*************************************************/ +static inline bool expert_read_handler(uint32_t control, uint32_t addr) +{ + // when active, the kernal and $8000 is presented in ultimax mode + if((expert_mode==EXPERT_ACTIVE)&&(((addr&0xe000)==0xe000)||((addr&0xe000)==0x8000))) + { + c64_crt_control(expert_modes[EXPERT_ULTIMAX]); /* switch to ultimax if we access kernal or 0x8000 */ + c64_data_write(crt_ptr[addr & 0x1fff]); + return true; + } + + // access to IO1 switch back to EXPERT_IDLE + if ((!(control & C64_IO1))&&(expert_mode!=EXPERT_PROGRAM)) + { + expert_mode=EXPERT_IDLE; + } + + // handle external NMI + if ((!((GPIOA->IDR)&GPIO_IDR_ID10))&&(expert_mode==EXPERT_IDLE)) // nmi is on gpioa-10 + { + freezer_state = FREEZE_START; + } + + // special button toggles expert on/off + if (control & SPECIAL_BTN) + { + freezer_button = FREEZE_PRESSED; + } + else if (freezer_button) + { + freezer_button = FREEZE_RELEASED; + + if(expert_mode==EXPERT_IDLE) expert_mode=EXPERT_OFF; + else if(expert_mode==EXPERT_OFF) expert_mode=EXPERT_IDLE; + } + + return false; +} + +/************************************************* +* C64 bus write callback (early) +*************************************************/ +static inline void expert_early_write_handler(void) +{ + // Use 3 consecutive writes to detect IRQ/NMI + if (freezer_state && ++freezer_state == FREEZE_3_WRITES) + { + expert_mode=EXPERT_ACTIVE; + c64_crt_control(expert_modes[expert_mode]); + freezer_state = FREEZE_RESET; + } +} + +/************************************************* +* C64 bus write callback +*************************************************/ +static inline void expert_write_handler(uint32_t control, uint32_t addr, uint32_t data) +{ + if((expert_mode==EXPERT_ACTIVE)&&(((addr&0xe000)==0x8000))) + { + c64_crt_control(expert_modes[EXPERT_ULTIMAX]); /* switch to ultimax if we access kernal or roml */ + crt_ptr[addr & 0x1fff]=data; + } + + // access to IO1 switches back to EXPERT_IDLE + if ((!(control & C64_IO1))&&(expert_mode!=EXPERT_PROGRAM)) + { + expert_mode=EXPERT_IDLE; + } + + // if we are in program mode we are writable at 0x8000 + if((expert_mode==EXPERT_PROGRAM)&&((addr&0xe000)==0x8000)) + { + crt_ptr[addr & 0x1FFF]=data; + } +} + +/************************************************* +* init when loading an expert image +*************************************************/ +static void expert_init(void) +{ + crt_ptr = crt_banks[0]; + + expert_mode=EXPERT_ACTIVE; + + c64_crt_control(expert_modes[expert_mode]); +} + +/************************************************* +* init when called as expansion +*************************************************/ + +static void expert_expansion_init(void) +{ + int i=0; + + crt_ptr = (uint8_t*)scratch_buf+8192; + + if(expert_signature()) + { + expert_mode=EXPERT_ACTIVE; + } + else + { + expert_mode=EXPERT_PROGRAM; + set_expert_signature(); + + for(i=0; i<8192; i++) crt_ptr[i]=0x00; + } + + c64_crt_control(expert_modes[expert_mode]); +} + +// contruct Expert handler +C64_VIC_BUS_HANDLER_EXPERT(expert) + diff --git a/firmware/cartridges/georam.c b/firmware/cartridges/georam.c new file mode 100644 index 0000000..96fd6b4 --- /dev/null +++ b/firmware/cartridges/georam.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 Kim Jørgensen + * + * Georam cart support written and (c) 2020 Chris van Dongen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +static uint32_t ramoffset; +static uint32_t bank, offset; + + +/************************************************* +* C64 bus read callback +*************************************************/ +static inline bool georam_read_handler(uint32_t control, uint32_t addr) +{ + if (!(control & C64_IO1)) + { + c64_data_write(crt_ptr[ramoffset+(addr & 0xff)]); + return true; + } + + + return false; +} + +/************************************************* +* C64 bus write callback +*************************************************/ +static inline void georam_write_handler(uint32_t control, uint32_t addr, uint32_t data) +{ + if (!(control & C64_IO1)) + { + crt_ptr[ramoffset+(addr & 0x0ff)]=(uint8_t)data; + } + + + if (!(control & C64_IO2)) + { + if(addr==0xDFFF) bank=data; // range (0-127) + if(addr==0xDFFE) offset=data; // range (0-63) + + bank&=0x03; // we only have 64k avail. + + ramoffset=((bank&0x07F<<14)||((offset&0x03f)<<8)); + } +} + +/************************************************* +* init georam +*************************************************/ +void georam_init(void) +{ + // ram init + for(int i=0; i<(64*1024); i++) + { + crt_ptr[i]=0x00; + } + + c64_crt_control(STATUS_LED_ON|CRT_PORT_NONE); + ramoffset=0; + offset=0; + bank=0; +} + +// Support MAX cartridges where character and sprite data is read from the cartridge +C64_VIC_BUS_HANDLER(georam) diff --git a/firmware/file_types.h b/firmware/file_types.h index 68ef1e2..60e19d2 100644 --- a/firmware/file_types.h +++ b/firmware/file_types.h @@ -83,6 +83,11 @@ typedef enum { CRT_GMOD2 } CRT_TYPE; +#define EXP_NONE 0xFFF0 +#define EXP_EXPERT 0xFFF1 +#define EXP_RES1 0xFFF2 // reserve +#define EXP_RES2 0xFFF3 // reserve + typedef enum { CRT_CHIP_ROM = 0x00, CRT_CHIP_RAM, @@ -140,11 +145,15 @@ typedef enum { DAT_FLAG_PERSIST_BASIC = 0x01, DAT_FLAG_AUTOSTART_D64 = 0x02, DAT_FLAG_DEVICE_NUM_D64_1 = 0x04, - DAT_FLAG_DEVICE_NUM_D64_2 = 0x08 + DAT_FLAG_DEVICE_NUM_D64_2 = 0x08, + DAT_FLAG_MEMEXPANSION_1 = 0x10, + DAT_FLAG_MEMEXPANSION_2 = 0x20, } DAT_FLAGS; #define DAT_FLAG_DEVICE_D64_POS 0x02 #define DAT_FLAG_DEVICE_D64_MSK (0x03 << DAT_FLAG_DEVICE_D64_POS) +#define DAT_FLAG_MEMEXPANSION_POS 0x04 +#define DAT_FLAG_MEMEXPANSION_MSK (0x03 << DAT_FLAG_MEMEXPANSION_POS) typedef enum { DAT_NONE = 0x00, @@ -155,7 +164,7 @@ typedef enum { DAT_KERNAL, DAT_BASIC, DAT_KILL, - DAT_KILL_C128 + DAT_KILL_C128, } DAT_BOOT_TYPE; typedef enum { diff --git a/firmware/loader.c b/firmware/loader.c index 88c1cf7..3a2f3e1 100644 --- a/firmware/loader.c +++ b/firmware/loader.c @@ -725,16 +725,40 @@ static bool c64_set_mode(void) break; case DAT_KILL: - { - c64_disable(); - // Also unstoppable! - c64_crt_control(STATUS_LED_OFF|CRT_PORT_8K); - c64_reset(false); - delay_ms(300); - c64_crt_control(CRT_PORT_NONE); - result = true; - } - break; + { + uint16_t expansion; + + if((dat_file.flags&DAT_FLAG_MEMEXPANSION_MSK)==0) // no expansion is selected + { + c64_disable(); + // Also unstoppable! + c64_crt_control(STATUS_LED_OFF|CRT_PORT_8K); + c64_reset(false); + delay_ms(300); + c64_crt_control(CRT_PORT_NONE); + result = true; + } + else + { + if (!c64_is_reset()) + { + // Disable VIC-II output if C64 has been started (needed for FC3) + c64_interface(true); + c64_send_wait_for_reset(); + c64_disable(); + } + + expansion=0xFFF0+((dat_file.flags&DAT_FLAG_MEMEXPANSION_MSK)>>DAT_FLAG_MEMEXPANSION_POS); + + c64_crt_control(STATUS_LED_ON|CRT_PORT_NONE); + // Try prevent triggering bug in H.E.R.O. No effect at power-on though + c64_sync_with_vic(); + crt_install_handler(expansion, CRT_FLAG_NONE); + c64_enable(); + result = true; + } + } + break; case DAT_KILL_C128: { diff --git a/firmware/main.c b/firmware/main.c index 84dc0f3..fb6c51d 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -55,6 +55,7 @@ int main(void) if (!auto_boot()) { + invalidate_expert_signature(); // pressing menu will invalidate Expert mem c64_enable(); menu_loop(); } diff --git a/firmware/menu_settings.c b/firmware/menu_settings.c index ce83662..08d9e97 100644 --- a/firmware/menu_settings.c +++ b/firmware/menu_settings.c @@ -97,6 +97,33 @@ static bool settings_save(OPTIONS_STATE *state, OPTIONS_ELEMENT *element, uint8_ return false; } +static const char * settings_memexpansion_text(void) +{ + sprint(scratch_buf, "Expansion on F8: %s", + ((settings_flags & DAT_FLAG_MEMEXPANSION_2) ? + ((settings_flags & DAT_FLAG_MEMEXPANSION_1) ? "---" : "---") : + ((settings_flags & DAT_FLAG_MEMEXPANSION_1) ? "Expert" : "None")) ); + + return scratch_buf; +} + +static bool settings_memexpansion_change(OPTIONS_STATE *state, OPTIONS_ELEMENT *element, uint8_t flags) +{ + uint8_t expansion; + + expansion = (settings_flags & DAT_FLAG_MEMEXPANSION_MSK) >> DAT_FLAG_MEMEXPANSION_POS; + expansion ++; + if(expansion==2) expansion=0; // slot 3-4 had no handler so roll over + expansion <<= DAT_FLAG_MEMEXPANSION_POS; + expansion &= DAT_FLAG_MEMEXPANSION_MSK; + + settings_flags = (settings_flags & ~DAT_FLAG_MEMEXPANSION_MSK) | expansion; + + options_element_text(element, settings_memexpansion_text()); + menu_state->dir(menu_state); // Refresh settings + return false; +} + static void handle_settings(void) { settings_flags = dat_file.flags; @@ -105,6 +132,7 @@ static void handle_settings(void) options_add_text_element(options, settings_basic_change, settings_basic_text()); options_add_text_element(options, settings_autostart_change, settings_autostart_text()); options_add_text_element(options, settings_device_change, settings_device_text()); + options_add_text_element(options, settings_memexpansion_change, settings_memexpansion_text()); options_add_text_element(options, settings_save, "Save"); options_add_dir(options, "Cancel"); handle_options(options);