From b0ad5db6c66254ea4e3899157aed5cc0cfe4f109 Mon Sep 17 00:00:00 2001 From: Philip Smart Date: Mon, 17 Apr 2023 21:46:26 +0100 Subject: [PATCH] Alpha release candidate rc1 --- .gitignore | 23 + CPLD/v1.0/MZ2000/build/tzpuFusionX_MZ2000.qsf | 4 +- CPLD/v1.0/MZ2000/tzpuFusionX.vhd | 454 +- CPLD/v1.0/MZ700/build/tzpuFusionX_MZ700.qsf | 4 +- CPLD/v1.0/MZ700/tzpuFusionX.vhd | 960 +-- CPLD/v1.0/MZ80A/tzpuFusionX.vhd | 433 +- CPLD/v1.0/PCW8256/tzpuFusionX.vhd | 1083 +++ CPLD/v1.0/PCW8256/tzpuFusionX_Toplevel.vhd | 227 + CPLD/v1.0/PCW8256/tzpuFusionX_pkg.vhd | 227 + software/FusionX/bin/k64fcpu | Bin 64696 -> 64652 bytes software/FusionX/bin/sharpbiter | Bin 21420 -> 21440 bytes software/FusionX/bin/z80ctrl | Bin 49876 -> 49876 bytes software/FusionX/modules/ttymzdrv.ko | Bin 594848 -> 575616 bytes software/FusionX/modules/z80drv.ko | Bin 688092 -> 679028 bytes software/FusionX/src/ttymz/Makefile | 46 +- software/FusionX/src/ttymz/sharpmz.c | 577 +- software/FusionX/src/ttymz/sharpmz.h | 348 +- software/FusionX/src/ttymz/z80io.c | 2 +- software/FusionX/src/ttymz/z80io.h | 95 +- software/FusionX/src/z80drv/MZ2000/z80ctrl.c | 794 --- .../FusionX/src/z80drv/MZ2000/z80driver.c | 1616 ----- .../FusionX/src/z80drv/MZ2000/z80driver.h | 326 - software/FusionX/src/z80drv/MZ2000/z80io.c | 428 -- software/FusionX/src/z80drv/MZ2000/z80io.h | 483 -- .../FusionX/src/z80drv/MZ2000/z80io_test.c | 541 -- software/FusionX/src/z80drv/MZ700/optparse.h | 403 -- software/FusionX/src/z80drv/MZ700/z80ctrl.c | 734 -- software/FusionX/src/z80drv/MZ700/z80driver.c | 1447 ---- software/FusionX/src/z80drv/MZ700/z80driver.h | 284 - software/FusionX/src/z80drv/MZ700/z80io.c | 428 -- software/FusionX/src/z80drv/MZ700/z80io.h | 483 -- .../FusionX/src/z80drv/MZ700/z80io_test.c | 541 -- software/FusionX/src/z80drv/MZ700/z80menu.c | 57 - software/FusionX/src/z80drv/MZ700/z80menu.h | 44 - software/FusionX/src/z80drv/MZ80A/optparse.h | 403 -- software/FusionX/src/z80drv/MZ80A/z80menu.c | 57 - software/FusionX/src/z80drv/MZ80A/z80menu.h | 44 - software/FusionX/src/z80drv/Makefile | 61 +- software/FusionX/src/z80drv/Z80 | 2 +- software/FusionX/src/z80drv/src/emumz.c | 6127 +++++++++++++++++ .../src/z80drv/{MZ80A => src}/k64fcpu.c | 25 +- .../src/z80drv/{MZ2000 => src}/optparse.h | 0 .../src/z80drv/{MZ80A => src}/sharpbiter.c | 90 +- .../src/z80drv/{MZ80A => src}/sharpmz.c | 0 .../FusionX/src/z80drv/{MZ80A => src}/tzpu.h | 8 +- .../src/z80drv/{MZ80A => src}/z80ctrl.c | 35 +- .../src/z80drv/{MZ80A => src}/z80driver.c | 1106 ++- .../src/z80drv/{MZ80A => src}/z80driver.h | 248 +- .../FusionX/src/z80drv/{MZ80A => src}/z80io.c | 0 .../FusionX/src/z80drv/{MZ80A => src}/z80io.h | 95 +- .../src/z80drv/{MZ80A => src}/z80io_test.c | 14 +- .../src/z80drv/{MZ2000 => src}/z80menu.c | 0 .../src/z80drv/{MZ2000 => src}/z80menu.h | 0 .../FusionX/src/z80drv/src/z80vhw_mz2000.c | 436 ++ .../FusionX/src/z80drv/src/z80vhw_mz700.c | 481 ++ .../FusionX/src/z80drv/src/z80vhw_mz80a.c | 382 + software/FusionX/src/z80drv/src/z80vhw_pcw.c | 379 + .../src/z80drv/{MZ80A => src}/z80vhw_rfs.c | 65 +- .../src/z80drv/{MZ80A => src}/z80vhw_tzpu.c | 355 +- 59 files changed, 12616 insertions(+), 10889 deletions(-) create mode 100644 CPLD/v1.0/PCW8256/tzpuFusionX.vhd create mode 100644 CPLD/v1.0/PCW8256/tzpuFusionX_Toplevel.vhd create mode 100644 CPLD/v1.0/PCW8256/tzpuFusionX_pkg.vhd delete mode 100644 software/FusionX/src/z80drv/MZ2000/z80ctrl.c delete mode 100644 software/FusionX/src/z80drv/MZ2000/z80driver.c delete mode 100644 software/FusionX/src/z80drv/MZ2000/z80driver.h delete mode 100644 software/FusionX/src/z80drv/MZ2000/z80io.c delete mode 100755 software/FusionX/src/z80drv/MZ2000/z80io.h delete mode 100644 software/FusionX/src/z80drv/MZ2000/z80io_test.c delete mode 100644 software/FusionX/src/z80drv/MZ700/optparse.h delete mode 100644 software/FusionX/src/z80drv/MZ700/z80ctrl.c delete mode 100644 software/FusionX/src/z80drv/MZ700/z80driver.c delete mode 100644 software/FusionX/src/z80drv/MZ700/z80driver.h delete mode 100644 software/FusionX/src/z80drv/MZ700/z80io.c delete mode 100755 software/FusionX/src/z80drv/MZ700/z80io.h delete mode 100644 software/FusionX/src/z80drv/MZ700/z80io_test.c delete mode 100644 software/FusionX/src/z80drv/MZ700/z80menu.c delete mode 100755 software/FusionX/src/z80drv/MZ700/z80menu.h delete mode 100644 software/FusionX/src/z80drv/MZ80A/optparse.h delete mode 100644 software/FusionX/src/z80drv/MZ80A/z80menu.c delete mode 100755 software/FusionX/src/z80drv/MZ80A/z80menu.h create mode 100644 software/FusionX/src/z80drv/src/emumz.c rename software/FusionX/src/z80drv/{MZ80A => src}/k64fcpu.c (99%) rename software/FusionX/src/z80drv/{MZ2000 => src}/optparse.h (100%) rename software/FusionX/src/z80drv/{MZ80A => src}/sharpbiter.c (84%) rename software/FusionX/src/z80drv/{MZ80A => src}/sharpmz.c (100%) rename software/FusionX/src/z80drv/{MZ80A => src}/tzpu.h (99%) rename software/FusionX/src/z80drv/{MZ80A => src}/z80ctrl.c (96%) rename software/FusionX/src/z80drv/{MZ80A => src}/z80driver.c (71%) rename software/FusionX/src/z80drv/{MZ80A => src}/z80driver.h (78%) rename software/FusionX/src/z80drv/{MZ80A => src}/z80io.c (100%) rename software/FusionX/src/z80drv/{MZ80A => src}/z80io.h (79%) rename software/FusionX/src/z80drv/{MZ80A => src}/z80io_test.c (98%) rename software/FusionX/src/z80drv/{MZ2000 => src}/z80menu.c (100%) rename software/FusionX/src/z80drv/{MZ2000 => src}/z80menu.h (100%) create mode 100644 software/FusionX/src/z80drv/src/z80vhw_mz2000.c create mode 100644 software/FusionX/src/z80drv/src/z80vhw_mz700.c create mode 100644 software/FusionX/src/z80drv/src/z80vhw_mz80a.c create mode 100644 software/FusionX/src/z80drv/src/z80vhw_pcw.c rename software/FusionX/src/z80drv/{MZ80A => src}/z80vhw_rfs.c (94%) rename software/FusionX/src/z80drv/{MZ80A => src}/z80vhw_tzpu.c (84%) diff --git a/.gitignore b/.gitignore index ad660cc2b..6bc873f0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1719,4 +1719,27 @@ software/FusionX/src/z80drv/z80drv.ko software/FusionX/src/z80drv/z80drv.mod.c software/FusionX/host/ software/FusionX/src/z80drv/sharpbiter +CPLD/v1.0/MZ2000/tzpuFusionX.vhd.bak +CPLD/v1.0/MZ2000/tzpuFusionX.vhd.new +CPLD/v1.0/MZ700/tzpuFusionX.vhd.old +CPLD/v1.0/MZ80A/tzpuFusionX.vhd.bak +software/FusionX/src/ttymz/Makefile.kmod +software/FusionX/src/ttymz/z80io.h.old +software/FusionX/src/z80drv/1 +software/FusionX/src/z80drv/2 +software/FusionX/src/z80drv/Makefile.mod +software/FusionX/src/z80drv/Makefile.x +software/FusionX/src/z80drv/Z80.c.old3 +software/FusionX/src/z80drv/Zeta.old3/ +software/FusionX/src/z80drv/integral.h +software/FusionX/src/z80drv/src.mz2000/ +software/FusionX/src/z80drv/src.mz80a/ +software/FusionX/src/z80drv/src.pcw/ +software/FusionX/src/z80drv/src/PCW8256_boot.hex +software/FusionX/src/z80drv/src/PCW8256_boot.rom +software/FusionX/src/z80drv/src/ccc +software/FusionX/src/z80drv/src/k64fcpu.c.hld +software/FusionX/src/z80drv/src/z80driver.c.bad +software/FusionX/src/z80drv/src/z80vhw_rfs.c.bad +CPLD/v1.0/MZ2000.old/ diff --git a/CPLD/v1.0/MZ2000/build/tzpuFusionX_MZ2000.qsf b/CPLD/v1.0/MZ2000/build/tzpuFusionX_MZ2000.qsf index bc46a3d6b..868e14bc4 100644 --- a/CPLD/v1.0/MZ2000/build/tzpuFusionX_MZ2000.qsf +++ b/CPLD/v1.0/MZ2000/build/tzpuFusionX_MZ2000.qsf @@ -127,10 +127,10 @@ set_location_assignment PIN_21 -to VSOM_RSV[1] # SOM Control Signals # =================== set_location_assignment PIN_28 -to VSOM_READY -set_location_assignment PIN_18 -to VSOM_LTSTATE +set_location_assignment PIN_19 -to VSOM_LTSTATE set_location_assignment PIN_27 -to VSOM_BUSRQ set_location_assignment PIN_26 -to VSOM_BUSACK -set_location_assignment PIN_19 -to VSOM_INT +set_location_assignment PIN_18 -to VSOM_INT set_location_assignment PIN_22 -to VSOM_NMI set_location_assignment PIN_25 -to VSOM_WAIT set_location_assignment PIN_23 -to VSOM_RESET diff --git a/CPLD/v1.0/MZ2000/tzpuFusionX.vhd b/CPLD/v1.0/MZ2000/tzpuFusionX.vhd index 75b688022..5617a2625 100644 --- a/CPLD/v1.0/MZ2000/tzpuFusionX.vhd +++ b/CPLD/v1.0/MZ2000/tzpuFusionX.vhd @@ -2,16 +2,17 @@ -- -- Name: tzpuFusionX.vhd -- Version: MZ-2000 --- Created: June 2020 +-- Created: Jan 2023 -- Author(s): Philip Smart -- Description: tzpuFusionX CPLD logic definition file. -- This module contains the definition of the tzpuFusionX project plus enhancements -- for the MZ-2000. -- -- Credits: --- Copyright: (c) 2018-22 Philip Smart +-- Copyright: (c) 2018-23 Philip Smart -- --- History: Oct 2022 - Initial write for the MZ-2000. +-- History: Jan 2023 v1.0 - Initial write for the MZ-2000. +-- Apr 2023 v1.1 - Updates from the PCW8256 development. -- --------------------------------------------------------------------------------------------------------- -- This source file is free software: you can redistribute it and-or modify @@ -128,7 +129,7 @@ end entity; architecture rtl of cpld512 is - -- Finite State Machine states. + -- Z80 Finite State Machine states. type SOMFSMState is ( IdleCycle, @@ -174,6 +175,14 @@ architecture rtl of cpld512 is BusReqCycle ); + -- Controller FSM states. + type CTRLFSMState is + ( + CTRLCMD_Idle, + CTRLCMD_ReadIOWrite, + CTRLCMD_ReadIOWrite_1 + ); + -- CPU Interface internal signals. signal Z80_BUSRQni : std_logic; signal Z80_INTni : std_logic; @@ -185,7 +194,6 @@ architecture rtl of cpld512 is signal Z80_HALTni : std_logic; signal Z80_M1ni : std_logic; signal Z80_RFSHni : std_logic; - signal Z80_DATAi : std_logic_vector(7 downto 0); signal Z80_BUSRQ_ACKni : std_logic; -- Internal CPU state control. @@ -203,9 +211,10 @@ architecture rtl of cpld512 is -- Refresh control. signal FSM_STATE : SOMFSMState := IdleCycle; - signal NEW_SPI_CMD : std_logic := '0'; + signal CTRL_STATE : CTRLFSMState := CTRLCMD_Idle; + signal NEW_SPI_DATA : std_logic := '0'; signal VCPU_CS_EDGE : std_logic_vector(1 downto 0) := "11"; - signal AUTOREFRESH_CNT : integer range 0 to 7; + signal AUTOREFRESH_CNT : integer range 0 to 63; signal FSM_STATUS : std_logic := '0'; signal FSM_CHECK_WAIT : std_logic := '0'; signal FSM_WAIT_ACTIVE : std_logic := '0'; @@ -229,11 +238,12 @@ architecture rtl of cpld512 is signal SPI_RX_SREG : std_logic_vector(7 downto 0); -- RX Shift Register signal SPI_TX_DATA : std_logic_vector(31 downto 0); -- Data to transmit. signal SPI_RX_DATA : std_logic_vector(31 downto 0); -- Data received. - signal SPI_BIT_CNT : integer range 0 to 16; -- Count of bits tx/rx'd. + signal SPI_BIT_CNT : integer range 0 to 7; -- Count of bits tx/rx'd. signal SPI_FRAME_CNT : integer range 0 to 4; -- Number of frames received (8bit chunks). -- SPI Command interface. signal SOM_CMD : std_logic_vector(7 downto 0) := (others => '0'); + signal SOM_PARAM_CNT : integer range 0 to 3; signal SPI_NEW_DATA : std_logic; signal SPI_PROCESSING : std_logic; signal SPI_CPU_ADDR : std_logic_vector(15 downto 0) := (others => '0'); @@ -275,7 +285,7 @@ begin -- On the second edge, if occurring within 1 second of the first, the PM_RESET signal to the SOM is triggered, held low for 1 second, -- forcing the SOM to reboot. SYSRESET: process( Z80_CLKi, Z80_RESETn ) - variable timer1 : integer range 0 to 354000 := 0; + variable timer1 : integer range 0 to 400000 := 0; variable timer100 : integer range 0 to 10 := 0; variable timerPMReset : integer range 0 to 10 := 0; variable resetCount : integer range 0 to 3 := 0; @@ -311,7 +321,7 @@ begin end if; -- 100ms interval. - if(timer1 = 354000) then + if(timer1 = 400000) then timer100 := timer100 + 1; if(timer100 >= 10) then @@ -340,8 +350,11 @@ begin -- SPI Slave input. Receive command and data from the SOM. SPI_INPUT : process(VSOM_SPI_CLK) begin + -- Chip Select inactive, disable process and reset control flags. + if(VSOM_SPI_CSn = '1') then + -- SPI_CLK_POLARITY='0' => falling edge; SPI_CLK_POLARITY='1' => rising edge - if(VSOM_SPI_CLK'event and VSOM_SPI_CLK = SPI_CLK_POLARITY) then + elsif(VSOM_SPI_CLK'event and VSOM_SPI_CLK = SPI_CLK_POLARITY) then if(VSOM_SPI_CSn = '0') then SPI_RX_SREG <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; @@ -356,7 +369,7 @@ begin elsif(SPI_SHIFT_EN = '1' and SPI_FRAME_CNT = 3 and SPI_BIT_CNT = 0) then SPI_RX_DATA(23 downto 16) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; - elsif(SPI_SHIFT_EN = '1' and SPI_FRAME_CNT = 4 and SPI_BIT_CNT = 0) then + elsif(SPI_FRAME_CNT = 4 and SPI_BIT_CNT = 0) then SPI_RX_DATA(31 downto 24) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; end if; end if; @@ -366,18 +379,22 @@ begin -- SPI Slave output. Return the current data set as selected by the input signals XACT. SPI_OUTPUT : process(VSOM_SPI_CLK,VSOM_SPI_CSn,SPI_TX_DATA) begin + -- Chip Select inactive, disable process and reset control flags. if(VSOM_SPI_CSn = '1') then SPI_SHIFT_EN <= '0'; - SPI_BIT_CNT <= 15; + SPI_BIT_CNT <= 7; -- SPI_CLK_POLARITY='0' => falling edge; SPI_CLK_POLARITY='1' => risinge edge elsif(VSOM_SPI_CLK'event and VSOM_SPI_CLK = not SPI_CLK_POLARITY) then + -- Each clock reset the shift enable and done flag in preparation for the next cycle. SPI_SHIFT_EN <= '1'; + -- Bit count decrements to detect when last bit of byte is sent. if(SPI_BIT_CNT > 0) then SPI_BIT_CNT <= SPI_BIT_CNT - 1; end if; + -- Shift out the next bit. VSOM_SPI_MISO <= SPI_TX_SREG(6); SPI_TX_SREG <= SPI_TX_SREG(5 downto 0) & '0'; @@ -392,6 +409,7 @@ begin SPI_FRAME_CNT<= 1; VSOM_SPI_MISO<= SPI_TX_DATA(7); SPI_TX_SREG <= SPI_TX_DATA(6 downto 0); + -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. elsif(SPI_FRAME_CNT = 1) then SPI_FRAME_CNT<= 2; VSOM_SPI_MISO<= SPI_TX_DATA(15); @@ -400,13 +418,14 @@ begin SPI_FRAME_CNT<= 3; VSOM_SPI_MISO<= SPI_TX_DATA(23); SPI_TX_SREG <= SPI_TX_DATA(22 downto 16); - else + elsif(SPI_FRAME_CNT = 3) then -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. SPI_FRAME_CNT<= 4; VSOM_SPI_MISO<= SPI_TX_DATA(31); SPI_TX_SREG <= SPI_TX_DATA(30 downto 24); + else + SPI_FRAME_CNT<= 0; end if; - SPI_BIT_CNT <= 7; end if; end if; @@ -424,6 +443,7 @@ begin AUTOREFRESH <= '0'; SPI_LOOPBACK_TEST <= '0'; SOM_CMD <= (others => '0'); + SOM_PARAM_CNT <= 0; SPI_CPU_ADDR <= (others => '0'); SPI_NEW_DATA <= '0'; @@ -432,6 +452,7 @@ begin -- for 8bit, 16bit and 32bit transmissions. -- The packet is formatted as follows: -- + -- < SPI_CPU_ADDR > < SPI_CPU_DATA >< SOM_CMD> -- < SPI_FRAME_CNT=4 >< SPI_FRAME=3 > < SPI_FRAME_CNT=2 >< SPI_FRAME_CNT=1> -- < 16bit Z80 Address > < Z80 Data > -- 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 @@ -441,66 +462,110 @@ begin -- elsif(VSOM_SPI_CSn'event and VSOM_SPI_CSn = '1') then - -- Command is always located in the upper byte of frame 1. - SOM_CMD <= SPI_RX_DATA(7 downto 0); + -- If active, decrement parameter count. Parameters sent after a command are not considered as commands. + if(SOM_PARAM_CNT > 0) then + SOM_PARAM_CNT <= SOM_PARAM_CNT - 1; + end if; - -- Toggle flag to indicate new data arrived. - SPI_NEW_DATA <= not SPI_NEW_DATA; + -- Process if command, store parameters. + if(SOM_PARAM_CNT = 0) then + -- Command is always located in the upper byte of frame 1. + SOM_CMD <= SPI_RX_DATA(7 downto 0); - -- Process the command. Some commands require the FSM, others can be serviced immediately. - case SPI_RX_DATA(7 downto 0) is + -- Toggle flag to indicate new data arrived. + SPI_NEW_DATA <= not SPI_NEW_DATA; - -- Z80XACT(0..15): Setup data and address as provided then execute FSM. - when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" | -- Fetch - X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" | -- Write - X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" | -- Read - X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" | -- WriteIO - X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" | -- ReadIO - X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" | -- - X"40" | X"41" | X"42" | X"43" | X"44" | X"45" | X"46" | X"47" | -- - X"48" | X"49" | X"4A" | X"4B" | X"4C" | X"4D" | X"4E" | X"4F" => + -- Process the command. Some commands require the FSM, others can be serviced immediately. + case SPI_RX_DATA(7 downto 0) is - -- Direct address set. - if(SPI_FRAME_CNT = 4) then - SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); - else - -- if(SPI_CPU_ADDR >= X"D010" and SPI_CPU_ADDR < X"D020") then - -- SPI_CPU_ADDR <= std_logic_vector(X"D020" + unsigned(SPI_RX_DATA(2 downto 0))); - -- else - SPI_CPU_ADDR <= std_logic_vector(unsigned(SPI_CPU_ADDR) + unsigned(SPI_RX_DATA(2 downto 0))); - -- end if; - end if; + -- Z80XACT(0..15): Setup data and address as provided then execute FSM. + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" | -- Fetch + X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" | -- Write + X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" | -- Read + X"48" | X"49" | X"4A" | X"4B" | X"4C" | X"4D" | X"4E" | X"4F" => - if(SPI_FRAME_CNT > 1) then - SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); - end if; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= std_logic_vector(unsigned(SPI_CPU_ADDR) + unsigned(SPI_RX_DATA(2 downto 0))); + end if; - -- SETSIGSET1: Set control lines directly. - when X"F0" => - VIDEO_SRCi <= SPI_RX_DATA(8); - MONO_VIDEO_SRCi <= SPI_RX_DATA(9); - AUDIO_SRC_Li <= SPI_RX_DATA(10); - AUDIO_SRC_Ri <= SPI_RX_DATA(11); - VBUS_ENi <= SPI_RX_DATA(12); - VGA_BLANKn <= not SPI_RX_DATA(13); + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; - -- Enable auto refresh DRAM cycle. - when X"F1" => - AUTOREFRESH <= '1'; + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => -- WriteIO - -- Disable auto refresh DRAM cycle. - when X"F2" => - AUTOREFRESH <= '0'; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; - -- SETLOOPBACK: Enable loopback test mode. - when X"FE" => - SPI_LOOPBACK_TEST<= '1'; + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; - -- No action, called to retrieve status. - when X"00" | X"FF" => + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" | -- ReadIO + X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => -- ReadIO-Write - when others => - end case; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= X"00" & SPI_RX_DATA(15 downto 8); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; + + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; + + -- ReadIO-Write, Read-WriteIO commands require target address, so indicate parameter needed. + if(SPI_RX_DATA(7 downto 0) >= X"38" and SPI_RX_DATA(7 downto 0) < X"40") then + SOM_PARAM_CNT <= 1; + end if; + + -- SETSIGSET1: Set control lines directly. + when X"F0" => + VIDEO_SRCi <= SPI_RX_DATA(8); + MONO_VIDEO_SRCi <= SPI_RX_DATA(9); + AUDIO_SRC_Li <= SPI_RX_DATA(10); + AUDIO_SRC_Ri <= SPI_RX_DATA(11); + VBUS_ENi <= SPI_RX_DATA(12); + VGA_BLANKn <= not SPI_RX_DATA(13); + + -- Enable auto refresh DRAM cycle. + when X"F1" => + AUTOREFRESH <= '1'; + + -- Disable auto refresh DRAM cycle. + when X"F2" => + AUTOREFRESH <= '0'; + + -- SETLOOPBACK: Enable loopback test mode. + when X"FE" => + SPI_LOOPBACK_TEST<= '1'; + + -- No action, called to retrieve status. + when X"00" | X"FF" => + + when others => + end case; + else + -- Store parameter depending on number of frames, either ADDR+DATA, ADDR or DATA. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= SPI_RX_DATA(15 downto 0); + else + SPI_CPU_DATA <= SPI_RX_DATA(7 downto 0); + end if; + end if; end if; end process; @@ -561,9 +626,9 @@ begin CPU_DATA_EN <= '0'; CPU_DATA_IN <= (others => '0'); REFRESH_ADDR <= (others => '0'); - AUTOREFRESH_CNT <= 7; + AUTOREFRESH_CNT <= 63; IPAR <= (others => '0'); - NEW_SPI_CMD <= '0'; + NEW_SPI_DATA <= '0'; VCPU_CS_EDGE <= "11"; SPI_PROCESSING <= '0'; @@ -580,98 +645,12 @@ begin -- New command, set flag as the signal is only 1 clock wide. if(SPI_LOOPBACK_TEST = '0' and VSOM_SPI_CSn = '1' and VCPU_CS_EDGE = "01") then - NEW_SPI_CMD <= '1'; + NEW_SPI_DATA <= '1'; end if; - -- Whenever we return to Idle or just prior to Refresh from a Fetch cycle set all control signals to default. - if(FSM_STATE = IdleCycle or FSM_STATE = RefreshCycle) then - CPU_DATA_EN <= '0'; - Z80_MREQni <= '1'; - Z80_IORQni <= '1'; - Z80_RDni <= '1'; - Z80_WRni <= '1'; - Z80_M1ni <= '1'; - FSM_STATUS <= '0'; - Z80_RFSHni <= '1'; - - -- Auto DRAM refresh cycles. When enabled, every 7 host clock cycles, a 2 cycle refresh period commences. - -- This will be overriden if the SPI receives a new command. - -- - if AUTOREFRESH = '1' and FSM_STATE = IdleCycle then - AUTOREFRESH_CNT <= AUTOREFRESH_CNT - 1; - if(AUTOREFRESH_CNT = 0) then - FSM_STATE <= RefreshCycle_3; - end if; - end if; - end if; - - -- If new command has been given and the FSM enters idle state, load up new command for processing. - if(NEW_SPI_CMD = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then - NEW_SPI_CMD <= '0'; - - -- Store new address and data for this command. - CPU_ADDR <= SPI_CPU_ADDR; - if(SPI_CPU_DATA /= CPU_DATA_OUT) then - CPU_DATA_OUT <= SPI_CPU_DATA; - end if; - - -- Process the SOM command. The SPI_REGISTER executes non FSM commands and stores FSM - -- data prior to this execution block, which fires 1 cycle later on the same control clock. - -- If the command is not for the FSM then the READY mechanism is held for one - -- further cycle before going inactive. - case SOM_CMD is - when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" => - -- Initiate a Fetch Cycle. - FSM_STATE <= FetchCycle; - - when X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" => - - -- Set the Z80 data bus value and initiate a Write Cycle. - FSM_STATE <= WriteCycle; - - when X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" => - -- Initiate a Read Cycle. - FSM_STATE <= ReadCycle; - - when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => - -- Set the Z80 data bus value and initiate an IO Write Cycle. - -- The SOM should set 15:8 to the B register value. - FSM_STATE <= WriteIOCycle; - - when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" => - -- Initiate a Read IO Cycle. - FSM_STATE <= ReadIOCycle; - - when X"50" => - -- Register a Halt state. - FSM_STATE <= HaltCycle; - - when X"51" => - -- Initiate a refresh cycle. - FSM_STATE <= RefreshCycle_3; - - when X"E0" => - -- Initiate a Halt Cycle. - FSM_STATE <= HaltCycle; - - -- Set the Refresh Address register. - when X"E1" => - REFRESH_ADDR <= CPU_DATA_OUT; - - -- Set the Interrupt Page Address Register. - when X"E2" => - IPAR <= CPU_DATA_OUT; - - when others => - end case; - - -- Toggle the processing flag to negate the new data flag. Used to indicate device is busy. - if(SPI_NEW_DATA /= SPI_PROCESSING) then - SPI_PROCESSING <= not SPI_PROCESSING; - end if; - - -- FSM Status bit. When processing a command it is set, cleared when idle. Used by SOM to determine command completion. - FSM_STATUS <= '1'; + -- Decrement refresh counter on each Z80 cycle, thus when idle and time expired, a refresh can be performed within parameters (256 cycles in 4ms). + if(AUTOREFRESH = '1' and AUTOREFRESH_CNT /= 0 and Z80_CLK_RE = '1') then + AUTOREFRESH_CNT <= AUTOREFRESH_CNT - 1; end if; -- Refresh status bit. Indicates a Refresh cycle is under way. @@ -686,7 +665,143 @@ begin FSM_WAIT_ACTIVE <= '1'; end if; - -- On each Z80 edge we advance the FSM to recreate the Z80 external signal transactions. + -- Whenever we return to Idle or just prior to Refresh from a Fetch cycle set all control signals to default. + if((FSM_STATE = IdleCycle or FSM_STATE = RefreshCycle) and Z80_CLK_RE = '1') then + CPU_DATA_EN <= '0'; + Z80_MREQni <= '1'; + Z80_IORQni <= '1'; + Z80_RDni <= '1'; + Z80_WRni <= '1'; + Z80_M1ni <= '1'; + FSM_STATUS <= '0'; + Z80_RFSHni <= '1'; + + -- Auto DRAM refresh cycles. When enabled, every 15.6us a refresh period commences. + -- This period may be extended if the SPI receives a new command. + -- + if AUTOREFRESH = '1' and FSM_STATE = IdleCycle then + if(AUTOREFRESH_CNT = 0) then + FSM_STATE <= RefreshCycle_3; + FSM_STATUS<= '1'; + -- 4164 DRAM = 256 cycles in 4ms. + AUTOREFRESH_CNT <= 63; + end if; + end if; + end if; + + -------------------------------------------------------------------------------------------- + -- CPLD Macro Command Finite State Machine. + -------------------------------------------------------------------------------------------- + + -- Controller state machine. + -- When idle, accept and process SPI commands which can lead to a controller macro command. + case CTRL_STATE is + + when CTRLCMD_Idle => + -- If new command has been given and the FSM enters idle state, load up new command for processing. + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + + -- Store new address and data for this command. + if(NEW_SPI_DATA = '1') then + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= SPI_CPU_DATA; + end if; + + -- Process the SOM command. The SPI_REGISTER executes non FSM commands and stores FSM + -- data prior to this execution block, which fires 1 cycle later on the same control clock. + -- If the command is not for the FSM then the READY mechanism is held for one + -- further cycle before going inactive. + case SOM_CMD is + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" => + -- Initiate a Fetch Cycle. + FSM_STATE <= FetchCycle; + + when X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" => + + -- Set the Z80 data bus value and initiate a Write Cycle. + FSM_STATE <= WriteCycle; + + when X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" => + -- Initiate a Read Cycle. + FSM_STATE <= ReadCycle; + + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => + -- Set the Z80 data bus value and initiate an IO Write Cycle. + -- The SOM should set 15:8 to the B register value. + FSM_STATE <= WriteIOCycle; + + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" => + -- Initiate a Read IO Cycle. + FSM_STATE <= ReadIOCycle; + + when X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => + -- Initiate a read IO write memory cycle via the controller FSM. + CTRL_STATE <= CTRLCMD_ReadIOWrite; + FSM_STATE <= ReadIOCycle; + + when X"50" => + -- Register a Halt state. + FSM_STATE <= HaltCycle; + + when X"51" => + -- Initiate a refresh cycle. + FSM_STATE <= RefreshCycle_3; + + when X"E0" => + -- Initiate a Halt Cycle. + FSM_STATE <= HaltCycle; + + -- Set the Refresh Address register. + when X"E1" => + REFRESH_ADDR <= CPU_DATA_OUT; + + -- Set the Interrupt Page Address Register. + when X"E2" => + IPAR <= CPU_DATA_OUT; + + when others => + end case; + + -- Toggle the processing flag to negate the new data flag. Used to indicate device is busy. + if(SPI_NEW_DATA /= SPI_PROCESSING) then + SPI_PROCESSING<= not SPI_PROCESSING; + end if; + + -- Clear new data flag ready for next cmd/param transfer. + NEW_SPI_DATA <= '0'; + + -- FSM Status bit. When processing a command it is set, cleared when idle. Used by SOM to determine command completion. + FSM_STATUS <= '1'; + end if; + + when CTRLCMD_ReadIOWrite => + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + NEW_SPI_DATA <= '0'; + CPU_DATA_EN <= '0'; + Z80_IORQni <= '1'; + Z80_RDni <= '1'; + Z80_RFSHni <= '1'; + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= CPU_DATA_IN; + FSM_STATE <= WriteCycle; + CTRL_STATE <= CTRLCMD_ReadIOWrite_1; + end if; + + when CTRLCMD_ReadIOWrite_1 => + if(FSM_STATE = WriteCycle_31) then + CTRL_STATE <= CTRLCMD_Idle; + end if; + + when others => + CTRL_STATE <= CTRLCMD_Idle; + + end case; + + -------------------------------------------------------------------------------------------- + -- Z80 Finite State Machine. + -------------------------------------------------------------------------------------------- + + -- On each Z80 edge we advance the Z80 FSM to recreate the Z80 external signal transactions. if(Z80_CLK_TGL = '1') then -- The FSM advances to the next stage on each Z80 edge unless in Idle state. @@ -704,7 +819,7 @@ begin when IdleCycle => CPU_LAST_T_STATE <= '1'; - -- FSM_STATE <= IdleCycle; + FSM_STATUS <= '0'; ----------------------------- -- Z80 Fetch Cycle. @@ -727,7 +842,7 @@ begin when FetchCycle_30 => -- To meet the timing diagrams, just after Rising edge on T3 clear signals. Data wont be available until - -- a short period before the falling edge of T3 (could be an MZ-2000 design restriction or the Z80 timing diagrams are a bit out). + -- a short period before the falling edge of T3. FSM_STATE <= RefreshCycle; ----------------------------- @@ -736,18 +851,18 @@ begin when RefreshCycle => -- Latch data from mainboard. CPU_DATA_IN <= Z80_DATA; - FSM_STATUS <= '0'; Z80_RFSHni <= '0'; when RefreshCycle_11 => -- Falling edge of T3 activates the MREQ line. Z80_MREQni <= '0'; + FSM_STATUS <= '0'; when RefreshCycle_20 => when RefreshCycle_21 => Z80_MREQni <= '1'; - REFRESH_ADDR(6 downto 0) <= REFRESH_ADDR(6 downto 0) + 1; + REFRESH_ADDR <= REFRESH_ADDR + 1; FSM_STATE <= IdleCycle; when RefreshCycle_3 => @@ -769,7 +884,7 @@ begin when WriteCycle_21 => Z80_WRni <= '0'; if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= WriteCycle_20; + FSM_STATE <= WriteCycle_20; end if; when WriteCycle_30 => @@ -794,7 +909,7 @@ begin when ReadCycle_21 => if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= ReadCycle_20; + FSM_STATE <= ReadCycle_20; end if; when ReadCycle_30 => @@ -805,7 +920,6 @@ begin FSM_STATUS <= '0'; FSM_STATE <= IdleCycle; - ----------------------------- -- Z80 IO Write Cycle. ----------------------------- @@ -825,7 +939,7 @@ begin when WriteIOCycle_31 => if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= WriteIOCycle_20; + FSM_STATE <= WriteIOCycle_30; end if; when WriteIOCycle_40 => @@ -854,7 +968,7 @@ begin when ReadIOCycle_31 => if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= ReadIOCycle_20; + FSM_STATE <= ReadIOCycle_30; end if; when ReadIOCycle_40 => @@ -902,8 +1016,6 @@ begin Z80_DATA <= CPU_DATA_OUT when Z80_BUSRQ_ACKni = '1' and CPU_DATA_EN = '1' else (others => 'Z'); - -- Z80_DATAi <= Z80_DATA when Z80_RDn = '0' - -- else (others => '1'); Z80_RDn <= Z80_RDni when Z80_BUSRQ_ACKni = '1' else 'Z'; Z80_WRn <= Z80_WRni when Z80_BUSRQ_ACKni = '1' @@ -944,7 +1056,7 @@ begin -- Signal mirrors. VSOM_READY <= '0' when FSM_STATUS='1' or SPI_NEW_DATA /= SPI_PROCESSING - else '1'; -- FSM Ready (1), Busy (0) + else '1'; -- FSM Ready (1), Busy (0) VSOM_LTSTATE <= '1' when CPU_LAST_T_STATE = '1' -- Last T-State in current cycle. else '0'; VSOM_BUSRQ <= not Z80_BUSRQn; -- Host device requesting Z80 Bus. @@ -981,5 +1093,9 @@ begin -- DAC clocks. --VGA_PXL_CLK <= CLK_50M; MONO_PXL_CLK <= VGA_PXL_CLK; + + -- Currently unassigned. + VGA_COLR <= '0'; + MONO_RSV <= '0'; end architecture; diff --git a/CPLD/v1.0/MZ700/build/tzpuFusionX_MZ700.qsf b/CPLD/v1.0/MZ700/build/tzpuFusionX_MZ700.qsf index 934556f84..c7266a7c0 100644 --- a/CPLD/v1.0/MZ700/build/tzpuFusionX_MZ700.qsf +++ b/CPLD/v1.0/MZ700/build/tzpuFusionX_MZ700.qsf @@ -127,10 +127,10 @@ set_location_assignment PIN_21 -to VSOM_RSV[1] # SOM Control Signals # =================== set_location_assignment PIN_28 -to VSOM_READY -set_location_assignment PIN_18 -to VSOM_LTSTATE +set_location_assignment PIN_19 -to VSOM_LTSTATE set_location_assignment PIN_27 -to VSOM_BUSRQ set_location_assignment PIN_26 -to VSOM_BUSACK -set_location_assignment PIN_19 -to VSOM_INT +set_location_assignment PIN_18 -to VSOM_INT set_location_assignment PIN_22 -to VSOM_NMI set_location_assignment PIN_25 -to VSOM_WAIT set_location_assignment PIN_23 -to VSOM_RESET diff --git a/CPLD/v1.0/MZ700/tzpuFusionX.vhd b/CPLD/v1.0/MZ700/tzpuFusionX.vhd index e031e0519..f20cd260f 100644 --- a/CPLD/v1.0/MZ700/tzpuFusionX.vhd +++ b/CPLD/v1.0/MZ700/tzpuFusionX.vhd @@ -2,16 +2,17 @@ -- -- Name: tzpuFusionX.vhd -- Version: MZ-700 --- Created: June 2020 +-- Created: Jan 2023 -- Author(s): Philip Smart -- Description: tzpuFusionX CPLD logic definition file. -- This module contains the definition of the tzpuFusionX project plus enhancements --- for the MZ700. +-- for the MZ-700. -- -- Credits: --- Copyright: (c) 2018-22 Philip Smart +-- Copyright: (c) 2018-23 Philip Smart -- --- History: Oct 2022 - Initial write for the MZ-700. +-- History: Jan 2023 v1.0 - Initial write for the MZ-700. +-- Apr 2023 v1.1 - Updates from the PCW8256 development. -- --------------------------------------------------------------------------------------------------------- -- This source file is free software: you can redistribute it and-or modify @@ -128,42 +129,62 @@ end entity; architecture rtl of cpld512 is - -- Finite State Machine states. + -- Z80 Finite State Machine states. type SOMFSMState is ( IdleCycle, FetchCycle, - FetchCycle_2, - FetchCycle_3, - FetchCycle_4, + FetchCycle_11, + FetchCycle_20, + FetchCycle_21, + FetchCycle_30, RefreshCycle, - RefreshCycle_1, - RefreshCycle_2, + RefreshCycle_11, + RefreshCycle_20, + RefreshCycle_21, RefreshCycle_3, WriteCycle, - WriteCycle_2, - WriteCycle_3, - WriteCycle_4, + WriteCycle_11, + WriteCycle_20, + WriteCycle_21, + WriteCycle_30, + WriteCycle_31, ReadCycle, - ReadCycle_2, - ReadCycle_3, - ReadCycle_4, + ReadCycle_11, + ReadCycle_20, + ReadCycle_21, + ReadCycle_30, + ReadCycle_31, WriteIOCycle, - WriteIOCycle_2, - WriteIOCycle_3, - WriteIOCycle_4, - WriteIOCycle_5, + WriteIOCycle_11, + WriteIOCycle_20, + WriteIOCycle_21, + WriteIOCycle_30, + WriteIOCycle_31, + WriteIOCycle_40, + WriteIOCycle_41, ReadIOCycle, - ReadIOCycle_2, - ReadIOCycle_3, - ReadIOCycle_4, + ReadIOCycle_11, + ReadIOCycle_20, + ReadIOCycle_21, + ReadIOCycle_30, + ReadIOCycle_31, + ReadIOCycle_40, + ReadIOCycle_41, HaltCycle, BusReqCycle ); + -- Controller FSM states. + type CTRLFSMState is + ( + CTRLCMD_Idle, + CTRLCMD_ReadIOWrite, + CTRLCMD_ReadIOWrite_1 + ); + -- CPU Interface internal signals. signal Z80_BUSRQni : std_logic; - signal Z80_BUSAKni : std_logic; signal Z80_INTni : std_logic; signal Z80_IORQni : std_logic; signal Z80_MREQni : std_logic; @@ -171,10 +192,8 @@ architecture rtl of cpld512 is signal Z80_RDni : std_logic; signal Z80_WRni : std_logic; signal Z80_HALTni : std_logic; - signal Z80_WAITni : std_logic; signal Z80_M1ni : std_logic; signal Z80_RFSHni : std_logic; - signal Z80_DATAi : std_logic_vector(7 downto 0); signal Z80_BUSRQ_ACKni : std_logic; -- Internal CPU state control. @@ -192,22 +211,24 @@ architecture rtl of cpld512 is -- Refresh control. signal FSM_STATE : SOMFSMState := IdleCycle; - signal NEW_SPI_CMD : std_logic := '0'; + signal CTRL_STATE : CTRLFSMState := CTRLCMD_Idle; + signal NEW_SPI_DATA : std_logic := '0'; signal VCPU_CS_EDGE : std_logic_vector(1 downto 0) := "11"; - signal AUTOREFRESH_CNT : integer range 0 to 7; + signal AUTOREFRESH_CNT : integer range 0 to 63; signal FSM_STATUS : std_logic := '0'; + signal FSM_CHECK_WAIT : std_logic := '0'; + signal FSM_WAIT_ACTIVE : std_logic := '0'; signal RFSH_STATUS : std_logic := '0'; signal REFRESH_ADDR : std_logic_vector(7 downto 0); signal IPAR : std_logic_vector(7 downto 0); signal AUTOREFRESH : std_logic; -- Clock edge detection and flagging. - signal Z80_CLK_RE : std_logic := '0'; - signal Z80_CLK_FE : std_logic := '0'; - signal Z80_CLK_LEVEL : std_logic := '0'; - signal Z80_CLK_LAST : std_logic := '0'; - signal CPU_T_STATE : integer range 0 to 5; - signal CPU_T_STATES : integer range 0 to 5; + signal Z80_CLKi : std_logic; + signal Z80_CLK_LAST : std_logic_vector(1 downto 0); + signal Z80_CLK_RE : std_logic; + signal Z80_CLK_FE : std_logic; + signal Z80_CLK_TGL : std_logic; signal CPU_T_STATE_SET : integer range 0 to 5; signal CPU_LAST_T_STATE : std_logic := '0'; @@ -217,11 +238,12 @@ architecture rtl of cpld512 is signal SPI_RX_SREG : std_logic_vector(7 downto 0); -- RX Shift Register signal SPI_TX_DATA : std_logic_vector(31 downto 0); -- Data to transmit. signal SPI_RX_DATA : std_logic_vector(31 downto 0); -- Data received. - signal SPI_BIT_CNT : integer range 0 to 16; -- Count of bits tx/rx'd. + signal SPI_BIT_CNT : integer range 0 to 7; -- Count of bits tx/rx'd. signal SPI_FRAME_CNT : integer range 0 to 4; -- Number of frames received (8bit chunks). -- SPI Command interface. signal SOM_CMD : std_logic_vector(7 downto 0) := (others => '0'); + signal SOM_PARAM_CNT : integer range 0 to 3; signal SPI_NEW_DATA : std_logic; signal SPI_PROCESSING : std_logic; signal SPI_CPU_ADDR : std_logic_vector(15 downto 0) := (others => '0'); @@ -256,7 +278,7 @@ begin -- On the first edge the VSOM_RESETn signal is set which allows the SOM to see it and the Z80 application to enter a reset state. -- On the second edge, if occurring within 1 second of the first, the PM_RESET signal to the SOM is triggered, held low for 1 second, -- forcing the SOM to reboot. - SYSRESET: process( Z80_CLK, Z80_RESETn ) + SYSRESET: process( Z80_CLKi, Z80_RESETn ) variable timer1 : integer range 0 to 354000 := 0; variable timer100 : integer range 0 to 10 := 0; variable timerPMReset : integer range 0 to 10 := 0; @@ -264,7 +286,7 @@ begin variable cpuResetEdge : std_logic := '1'; begin -- Synchronous on the HOST Clock. - if(rising_edge(Z80_CLK)) then + if(rising_edge(Z80_CLKi)) then -- If the PM Reset timer is active, count down and on expiry release the SOM PM_RESET line. if(timerPMReset = 0 and PM_RESETi = '1') then @@ -322,8 +344,11 @@ begin -- SPI Slave input. Receive command and data from the SOM. SPI_INPUT : process(VSOM_SPI_CLK) begin + -- Chip Select inactive, disable process and reset control flags. + if(VSOM_SPI_CSn = '1') then + -- SPI_CLK_POLARITY='0' => falling edge; SPI_CLK_POLARITY='1' => rising edge - if(VSOM_SPI_CLK'event and VSOM_SPI_CLK = SPI_CLK_POLARITY) then + elsif(VSOM_SPI_CLK'event and VSOM_SPI_CLK = SPI_CLK_POLARITY) then if(VSOM_SPI_CSn = '0') then SPI_RX_SREG <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; @@ -338,7 +363,7 @@ begin elsif(SPI_SHIFT_EN = '1' and SPI_FRAME_CNT = 3 and SPI_BIT_CNT = 0) then SPI_RX_DATA(23 downto 16) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; - elsif(SPI_SHIFT_EN = '1' and SPI_FRAME_CNT = 4 and SPI_BIT_CNT = 0) then + elsif(SPI_FRAME_CNT = 4 and SPI_BIT_CNT = 0) then SPI_RX_DATA(31 downto 24) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; end if; end if; @@ -348,18 +373,22 @@ begin -- SPI Slave output. Return the current data set as selected by the input signals XACT. SPI_OUTPUT : process(VSOM_SPI_CLK,VSOM_SPI_CSn,SPI_TX_DATA) begin + -- Chip Select inactive, disable process and reset control flags. if(VSOM_SPI_CSn = '1') then SPI_SHIFT_EN <= '0'; - SPI_BIT_CNT <= 15; + SPI_BIT_CNT <= 7; -- SPI_CLK_POLARITY='0' => falling edge; SPI_CLK_POLARITY='1' => risinge edge elsif(VSOM_SPI_CLK'event and VSOM_SPI_CLK = not SPI_CLK_POLARITY) then + -- Each clock reset the shift enable and done flag in preparation for the next cycle. SPI_SHIFT_EN <= '1'; + -- Bit count decrements to detect when last bit of byte is sent. if(SPI_BIT_CNT > 0) then SPI_BIT_CNT <= SPI_BIT_CNT - 1; end if; + -- Shift out the next bit. VSOM_SPI_MISO <= SPI_TX_SREG(6); SPI_TX_SREG <= SPI_TX_SREG(5 downto 0) & '0'; @@ -374,6 +403,7 @@ begin SPI_FRAME_CNT<= 1; VSOM_SPI_MISO<= SPI_TX_DATA(7); SPI_TX_SREG <= SPI_TX_DATA(6 downto 0); + -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. elsif(SPI_FRAME_CNT = 1) then SPI_FRAME_CNT<= 2; VSOM_SPI_MISO<= SPI_TX_DATA(15); @@ -382,13 +412,14 @@ begin SPI_FRAME_CNT<= 3; VSOM_SPI_MISO<= SPI_TX_DATA(23); SPI_TX_SREG <= SPI_TX_DATA(22 downto 16); - else + elsif(SPI_FRAME_CNT = 3) then -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. SPI_FRAME_CNT<= 4; VSOM_SPI_MISO<= SPI_TX_DATA(31); SPI_TX_SREG <= SPI_TX_DATA(30 downto 24); + else + SPI_FRAME_CNT<= 0; end if; - SPI_BIT_CNT <= 7; end if; end if; @@ -406,6 +437,7 @@ begin AUTOREFRESH <= '1'; SPI_LOOPBACK_TEST <= '0'; SOM_CMD <= (others => '0'); + SOM_PARAM_CNT <= 0; SPI_CPU_ADDR <= (others => '0'); SPI_NEW_DATA <= '0'; @@ -414,125 +446,153 @@ begin -- for 8bit, 16bit and 32bit transmissions. -- The packet is formatted as follows: -- + -- < SPI_CPU_ADDR > < SPI_CPU_DATA >< SOM_CMD> -- < SPI_FRAME_CNT=4 >< SPI_FRAME=3 > < SPI_FRAME_CNT=2 >< SPI_FRAME_CNT=1> - -- < 16bit Z80 Address > < Z80 Data >< Command = 00.. 80 > - -- 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 2 0 + -- < 16bit Z80 Address > < Z80 Data > + -- 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 -- - -- < > < Data >< Command = F0.. FF > - -- 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 2 0 + -- < > < Data > + -- 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 -- elsif(VSOM_SPI_CSn'event and VSOM_SPI_CSn = '1') then - -- Command is always located in the upper byte of frame 1. - SOM_CMD <= SPI_RX_DATA(7 downto 0); + -- If active, decrement parameter count. Parameters sent after a command are not considered as commands. + if(SOM_PARAM_CNT > 0) then + SOM_PARAM_CNT <= SOM_PARAM_CNT - 1; + end if; - -- Toggle flag to indicate new data arrived. - SPI_NEW_DATA <= not SPI_NEW_DATA; + -- Process if command, store parameters. + if(SOM_PARAM_CNT = 0) then + -- Command is always located in the upper byte of frame 1. + SOM_CMD <= SPI_RX_DATA(7 downto 0); - -- Process the command. Some commands require the FSM, others can be serviced immediately. - case SPI_RX_DATA(7 downto 0) is + -- Toggle flag to indicate new data arrived. + SPI_NEW_DATA <= not SPI_NEW_DATA; - -- Z80XACT(0..15): Setup data and address as provided then execute FSM. - when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" | -- Fetch - X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" | -- Write - X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" | -- Read - X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" | -- WriteIO - X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" | -- ReadIO - X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" | -- - X"40" | X"41" | X"42" | X"43" | X"44" | X"45" | X"46" | X"47" | -- - X"48" | X"49" | X"4A" | X"4B" | X"4C" | X"4D" | X"4E" | X"4F" => + -- Process the command. Some commands require the FSM, others can be serviced immediately. + case SPI_RX_DATA(7 downto 0) is - -- Direct address set. - if(SPI_FRAME_CNT = 4) then - SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); - else - SPI_CPU_ADDR <= std_logic_vector(unsigned(CPU_ADDR) + unsigned(SPI_RX_DATA(2 downto 0))); - end if; + -- Z80XACT(0..15): Setup data and address as provided then execute FSM. + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" | -- Fetch + X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" | -- Write + X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" | -- Read + X"48" | X"49" | X"4A" | X"4B" | X"4C" | X"4D" | X"4E" | X"4F" => - if(SPI_FRAME_CNT > 1) then - SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); - end if; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= std_logic_vector(unsigned(SPI_CPU_ADDR) + unsigned(SPI_RX_DATA(2 downto 0))); + end if; - -- SETSIGSET1: Set control lines directly. - when X"F0" => - VIDEO_SRCi <= SPI_RX_DATA(8); - MONO_VIDEO_SRCi <= SPI_RX_DATA(9); - AUDIO_SRC_Li <= SPI_RX_DATA(10); - AUDIO_SRC_Ri <= SPI_RX_DATA(11); - VBUS_ENi <= SPI_RX_DATA(12); - VGA_BLANKn <= not SPI_RX_DATA(13); + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; - -- Enable auto refresh DRAM cycle. - when X"F1" => - AUTOREFRESH <= '1'; + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => -- WriteIO - -- Disable auto refresh DRAM cycle. - when X"F2" => - AUTOREFRESH <= '0'; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; - -- SETLOOPBACK: Enable loopback test mode. - when X"FE" => - SPI_LOOPBACK_TEST <= '1'; + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; - -- No action, called to retrieve status. - when X"00" | X"FF" => + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" | -- ReadIO + X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => -- ReadIO-Write - when others => - end case; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= X"00" & SPI_RX_DATA(15 downto 8); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; + + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; + + -- ReadIO-Write, Read-WriteIO commands require target address, so indicate parameter needed. + if(SPI_RX_DATA(7 downto 0) >= X"38" and SPI_RX_DATA(7 downto 0) < X"40") then + SOM_PARAM_CNT <= 1; + end if; + + -- SETSIGSET1: Set control lines directly. + when X"F0" => + VIDEO_SRCi <= SPI_RX_DATA(8); + MONO_VIDEO_SRCi <= SPI_RX_DATA(9); + AUDIO_SRC_Li <= SPI_RX_DATA(10); + AUDIO_SRC_Ri <= SPI_RX_DATA(11); + VBUS_ENi <= SPI_RX_DATA(12); + VGA_BLANKn <= not SPI_RX_DATA(13); + + -- Enable auto refresh DRAM cycle. + when X"F1" => + AUTOREFRESH <= '1'; + + -- Disable auto refresh DRAM cycle. + when X"F2" => + AUTOREFRESH <= '0'; + + -- SETLOOPBACK: Enable loopback test mode. + when X"FE" => + SPI_LOOPBACK_TEST<= '1'; + + -- No action, called to retrieve status. + when X"00" | X"FF" => + + when others => + end case; + else + -- Store parameter depending on number of frames, either ADDR+DATA, ADDR or DATA. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= SPI_RX_DATA(15 downto 0); + else + SPI_CPU_DATA <= SPI_RX_DATA(7 downto 0); + end if; + end if; end if; end process; - -- Z80 Clock edge detection. Each falling and rising edge sets a flag for 1 cycle along with the current clock level flag. - -- On expiry of the defined T-States, a flag is raised to indicate end of cycle. - CLKEDGE: process( CLK_50M, Z80_RESETn, Z80_CLK, CPU_T_STATE_SET ) + -- Process to detect the Z80 Clock edges. Each edge is used to recreate the Z80 external signals. + -- + Z80CLK: process( CLK_50M, Z80_CLKi, Z80_RESETn ) begin if(Z80_RESETn = '0') then - Z80_CLK_RE <= '0'; - Z80_CLK_FE <= '0'; - Z80_CLK_LEVEL <= '0'; - CPU_T_STATE <= 1; - CPU_LAST_T_STATE <= '0'; + Z80_CLK_RE <= '1'; + Z80_CLK_FE <= '1'; + Z80_CLK_TGL <= '1'; elsif(rising_edge(CLK_50M)) then - -- Host clock edge detection. For Z80 operations the clock edge is required to meet host and original timings. - if(Z80_CLK = '1' and Z80_CLK_LAST = '0') then - Z80_CLK_RE <= '1'; - Z80_CLK_LEVEL <= '1'; - - -- T state increments on each rising edge unless WAIT asserted. - if(Z80_WAITni = '1') then - -- Wrap around to next T-State if limit for last transaction type reached. - if(CPU_T_STATE = CPU_T_STATES) then - CPU_T_STATE <= 1; - CPU_LAST_T_STATE <= '0'; - else - CPU_T_STATE <= CPU_T_STATE + 1; - end if; + -- Default is to clear the signals, only active for 1 clock period. + Z80_CLK_RE <= '0'; + Z80_CLK_FE <= '0'; + Z80_CLK_TGL <= '0'; - end if; + -- Rising Edge. + if(Z80_CLKi = '1' and Z80_CLK_LAST = "00") then + Z80_CLK_RE <= '1'; - elsif(Z80_CLK = '0' and Z80_CLK_LAST = '1') then - Z80_CLK_FE <= '1'; - Z80_CLK_LEVEL <= '0'; + -- Toggle on rising edge is delayed by one clock to allow time for command to be decoded. + elsif(Z80_CLKi = '1' and Z80_CLK_LAST = "01") then + Z80_CLK_TGL <= '1'; - -- Falling edge of the last T-State sets the Last T-State flag. This gives the SOM sufficient time to detect it - -- and execute necessary code before next T-State commences. - if(CPU_T_STATE = CPU_T_STATES) then - CPU_LAST_T_STATE <= '1'; - end if; - else - Z80_CLK_RE <= '0'; - Z80_CLK_FE <= '0'; + -- Falling Edge. + elsif(Z80_CLKi = '0' and Z80_CLK_LAST = "11") then + Z80_CLK_FE <= '1'; + Z80_CLK_TGL <= '1'; end if; - - -- Mechanism to set the T-State. - if(CPU_T_STATE_SET /= 0) then - CPU_T_STATE <= CPU_T_STATE_SET; - CPU_LAST_T_STATE <= '0'; - end if; - - Z80_CLK_LAST <= Z80_CLK; + Z80_CLK_LAST <= Z80_CLK_LAST(0) & Z80_CLKi; end if; end process; @@ -541,10 +601,9 @@ begin -- A command processor, based on an FSM concept, to process requested commands, ie. Z80 Write, Z80 Read etc. -- The external signal SOM_CMD_EN, when set, indicates a new command available in SOM_CMD. -- - SOMFSM: process( CLK_50M, Z80_RESETn ) + SOMFSM: process( CLK_50M, Z80_CLKi, Z80_RESETn ) begin if(Z80_RESETn = '0') then - Z80_BUSAKni <= '1'; Z80_IORQni <= '1'; Z80_MREQni <= '1'; Z80_RDni <= '1'; @@ -553,25 +612,22 @@ begin Z80_M1ni <= '1'; Z80_RFSHni <= '1'; Z80_BUSRQ_ACKni <= '1'; + FSM_CHECK_WAIT <= '0'; + FSM_WAIT_ACTIVE <= '0'; FSM_STATUS <= '0'; + FSM_STATE <= IdleCycle; RFSH_STATUS <= '0'; CPU_DATA_EN <= '0'; CPU_DATA_IN <= (others => '0'); REFRESH_ADDR <= (others => '0'); - AUTOREFRESH_CNT <= 7; + AUTOREFRESH_CNT <= 63; IPAR <= (others => '0'); - FSM_STATE <= IdleCycle; - NEW_SPI_CMD <= '0'; + NEW_SPI_DATA <= '0'; VCPU_CS_EDGE <= "11"; - CPU_T_STATES <= 3; - CPU_T_STATE_SET <= 0; SPI_PROCESSING <= '0'; elsif(rising_edge(CLK_50M)) then - -- Setup of T State is one cycle wide. - CPU_T_STATE_SET <= 0; - -- Bus request mechanism. If an externel Bus Request comes in and the FSM is idle, run the Bus Request command which -- suspends processing and tri-states the bus. if(Z80_BUSRQn = '0' and Z80_BUSRQ_ACKni = '1' and FSM_STATE = IdleCycle) then @@ -583,11 +639,28 @@ begin -- New command, set flag as the signal is only 1 clock wide. if(SPI_LOOPBACK_TEST = '0' and VSOM_SPI_CSn = '1' and VCPU_CS_EDGE = "01") then - NEW_SPI_CMD <= '1'; + NEW_SPI_DATA <= '1'; + end if; + + -- Decrement refresh counter on each Z80 cycle, thus when idle and time expired, a refresh can be performed within parameters (256 cycles in 4ms). + if(AUTOREFRESH = '1' and AUTOREFRESH_CNT /= 0 and Z80_CLK_RE = '1') then + AUTOREFRESH_CNT <= AUTOREFRESH_CNT - 1; + end if; + + -- Refresh status bit. Indicates a Refresh cycle is under way. + if FSM_STATE = RefreshCycle or FSM_STATE = RefreshCycle_11 or FSM_STATE = RefreshCycle_20 or FSM_STATE = RefreshCycle_21 or FSM_STATE = RefreshCycle_3 then + RFSH_STATUS <= '1'; + else + RFSH_STATUS <= '0'; + end if; + + -- If we are in a WAIT sampling 1/2 cycle and wait goes active, set the state so we repeat the full clock cycle by winding back 2 places. + if(FSM_CHECK_WAIT = '1' and Z80_WAITn = '0' and Z80_CLK_TGL = '0') then + FSM_WAIT_ACTIVE <= '1'; end if; -- Whenever we return to Idle or just prior to Refresh from a Fetch cycle set all control signals to default. - if(FSM_STATE = IdleCycle or FSM_STATE = RefreshCycle) then + if((FSM_STATE = IdleCycle or FSM_STATE = RefreshCycle) and Z80_CLK_RE = '1') then CPU_DATA_EN <= '0'; Z80_MREQni <= '1'; Z80_IORQni <= '1'; @@ -597,316 +670,337 @@ begin FSM_STATUS <= '0'; Z80_RFSHni <= '1'; - -- Auto DRAM refresh cycles. When enabled, every 7 host clock cycles, a 2 cycle refresh period commences. - -- This will be overriden if the SPI receives a new command. + -- Auto DRAM refresh cycles. When enabled, every 15.6us a refresh period commences. + -- This period may be extended if the SPI receives a new command. -- if AUTOREFRESH = '1' and FSM_STATE = IdleCycle then - AUTOREFRESH_CNT <= AUTOREFRESH_CNT - 1; if(AUTOREFRESH_CNT = 0) then FSM_STATE <= RefreshCycle_3; + FSM_STATUS<= '1'; + -- 4164 DRAM = 256 cycles in 4ms. + AUTOREFRESH_CNT <= 63; end if; end if; end if; - -- If new command has been given and the FSM enters idle state, load up new command for processing. - if(NEW_SPI_CMD = '1' and FSM_STATE = IdleCycle) then - NEW_SPI_CMD <= '0'; + -------------------------------------------------------------------------------------------- + -- CPLD Macro Command Finite State Machine. + -------------------------------------------------------------------------------------------- - -- Store new address and data for this command. - CPU_ADDR <= SPI_CPU_ADDR; - if(SPI_CPU_DATA /= CPU_DATA_OUT) then - CPU_DATA_OUT <= SPI_CPU_DATA; + -- Controller state machine. + -- When idle, accept and process SPI commands which can lead to a controller macro command. + case CTRL_STATE is + + when CTRLCMD_Idle => + -- If new command has been given and the FSM enters idle state, load up new command for processing. + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + + -- Store new address and data for this command. + if(NEW_SPI_DATA = '1') then + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= SPI_CPU_DATA; + end if; + + -- Process the SOM command. The SPI_REGISTER executes non FSM commands and stores FSM + -- data prior to this execution block, which fires 1 cycle later on the same control clock. + -- If the command is not for the FSM then the READY mechanism is held for one + -- further cycle before going inactive. + case SOM_CMD is + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" => + -- Initiate a Fetch Cycle. + FSM_STATE <= FetchCycle; + + when X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" => + + -- Set the Z80 data bus value and initiate a Write Cycle. + FSM_STATE <= WriteCycle; + + when X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" => + -- Initiate a Read Cycle. + FSM_STATE <= ReadCycle; + + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => + -- Set the Z80 data bus value and initiate an IO Write Cycle. + -- The SOM should set 15:8 to the B register value. + FSM_STATE <= WriteIOCycle; + + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" => + -- Initiate a Read IO Cycle. + FSM_STATE <= ReadIOCycle; + + when X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => + -- Initiate a read IO write memory cycle via the controller FSM. + CTRL_STATE <= CTRLCMD_ReadIOWrite; + FSM_STATE <= ReadIOCycle; + + when X"50" => + -- Register a Halt state. + FSM_STATE <= HaltCycle; + + when X"51" => + -- Initiate a refresh cycle. + FSM_STATE <= RefreshCycle_3; + + when X"E0" => + -- Initiate a Halt Cycle. + FSM_STATE <= HaltCycle; + + -- Set the Refresh Address register. + when X"E1" => + REFRESH_ADDR <= CPU_DATA_OUT; + + -- Set the Interrupt Page Address Register. + when X"E2" => + IPAR <= CPU_DATA_OUT; + + when others => + end case; + + -- Toggle the processing flag to negate the new data flag. Used to indicate device is busy. + if(SPI_NEW_DATA /= SPI_PROCESSING) then + SPI_PROCESSING<= not SPI_PROCESSING; + end if; + + -- Clear new data flag ready for next cmd/param transfer. + NEW_SPI_DATA <= '0'; + + -- FSM Status bit. When processing a command it is set, cleared when idle. Used by SOM to determine command completion. + FSM_STATUS <= '1'; + end if; + + when CTRLCMD_ReadIOWrite => + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + NEW_SPI_DATA <= '0'; + CPU_DATA_EN <= '0'; + Z80_IORQni <= '1'; + Z80_RDni <= '1'; + Z80_RFSHni <= '1'; + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= CPU_DATA_IN; + FSM_STATE <= WriteCycle; + CTRL_STATE <= CTRLCMD_ReadIOWrite_1; + end if; + + when CTRLCMD_ReadIOWrite_1 => + if(FSM_STATE = WriteCycle_31) then + CTRL_STATE <= CTRLCMD_Idle; + end if; + + when others => + CTRL_STATE <= CTRLCMD_Idle; + + end case; + + -------------------------------------------------------------------------------------------- + -- Z80 Finite State Machine. + -------------------------------------------------------------------------------------------- + + -- On each Z80 edge we advance the Z80 FSM to recreate the Z80 external signal transactions. + if(Z80_CLK_TGL = '1') then + + -- The FSM advances to the next stage on each Z80 edge unless in Idle state. + if(FSM_STATE /= IdleCycle) then + FSM_STATE <= SOMFSMState'val(SOMFSMState'POS(FSM_STATE)+1); end if; - -- Process the SOM command. The SPI_REGISTER executes non FSM commands and stores FSM - -- data prior to this execution block, which fires 1 cycle later on the same control clock. - -- If the command is not for the FSM then the READY mechanism is held for one - -- further cycle before going inactive. - case SOM_CMD is - when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" => - -- Initiate a Fetch Cycle. - FSM_STATE <= FetchCycle; + -- Half cycle expired so we dont check the Z80 wait again. + FSM_CHECK_WAIT <= '0'; + FSM_WAIT_ACTIVE <= '0'; - when X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" => + -- FSM to implement all the required Z80 cycles. + -- + case FSM_STATE is - -- Set the Z80 data bus value and initiate a Write Cycle. - FSM_STATE <= WriteCycle; + when IdleCycle => + CPU_LAST_T_STATE <= '1'; + FSM_STATUS <= '0'; - when X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" => - -- Initiate a Read Cycle. - FSM_STATE <= ReadCycle; + ----------------------------- + -- Z80 Fetch Cycle. + ----------------------------- + when FetchCycle => + Z80_M1ni <= '0'; - when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => - -- Set the Z80 data bus value and initiate an IO Write Cycle. - -- The SOM should set 15:8 to the B register value. - FSM_STATE <= WriteIOCycle; + when FetchCycle_11 => + Z80_M1ni <= '0'; + Z80_MREQni <= '0'; + Z80_RDni <= '0'; - when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" => - -- Initiate a Read IO Cycle. - FSM_STATE <= ReadIOCycle; + when FetchCycle_20 => + FSM_CHECK_WAIT <= '1'; - when X"50" => - -- Register a Halt state. - FSM_STATE <= HaltCycle; + when FetchCycle_21 => + if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then + FSM_STATE <= FetchCycle_20; + end if; - when X"51" => - -- Initiate a refresh cycle. - FSM_STATE <= RefreshCycle_3; + when FetchCycle_30 => + -- To meet the timing diagrams, just after Rising edge on T3 clear signals. Data wont be available until + -- a short period before the falling edge of T3. + FSM_STATE <= RefreshCycle; - when X"E0" => - -- Initiate a Halt Cycle. - FSM_STATE <= HaltCycle; + ----------------------------- + -- Z80 Refresh Cycle. + ----------------------------- + when RefreshCycle => + -- Latch data from mainboard. + CPU_DATA_IN <= Z80_DATA; + Z80_RFSHni <= '0'; - -- Set the Refresh Address register. - when X"E1" => - REFRESH_ADDR <= CPU_DATA_OUT; + when RefreshCycle_11 => + -- Falling edge of T3 activates the MREQ line. + Z80_MREQni <= '0'; + FSM_STATUS <= '0'; - -- Set the Interrupt Page Address Register. - when X"E2" => - IPAR <= CPU_DATA_OUT; + when RefreshCycle_20 => + + when RefreshCycle_21 => + Z80_MREQni <= '1'; + REFRESH_ADDR <= REFRESH_ADDR + 1; + FSM_STATE <= IdleCycle; + + when RefreshCycle_3 => + Z80_RFSHni <= '0'; + FSM_STATE <= RefreshCycle_11; + + ----------------------------- + -- Z80 Write Cycle. + ----------------------------- + when WriteCycle => + + when WriteCycle_11 => + Z80_MREQni <= '0'; + CPU_DATA_EN <= '1'; + + when WriteCycle_20 => + FSM_CHECK_WAIT <= '1'; + + when WriteCycle_21 => + Z80_WRni <= '0'; + if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then + FSM_STATE <= WriteCycle_20; + end if; + + when WriteCycle_30 => + + when WriteCycle_31 => + FSM_STATUS <= '0'; + Z80_MREQni <= '1'; + Z80_WRni <= '1'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 Read Cycle. + ----------------------------- + when ReadCycle => + + when ReadCycle_11 => + Z80_MREQni <= '0'; + Z80_RDni <= '0'; + + when ReadCycle_20 => + FSM_CHECK_WAIT <= '1'; + + when ReadCycle_21 => + if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then + FSM_STATE <= ReadCycle_20; + end if; + + when ReadCycle_30 => + + when ReadCycle_31 => + -- Latch data from mainboard. + CPU_DATA_IN <= Z80_DATA; + FSM_STATUS <= '0'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 IO Write Cycle. + ----------------------------- + when WriteIOCycle => + + when WriteIOCycle_11 => + CPU_DATA_EN <= '1'; + + when WriteIOCycle_20 => + Z80_IORQni <= '0'; + Z80_WRni <= '0'; + + when WriteIOCycle_21 => + + when WriteIOCycle_30 => + FSM_CHECK_WAIT <= '1'; + + when WriteIOCycle_31 => + if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then + FSM_STATE <= WriteIOCycle_30; + end if; + + when WriteIOCycle_40 => + + when WriteIOCycle_41 => + FSM_STATUS <= '0'; + Z80_IORQni <= '1'; + Z80_WRni <= '1'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 IO Read Cycle. + ----------------------------- + when ReadIOCycle => + + when ReadIOCycle_11 => + + when ReadIOCycle_20 => + Z80_IORQni <= '0'; + Z80_RDni <= '0'; + + when ReadIOCycle_21 => + + when ReadIOCycle_30 => + FSM_CHECK_WAIT <= '1'; + + when ReadIOCycle_31 => + if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then + FSM_STATE <= ReadIOCycle_30; + end if; + + when ReadIOCycle_40 => + + when ReadIOCycle_41 => + -- Latch data from mainboard. + CPU_DATA_IN <= Z80_DATA; + FSM_STATUS <= '0'; + + -- IORQ/RD are deactivated at idle giving 1 clock to latch the data in. + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Halt Request. + ----------------------------- + when HaltCycle => + Z80_HALTni <= '0'; + FSM_STATUS <= '0'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 Bus Request. + ----------------------------- + when BusReqCycle => + Z80_BUSRQ_ACKni <= '0'; + FSM_STATE <= IdleCycle; when others => + FSM_STATE <= IdleCycle; end case; - - -- Toggle the processing flag to negate the new data flag. Used to indicate device is busy. - if(SPI_NEW_DATA /= SPI_PROCESSING) then - SPI_PROCESSING <= not SPI_PROCESSING; - end if; - - -- FSM Status bit. When processing a command it is set, cleared when idle. Used by SOM to determine command completion. - FSM_STATUS <= '1'; end if; - -- Refresh status bit. Indicates a Refresh cycle is under way. - if FSM_STATE = RefreshCycle or FSM_STATE = RefreshCycle_1 or FSM_STATE = RefreshCycle_2 or FSM_STATE = RefreshCycle_3 then - RFSH_STATUS <= '1'; - else - RFSH_STATUS <= '0'; - end if; - - -- FSM to implement all the required Z80 cycles. - -- - case FSM_STATE is - when IdleCycle => - - ----------------------------- - -- Z80 Fetch Cycle. - ----------------------------- - when FetchCycle => - if(Z80_CLK_RE = '1' or Z80_CLK_LEVEL = '1') then - CPU_T_STATE_SET<= 1; - CPU_T_STATES <= 4; - Z80_M1ni <= '0'; - FSM_STATE <= FetchCycle_2; - end if; - - when FetchCycle_2 => - if(Z80_CLK_FE = '1' and CPU_T_STATE = 1) then - Z80_MREQni <= '0'; - Z80_RDni <= '0'; - FSM_STATE <= FetchCycle_3; - end if; - - when FetchCycle_3 => - if(CPU_T_STATE = 2 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - FSM_STATE <= FetchCycle_4; - end if; - - when FetchCycle_4 => - -- To meet the timing diagrams, just after Rising edge on T3 clear signals. Data wont be available until - -- a short period before the falling edge of T3 (could be an MZ-700 design restriction or the Z80 timing diagrams are a bit out). - if(CPU_T_STATE = 3 and Z80_CLK_RE = '1' and Z80_WAITni = '1') then - FSM_STATE <= RefreshCycle; - end if; - - ----------------------------- - -- Z80 Refresh Cycle. - ----------------------------- - when RefreshCycle => - -- Latch data from mainboard. - CPU_DATA_IN <= Z80_DATA; - FSM_STATUS <= '0'; - Z80_RFSHni <= '0'; - FSM_STATE <= RefreshCycle_1; - - when RefreshCycle_1 => - -- Falling edge of T3 activates the MREQ line. - if(Z80_CLK_FE = '1' and CPU_T_STATE = 3) then - Z80_MREQni <= '0'; - FSM_STATE <= RefreshCycle_2; - end if; - - when RefreshCycle_2 => - if(Z80_CLK_FE = '1' and CPU_T_STATE = 4) then - Z80_MREQni <= '1'; - end if; - if(Z80_MREQni = '1' and CPU_T_STATE = 4) then - REFRESH_ADDR(6 downto 0) <= REFRESH_ADDR(6 downto 0) + 1; - FSM_STATE <= IdleCycle; - end if; - - when RefreshCycle_3 => - CPU_T_STATE_SET <= 3; - CPU_T_STATES <= 4; - Z80_RFSHni <= '0'; - FSM_STATE <= RefreshCycle_1; - - ----------------------------- - -- Z80 Write Cycle. - ----------------------------- - when WriteCycle => - FSM_STATUS <= '0'; - if(Z80_CLK_RE = '1' or Z80_CLK_LEVEL = '1') then - CPU_T_STATE_SET<= 1; - CPU_T_STATES <= 3; - FSM_STATE <= WriteCycle_2; - end if; - - when WriteCycle_2 => - if(Z80_CLK_FE = '1' and CPU_T_STATE = 1) then - Z80_MREQni <= '0'; - CPU_DATA_EN <= '1'; - FSM_STATE <= WriteCycle_3; - end if; - - when WriteCycle_3 => - if(CPU_T_STATE = 2 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - Z80_WRni <= '0'; - FSM_STATE <= WriteCycle_4; - end if; - - when WriteCycle_4 => - if(CPU_T_STATE = 3 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - Z80_MREQni <= '1'; - Z80_WRni <= '1'; - FSM_STATE <= IdleCycle; - end if; - - - ----------------------------- - -- Z80 Read Cycle. - ----------------------------- - when ReadCycle => - if(Z80_CLK_RE = '1' or Z80_CLK_LEVEL = '1') then - CPU_T_STATE_SET<= 1; - CPU_T_STATES <= 3; - FSM_STATE <= ReadCycle_2; - end if; - - when ReadCycle_2 => - if(Z80_CLK_FE = '1' and CPU_T_STATE = 1) then - Z80_MREQni <= '0'; - Z80_RDni <= '0'; - FSM_STATE <= ReadCycle_3; - end if; - - when ReadCycle_3 => - if(CPU_T_STATE = 2 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - FSM_STATE <= ReadCycle_4; - end if; - - when ReadCycle_4 => - if(CPU_T_STATE = 3 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - -- Latch data from mainboard. - CPU_DATA_IN <= Z80_DATA; - FSM_STATUS <= '0'; - - -- MREQ/RD are deactivated at idle giving 1 clock to latch the data in. - FSM_STATE <= IdleCycle; - end if; - - - ----------------------------- - -- Z80 IO Write Cycle. - ----------------------------- - when WriteIOCycle => - FSM_STATUS <= '0'; - if(Z80_CLK_RE = '1' or Z80_CLK_LEVEL = '1') then - CPU_T_STATE_SET<= 1; - CPU_T_STATES <= 4; - FSM_STATE <= WriteIOCycle_2; - end if; - - when WriteIOCycle_2 => - if(Z80_CLK_FE = '1') then - CPU_DATA_EN <= '1'; - FSM_STATE <= WriteIOCycle_3; - end if; - - when WriteIOCycle_3 => - if(Z80_CLK_FE = '1' and CPU_T_STATE = 2) then - Z80_IORQni <= '0'; - Z80_WRni <= '0'; - FSM_STATE <= WriteIOCycle_4; - end if; - - when WriteIOCycle_4 => - -- Add automatic Wait State (called T3 here but actually TW). - if(CPU_T_STATE = 3 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - FSM_STATE <= WriteIOCycle_5; - end if; - - when WriteIOCycle_5 => - if(CPU_T_STATE = 4 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - Z80_IORQni <= '1'; - Z80_WRni <= '1'; - FSM_STATE <= IdleCycle; - end if; - - ----------------------------- - -- Z80 IO Read Cycle. - ----------------------------- - when ReadIOCycle => - if(Z80_CLK_RE = '1' or Z80_CLK_LEVEL = '1') then - CPU_T_STATE_SET<= 1; - CPU_T_STATES <= 4; - FSM_STATE <= ReadIOCycle_2; - end if; - - when ReadIOCycle_2 => - if(Z80_CLK_FE = '1' and CPU_T_STATE = 2) then - Z80_IORQni <= '0'; - Z80_RDni <= '0'; - FSM_STATE <= ReadIOCycle_3; - end if; - - when ReadIOCycle_3 => - -- Add automatic Wait State. - if(CPU_T_STATE = 3 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - CPU_T_STATE_SET<= 2; - FSM_STATE <= ReadIOCycle_4; - end if; - - when ReadIOCycle_4 => - if(CPU_T_STATE = 4 and Z80_CLK_FE = '1' and Z80_WAITni = '1') then - -- Latch data from mainboard. - CPU_DATA_IN <= Z80_DATA; - FSM_STATUS <= '0'; - - -- IORA/RD are deactivated at idle giving 1 clock to latch the data in. - FSM_STATE <= IdleCycle; - end if; - - ----------------------------- - -- Halt Request. - ----------------------------- - when HaltCycle => - Z80_HALTni <= '0'; - FSM_STATUS <= '0'; - FSM_STATE <= IdleCycle; - - ----------------------------- - -- Z80 Bus Request. - ----------------------------- - when BusReqCycle => - Z80_BUSRQ_ACKni <= '0'; - FSM_STATE <= IdleCycle; - end case; - VCPU_CS_EDGE <= VCPU_CS_EDGE(0) & VSOM_SPI_CSn; end if; end process; + Z80_CLKi <= not Z80_CLK; + -- CPU Interface tri-state control based on acknowledged bus request. Z80_ADDR <= IPAR & REFRESH_ADDR when Z80_RFSHni = '0' else @@ -916,8 +1010,6 @@ begin Z80_DATA <= CPU_DATA_OUT when Z80_BUSRQ_ACKni = '1' and CPU_DATA_EN = '1' else (others => 'Z'); - -- Z80_DATAi <= Z80_DATA when Z80_RDn = '0' - -- else (others => '1'); Z80_RDn <= Z80_RDni when Z80_BUSRQ_ACKni = '1' else 'Z'; Z80_WRn <= Z80_WRni when Z80_BUSRQ_ACKni = '1' @@ -938,7 +1030,6 @@ begin -- CPU Interface single state input. Z80_NMIni <= Z80_NMIn; Z80_INTni <= Z80_INTn; - Z80_WAITni <= Z80_WAITn; Z80_BUSRQni <= Z80_BUSRQn; -- SOM Reset. @@ -947,18 +1038,19 @@ begin -- SOM to CPLD Interface. VSOM_DATA_OUT <= CPU_DATA_IN when VSOM_HBYTE = '1' else - FSM_STATUS & RFSH_STATUS & Z80_BUSRQ_ACKni & Z80_BUSRQn & Z80_INTn & Z80_NMIn & Z80_WAITn & Z80_RESETn when VSOM_HBYTE = '0' + FSM_STATUS & RFSH_STATUS & Z80_BUSRQ_ACKni & Z80_BUSRQni & Z80_INTni & Z80_NMIni & Z80_WAITn & Z80_RESETn when VSOM_HBYTE = '0' else (others => '0'); -- Loopback test, echo what was received. SPI_TX_DATA <= SPI_RX_DATA when SPI_LOOPBACK_TEST = '1' else - CPU_ADDR & CPU_DATA_IN & FSM_STATUS & RFSH_STATUS & Z80_BUSRQ_ACKni & Z80_BUSRQn & Z80_INTn & Z80_NMIn & Z80_WAITn & Z80_RESETn; + --CPU_ADDR & SOM_CMD & FSM_STATUS & RFSH_STATUS & std_logic_vector(to_unsigned(SOMFSMState'POS(FSM_STATE), 6)); + CPU_ADDR & CPU_DATA_IN & FSM_STATUS & RFSH_STATUS & Z80_BUSRQ_ACKni & Z80_BUSRQni & Z80_INTni & Z80_NMIni & Z80_WAITn & Z80_RESETn; -- Signal mirrors. VSOM_READY <= '0' when FSM_STATUS='1' or SPI_NEW_DATA /= SPI_PROCESSING - else '1'; -- FSM Ready (1), Busy (0) + else '1'; -- FSM Ready (1), Busy (0) VSOM_LTSTATE <= '1' when CPU_LAST_T_STATE = '1' -- Last T-State in current cycle. else '0'; VSOM_BUSRQ <= not Z80_BUSRQn; -- Host device requesting Z80 Bus. @@ -990,10 +1082,14 @@ begin -- Generate composite sync. VGA_CSYNCn <= VGA_HSYNCn xor not VGA_VSYNCn; - MONO_CSYNCn <= VGA_HSYNCn xor not VGA_VSYNCn; + MONO_CSYNCn <= not VGA_HSYNCn xor not VGA_VSYNCn; -- DAC clocks. --VGA_PXL_CLK <= CLK_50M; MONO_PXL_CLK <= VGA_PXL_CLK; + + -- Currently unassigned. + VGA_COLR <= '0'; + MONO_RSV <= '0'; end architecture; diff --git a/CPLD/v1.0/MZ80A/tzpuFusionX.vhd b/CPLD/v1.0/MZ80A/tzpuFusionX.vhd index 0b0875ef5..19021452d 100644 --- a/CPLD/v1.0/MZ80A/tzpuFusionX.vhd +++ b/CPLD/v1.0/MZ80A/tzpuFusionX.vhd @@ -15,6 +15,7 @@ -- Feb 2023 v1.1 - Updates, after numerous tests to try and speed up the Z80 transaction -- from SSD202 issuing a command to data being returned. Source now -- different to the MZ-700/MZ-2000 so will need back porting. +-- Apr 2023 v1.2 - Updated from the PCW8256 development. -- --------------------------------------------------------------------------------------------------------- -- This source file is free software: you can redistribute it and-or modify @@ -131,7 +132,7 @@ end entity; architecture rtl of cpld512 is - -- Finite State Machine states. + -- Z80 Finite State Machine states. type SOMFSMState is ( IdleCycle, @@ -177,6 +178,14 @@ architecture rtl of cpld512 is BusReqCycle ); + -- Controller FSM states. + type CTRLFSMState is + ( + CTRLCMD_Idle, + CTRLCMD_ReadIOWrite, + CTRLCMD_ReadIOWrite_1 + ); + -- CPU Interface internal signals. signal Z80_BUSRQni : std_logic; signal Z80_INTni : std_logic; @@ -188,7 +197,6 @@ architecture rtl of cpld512 is signal Z80_HALTni : std_logic; signal Z80_M1ni : std_logic; signal Z80_RFSHni : std_logic; - signal Z80_DATAi : std_logic_vector(7 downto 0); signal Z80_BUSRQ_ACKni : std_logic; -- Internal CPU state control. @@ -206,14 +214,15 @@ architecture rtl of cpld512 is -- Refresh control. signal FSM_STATE : SOMFSMState := IdleCycle; - signal NEW_SPI_CMD : std_logic := '0'; + signal CTRL_STATE : CTRLFSMState := CTRLCMD_Idle; + signal NEW_SPI_DATA : std_logic := '0'; signal VCPU_CS_EDGE : std_logic_vector(1 downto 0) := "11"; - signal AUTOREFRESH_CNT : integer range 0 to 7; + signal AUTOREFRESH_CNT : integer range 0 to 31; signal FSM_STATUS : std_logic := '0'; signal FSM_CHECK_WAIT : std_logic := '0'; signal FSM_WAIT_ACTIVE : std_logic := '0'; signal RFSH_STATUS : std_logic := '0'; - signal REFRESH_ADDR : std_logic_vector(7 downto 0); + signal REFRESH_ADDR : std_logic_vector(6 downto 0); signal IPAR : std_logic_vector(7 downto 0); signal AUTOREFRESH : std_logic; @@ -232,11 +241,12 @@ architecture rtl of cpld512 is signal SPI_RX_SREG : std_logic_vector(7 downto 0); -- RX Shift Register signal SPI_TX_DATA : std_logic_vector(31 downto 0); -- Data to transmit. signal SPI_RX_DATA : std_logic_vector(31 downto 0); -- Data received. - signal SPI_BIT_CNT : integer range 0 to 16; -- Count of bits tx/rx'd. + signal SPI_BIT_CNT : integer range 0 to 7; -- Count of bits tx/rx'd. signal SPI_FRAME_CNT : integer range 0 to 4; -- Number of frames received (8bit chunks). -- SPI Command interface. signal SOM_CMD : std_logic_vector(7 downto 0) := (others => '0'); + signal SOM_PARAM_CNT : integer range 0 to 3; signal SPI_NEW_DATA : std_logic; signal SPI_PROCESSING : std_logic; signal SPI_CPU_ADDR : std_logic_vector(15 downto 0) := (others => '0'); @@ -340,8 +350,11 @@ begin -- SPI Slave input. Receive command and data from the SOM. SPI_INPUT : process(VSOM_SPI_CLK) begin + -- Chip Select inactive, disable process and reset control flags. + if(VSOM_SPI_CSn = '1') then + -- SPI_CLK_POLARITY='0' => falling edge; SPI_CLK_POLARITY='1' => rising edge - if(VSOM_SPI_CLK'event and VSOM_SPI_CLK = SPI_CLK_POLARITY) then + elsif(VSOM_SPI_CLK'event and VSOM_SPI_CLK = SPI_CLK_POLARITY) then if(VSOM_SPI_CSn = '0') then SPI_RX_SREG <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; @@ -396,6 +409,7 @@ begin SPI_FRAME_CNT<= 1; VSOM_SPI_MISO<= SPI_TX_DATA(7); SPI_TX_SREG <= SPI_TX_DATA(6 downto 0); + -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. elsif(SPI_FRAME_CNT = 1) then SPI_FRAME_CNT<= 2; VSOM_SPI_MISO<= SPI_TX_DATA(15); @@ -404,13 +418,14 @@ begin SPI_FRAME_CNT<= 3; VSOM_SPI_MISO<= SPI_TX_DATA(23); SPI_TX_SREG <= SPI_TX_DATA(22 downto 16); - else + elsif(SPI_FRAME_CNT = 3) then -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. SPI_FRAME_CNT<= 4; VSOM_SPI_MISO<= SPI_TX_DATA(31); SPI_TX_SREG <= SPI_TX_DATA(30 downto 24); + else + SPI_FRAME_CNT<= 0; end if; - SPI_BIT_CNT <= 7; end if; end if; @@ -428,6 +443,7 @@ begin AUTOREFRESH <= '0'; SPI_LOOPBACK_TEST <= '0'; SOM_CMD <= (others => '0'); + SOM_PARAM_CNT <= 0; SPI_CPU_ADDR <= (others => '0'); SPI_NEW_DATA <= '0'; @@ -446,66 +462,110 @@ begin -- elsif(VSOM_SPI_CSn'event and VSOM_SPI_CSn = '1') then - -- Command is always located in the upper byte of frame 1. - SOM_CMD <= SPI_RX_DATA(7 downto 0); + -- If active, decrement parameter count. Parameters sent after a command are not considered as commands. + if(SOM_PARAM_CNT > 0) then + SOM_PARAM_CNT <= SOM_PARAM_CNT - 1; + end if; - -- Toggle flag to indicate new data arrived. - SPI_NEW_DATA <= not SPI_NEW_DATA; + -- Process if command, store parameters. + if(SOM_PARAM_CNT = 0) then + -- Command is always located in the upper byte of frame 1. + SOM_CMD <= SPI_RX_DATA(7 downto 0); - -- Process the command. Some commands require the FSM, others can be serviced immediately. - case SPI_RX_DATA(7 downto 0) is + -- Toggle flag to indicate new data arrived. + SPI_NEW_DATA <= not SPI_NEW_DATA; - -- Z80XACT(0..15): Setup data and address as provided then execute FSM. - when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" | -- Fetch - X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" | -- Write - X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" | -- Read - X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" | -- WriteIO - X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" | -- ReadIO - X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" | -- - X"40" | X"41" | X"42" | X"43" | X"44" | X"45" | X"46" | X"47" | -- - X"48" | X"49" | X"4A" | X"4B" | X"4C" | X"4D" | X"4E" | X"4F" => + -- Process the command. Some commands require the FSM, others can be serviced immediately. + case SPI_RX_DATA(7 downto 0) is - -- Direct address set. - if(SPI_FRAME_CNT = 4) then - SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); - else - -- if(SPI_CPU_ADDR >= X"D010" and SPI_CPU_ADDR < X"D020") then - -- SPI_CPU_ADDR <= std_logic_vector(X"D020" + unsigned(SPI_RX_DATA(2 downto 0))); - -- else - SPI_CPU_ADDR <= std_logic_vector(unsigned(SPI_CPU_ADDR) + unsigned(SPI_RX_DATA(2 downto 0))); - -- end if; - end if; + -- Z80XACT(0..15): Setup data and address as provided then execute FSM. + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" | -- Fetch + X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" | -- Write + X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" | -- Read + X"48" | X"49" | X"4A" | X"4B" | X"4C" | X"4D" | X"4E" | X"4F" => - if(SPI_FRAME_CNT > 1) then - SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); - end if; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= std_logic_vector(unsigned(SPI_CPU_ADDR) + unsigned(SPI_RX_DATA(2 downto 0))); + end if; - -- SETSIGSET1: Set control lines directly. - when X"F0" => - VIDEO_SRCi <= SPI_RX_DATA(8); - MONO_VIDEO_SRCi <= SPI_RX_DATA(9); - AUDIO_SRC_Li <= SPI_RX_DATA(10); - AUDIO_SRC_Ri <= SPI_RX_DATA(11); - VBUS_ENi <= SPI_RX_DATA(12); - VGA_BLANKn <= not SPI_RX_DATA(13); + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; - -- Enable auto refresh DRAM cycle. - when X"F1" => - AUTOREFRESH <= '1'; + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => -- WriteIO - -- Disable auto refresh DRAM cycle. - when X"F2" => - AUTOREFRESH <= '0'; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; - -- SETLOOPBACK: Enable loopback test mode. - when X"FE" => - SPI_LOOPBACK_TEST<= '1'; + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; - -- No action, called to retrieve status. - when X"00" | X"FF" => + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" | -- ReadIO + X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => -- ReadIO-Write - when others => - end case; + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= X"00" & SPI_RX_DATA(15 downto 8); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; + + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; + + -- ReadIO-Write, Read-WriteIO commands require target address, so indicate parameter needed. + if(SPI_RX_DATA(7 downto 0) >= X"38" and SPI_RX_DATA(7 downto 0) < X"40") then + SOM_PARAM_CNT <= 1; + end if; + + -- SETSIGSET1: Set control lines directly. + when X"F0" => + VIDEO_SRCi <= SPI_RX_DATA(8); + MONO_VIDEO_SRCi <= SPI_RX_DATA(9); + AUDIO_SRC_Li <= SPI_RX_DATA(10); + AUDIO_SRC_Ri <= SPI_RX_DATA(11); + VBUS_ENi <= SPI_RX_DATA(12); + VGA_BLANKn <= not SPI_RX_DATA(13); + + -- Enable auto refresh DRAM cycle. + when X"F1" => + AUTOREFRESH <= '1'; + + -- Disable auto refresh DRAM cycle. + when X"F2" => + AUTOREFRESH <= '0'; + + -- SETLOOPBACK: Enable loopback test mode. + when X"FE" => + SPI_LOOPBACK_TEST<= '1'; + + -- No action, called to retrieve status. + when X"00" | X"FF" => + + when others => + end case; + else + -- Store parameter depending on number of frames, either ADDR+DATA, ADDR or DATA. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= SPI_RX_DATA(15 downto 0); + else + SPI_CPU_DATA <= SPI_RX_DATA(7 downto 0); + end if; + end if; end if; end process; @@ -566,9 +626,9 @@ begin CPU_DATA_EN <= '0'; CPU_DATA_IN <= (others => '0'); REFRESH_ADDR <= (others => '0'); - AUTOREFRESH_CNT <= 7; + AUTOREFRESH_CNT <= 31; IPAR <= (others => '0'); - NEW_SPI_CMD <= '0'; + NEW_SPI_DATA <= '0'; VCPU_CS_EDGE <= "11"; SPI_PROCESSING <= '0'; @@ -585,99 +645,12 @@ begin -- New command, set flag as the signal is only 1 clock wide. if(SPI_LOOPBACK_TEST = '0' and VSOM_SPI_CSn = '1' and VCPU_CS_EDGE = "01") then - NEW_SPI_CMD <= '1'; + NEW_SPI_DATA <= '1'; end if; - -- Whenever we return to Idle or just prior to Refresh from a Fetch cycle set all control signals to default. - if((FSM_STATE = IdleCycle or FSM_STATE = RefreshCycle) and Z80_CLK_TGL = '1') then - CPU_DATA_EN <= '0'; - Z80_MREQni <= '1'; - Z80_IORQni <= '1'; - Z80_RDni <= '1'; - Z80_WRni <= '1'; - Z80_M1ni <= '1'; - FSM_STATUS <= '0'; - Z80_RFSHni <= '1'; - - -- Auto DRAM refresh cycles. When enabled, every 7 host clock cycles, a 2 cycle refresh period commences. - -- This will be overriden if the SPI receives a new command. - -- - if AUTOREFRESH = '1' and FSM_STATE = IdleCycle then - AUTOREFRESH_CNT <= AUTOREFRESH_CNT - 1; - if(AUTOREFRESH_CNT = 0) then - FSM_STATE <= RefreshCycle_3; - FSM_STATUS<= '1'; - end if; - end if; - end if; - - -- If new command has been given and the FSM enters idle state, load up new command for processing. - if(NEW_SPI_CMD = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then - NEW_SPI_CMD <= '0'; - - -- Store new address and data for this command. - CPU_ADDR <= SPI_CPU_ADDR; - if(SPI_CPU_DATA /= CPU_DATA_OUT) then - CPU_DATA_OUT <= SPI_CPU_DATA; - end if; - - -- Process the SOM command. The SPI_REGISTER executes non FSM commands and stores FSM - -- data prior to this execution block, which fires 1 cycle later on the same control clock. - -- If the command is not for the FSM then the READY mechanism is held for one - -- further cycle before going inactive. - case SOM_CMD is - when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" => - -- Initiate a Fetch Cycle. - FSM_STATE <= FetchCycle; - - when X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" => - - -- Set the Z80 data bus value and initiate a Write Cycle. - FSM_STATE <= WriteCycle; - - when X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" => - -- Initiate a Read Cycle. - FSM_STATE <= ReadCycle; - - when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => - -- Set the Z80 data bus value and initiate an IO Write Cycle. - -- The SOM should set 15:8 to the B register value. - FSM_STATE <= WriteIOCycle; - - when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" => - -- Initiate a Read IO Cycle. - FSM_STATE <= ReadIOCycle; - - when X"50" => - -- Register a Halt state. - FSM_STATE <= HaltCycle; - - when X"51" => - -- Initiate a refresh cycle. - FSM_STATE <= RefreshCycle_3; - - when X"E0" => - -- Initiate a Halt Cycle. - FSM_STATE <= HaltCycle; - - -- Set the Refresh Address register. - when X"E1" => - REFRESH_ADDR <= CPU_DATA_OUT; - - -- Set the Interrupt Page Address Register. - when X"E2" => - IPAR <= CPU_DATA_OUT; - - when others => - end case; - - -- Toggle the processing flag to negate the new data flag. Used to indicate device is busy. - if(SPI_NEW_DATA /= SPI_PROCESSING) then - SPI_PROCESSING <= not SPI_PROCESSING; - end if; - - -- FSM Status bit. When processing a command it is set, cleared when idle. Used by SOM to determine command completion. - FSM_STATUS <= '1'; + -- Decrement refresh counter on each Z80 cycle, thus when idle and time expired, a refresh can be performed within parameters (256 cycles in 4ms). + if(AUTOREFRESH = '1' and AUTOREFRESH_CNT /= 0 and Z80_CLK_RE = '1') then + AUTOREFRESH_CNT <= AUTOREFRESH_CNT - 1; end if; -- Refresh status bit. Indicates a Refresh cycle is under way. @@ -692,7 +665,143 @@ begin FSM_WAIT_ACTIVE <= '1'; end if; - -- On each Z80 edge we advance the FSM to recreate the Z80 external signal transactions. + -- Whenever we return to Idle or just prior to Refresh from a Fetch cycle set all control signals to default. + if((FSM_STATE = IdleCycle or FSM_STATE = RefreshCycle) and Z80_CLK_RE = '1') then + CPU_DATA_EN <= '0'; + Z80_MREQni <= '1'; + Z80_IORQni <= '1'; + Z80_RDni <= '1'; + Z80_WRni <= '1'; + Z80_M1ni <= '1'; + FSM_STATUS <= '0'; + Z80_RFSHni <= '1'; + + -- Auto DRAM refresh cycles. When enabled, every 15.6us a refresh period commences. + -- This period may be extended if the SPI receives a new command. + -- + if AUTOREFRESH = '1' and FSM_STATE = IdleCycle then + if(AUTOREFRESH_CNT = 0) then + FSM_STATE <= RefreshCycle_3; + FSM_STATUS<= '1'; + -- 4116 DRAM = 128 cycles in 2ms. + AUTOREFRESH_CNT <= 31; + end if; + end if; + end if; + + -------------------------------------------------------------------------------------------- + -- CPLD Macro Command Finite State Machine. + -------------------------------------------------------------------------------------------- + + -- Controller state machine. + -- When idle, accept and process SPI commands which can lead to a controller macro command. + case CTRL_STATE is + + when CTRLCMD_Idle => + -- If new command has been given and the FSM enters idle state, load up new command for processing. + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + + -- Store new address and data for this command. + if(NEW_SPI_DATA = '1') then + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= SPI_CPU_DATA; + end if; + + -- Process the SOM command. The SPI_REGISTER executes non FSM commands and stores FSM + -- data prior to this execution block, which fires 1 cycle later on the same control clock. + -- If the command is not for the FSM then the READY mechanism is held for one + -- further cycle before going inactive. + case SOM_CMD is + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" => + -- Initiate a Fetch Cycle. + FSM_STATE <= FetchCycle; + + when X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" => + + -- Set the Z80 data bus value and initiate a Write Cycle. + FSM_STATE <= WriteCycle; + + when X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" => + -- Initiate a Read Cycle. + FSM_STATE <= ReadCycle; + + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => + -- Set the Z80 data bus value and initiate an IO Write Cycle. + -- The SOM should set 15:8 to the B register value. + FSM_STATE <= WriteIOCycle; + + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" => + -- Initiate a Read IO Cycle. + FSM_STATE <= ReadIOCycle; + + when X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => + -- Initiate a read IO write memory cycle via the controller FSM. + CTRL_STATE <= CTRLCMD_ReadIOWrite; + FSM_STATE <= ReadIOCycle; + + when X"50" => + -- Register a Halt state. + FSM_STATE <= HaltCycle; + + when X"51" => + -- Initiate a refresh cycle. + FSM_STATE <= RefreshCycle_3; + + when X"E0" => + -- Initiate a Halt Cycle. + FSM_STATE <= HaltCycle; + + -- Set the Refresh Address register. + when X"E1" => + REFRESH_ADDR <= CPU_DATA_OUT(6 downto 0); + + -- Set the Interrupt Page Address Register. + when X"E2" => + IPAR <= CPU_DATA_OUT; + + when others => + end case; + + -- Toggle the processing flag to negate the new data flag. Used to indicate device is busy. + if(SPI_NEW_DATA /= SPI_PROCESSING) then + SPI_PROCESSING<= not SPI_PROCESSING; + end if; + + -- Clear new data flag ready for next cmd/param transfer. + NEW_SPI_DATA <= '0'; + + -- FSM Status bit. When processing a command it is set, cleared when idle. Used by SOM to determine command completion. + FSM_STATUS <= '1'; + end if; + + when CTRLCMD_ReadIOWrite => + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + NEW_SPI_DATA <= '0'; + CPU_DATA_EN <= '0'; + Z80_IORQni <= '1'; + Z80_RDni <= '1'; + Z80_RFSHni <= '1'; + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= CPU_DATA_IN; + FSM_STATE <= WriteCycle; + CTRL_STATE <= CTRLCMD_ReadIOWrite_1; + end if; + + when CTRLCMD_ReadIOWrite_1 => + if(FSM_STATE = WriteCycle_31) then + CTRL_STATE <= CTRLCMD_Idle; + end if; + + when others => + CTRL_STATE <= CTRLCMD_Idle; + + end case; + + -------------------------------------------------------------------------------------------- + -- Z80 Finite State Machine. + -------------------------------------------------------------------------------------------- + + -- On each Z80 edge we advance the Z80 FSM to recreate the Z80 external signal transactions. if(Z80_CLK_TGL = '1') then -- The FSM advances to the next stage on each Z80 edge unless in Idle state. @@ -711,7 +820,6 @@ begin when IdleCycle => CPU_LAST_T_STATE <= '1'; FSM_STATUS <= '0'; - -- FSM_STATE <= IdleCycle; ----------------------------- -- Z80 Fetch Cycle. @@ -734,7 +842,7 @@ begin when FetchCycle_30 => -- To meet the timing diagrams, just after Rising edge on T3 clear signals. Data wont be available until - -- a short period before the falling edge of T3 (could be an MZ-80A design restriction or the Z80 timing diagrams are a bit out). + -- a short period before the falling edge of T3. FSM_STATE <= RefreshCycle; ----------------------------- @@ -754,7 +862,7 @@ begin when RefreshCycle_21 => Z80_MREQni <= '1'; - REFRESH_ADDR(6 downto 0) <= REFRESH_ADDR(6 downto 0) + 1; + REFRESH_ADDR <= REFRESH_ADDR + 1; FSM_STATE <= IdleCycle; when RefreshCycle_3 => @@ -776,7 +884,7 @@ begin when WriteCycle_21 => Z80_WRni <= '0'; if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= WriteCycle_20; + FSM_STATE <= WriteCycle_20; end if; when WriteCycle_30 => @@ -801,7 +909,7 @@ begin when ReadCycle_21 => if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= ReadCycle_20; + FSM_STATE <= ReadCycle_20; end if; when ReadCycle_30 => @@ -812,7 +920,6 @@ begin FSM_STATUS <= '0'; FSM_STATE <= IdleCycle; - ----------------------------- -- Z80 IO Write Cycle. ----------------------------- @@ -832,7 +939,7 @@ begin when WriteIOCycle_31 => if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= WriteIOCycle_20; + FSM_STATE <= WriteIOCycle_30; end if; when WriteIOCycle_40 => @@ -861,7 +968,7 @@ begin when ReadIOCycle_31 => if(Z80_WAITn = '0' or FSM_WAIT_ACTIVE = '1') then - FSM_STATE <= ReadIOCycle_20; + FSM_STATE <= ReadIOCycle_30; end if; when ReadIOCycle_40 => @@ -901,7 +1008,7 @@ begin Z80_CLKi <= not Z80_CLK; -- CPU Interface tri-state control based on acknowledged bus request. - Z80_ADDR <= IPAR & REFRESH_ADDR when Z80_RFSHni = '0' + Z80_ADDR <= IPAR & '0' & REFRESH_ADDR when Z80_RFSHni = '0' else CPU_ADDR when Z80_BUSRQ_ACKni = '1' else @@ -909,8 +1016,6 @@ begin Z80_DATA <= CPU_DATA_OUT when Z80_BUSRQ_ACKni = '1' and CPU_DATA_EN = '1' else (others => 'Z'); - -- Z80_DATAi <= Z80_DATA when Z80_RDn = '0' - -- else (others => '1'); Z80_RDn <= Z80_RDni when Z80_BUSRQ_ACKni = '1' else 'Z'; Z80_WRn <= Z80_WRni when Z80_BUSRQ_ACKni = '1' @@ -951,7 +1056,7 @@ begin -- Signal mirrors. VSOM_READY <= '0' when FSM_STATUS='1' or SPI_NEW_DATA /= SPI_PROCESSING - else '1'; -- FSM Ready (1), Busy (0) + else '1'; -- FSM Ready (1), Busy (0) VSOM_LTSTATE <= '1' when CPU_LAST_T_STATE = '1' -- Last T-State in current cycle. else '0'; VSOM_BUSRQ <= not Z80_BUSRQn; -- Host device requesting Z80 Bus. diff --git a/CPLD/v1.0/PCW8256/tzpuFusionX.vhd b/CPLD/v1.0/PCW8256/tzpuFusionX.vhd new file mode 100644 index 000000000..892ef093b --- /dev/null +++ b/CPLD/v1.0/PCW8256/tzpuFusionX.vhd @@ -0,0 +1,1083 @@ +------------------------------------------------------------------------------------------------------- +-- +-- Name: tzpuFusionX.vhd +-- Version: Amstrad PCW-8256 +-- Created: Mar 2023 +-- Author(s): Philip Smart +-- Description: tzpuFusionX CPLD logic definition file. +-- This module contains the definition of the tzpuFusionX project plus enhancements +-- for the Amstrad PCW-8256. +-- +-- Credits: +-- Copyright: (c) 2018-23 Philip Smart +-- +-- History: Nov 2022 v1.0 - Initial write for the MZ-2000, adaption to the MZ-80A. +-- Feb 2023 v1.1 - Updates, after numerous tests to try and speed up the Z80 transaction +-- from SSD202 issuing a command to data being returned. Source now +-- different to the MZ-700/MZ-2000 so will need back porting. +-- Mar 2023 v1.0 - Snapshot taken from MZ-80A source for the Amstrad PCW-8256 version. +-- Version reset to v1.0 for the Amstrad. +-- Apr 2023 v1.1 - Significant changes to attempt to increase throughput as the Amstrad +-- has been designed to operate items such as the FDC within a flyback +-- window which is very difficult to meet given the boot code. +-- +--------------------------------------------------------------------------------------------------------- +-- This source file is free software: you can redistribute it and-or modify +-- it under the terms of the GNU General Public License as published +-- by the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This source file is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see . +--------------------------------------------------------------------------------------------------------- +library ieee; +library pkgs; +use ieee.std_logic_1164.all; +use ieee.std_logic_unsigned.all; +use ieee.numeric_std.all; +use work.tzpuFusionX_pkg.all; + +entity cpld512 is + generic ( + SPI_CLK_POLARITY : std_logic := '0' + ); + port ( + -- Z80 Address Bus + Z80_ADDR : inout std_logic_vector(15 downto 0); + + -- Z80 Data Bus + Z80_DATA : inout std_logic_vector(7 downto 0); + + -- Z80 Control signals. + Z80_BUSRQn : in std_logic; + Z80_BUSAKn : out std_logic; + Z80_INTn : in std_logic; + Z80_IORQn : inout std_logic; + Z80_MREQn : inout std_logic; + Z80_NMIn : in std_logic; + Z80_RDn : inout std_logic; + Z80_WRn : inout std_logic; + Z80_RESETn : in std_logic; -- Host CPU Reset signal, also CPLD reset. + Z80_HALTn : out std_logic; + Z80_WAITn : in std_logic; + Z80_M1n : inout std_logic; + Z80_RFSHn : inout std_logic; + + -- SOM Control Signals + VSOM_SPI_CLK : in std_logic; -- SOM SPI Channel 0 Clock. + VSOM_SPI_MOSI : in std_logic; -- MOSI Input. + VSOM_SPI_MISO : out std_logic; -- MISO Output. + VSOM_SPI_CSn : in std_logic; -- Enable. + + -- SOM Parallel Bus. + VSOM_DATA_OUT : out std_logic_vector(7 downto 0); -- Address/Data bus for CPLD control registers. + VSOM_HBYTE : in std_logic; -- Parallel Bus High (1)/Low (0) byte. + VSOM_READY : out std_logic; -- FSM Ready (1), Busy (0) + VSOM_LTSTATE : out std_logic; -- Last T-State in current cycle, 1 = active. + VSOM_BUSRQ : out std_logic; -- Host device requesting Z80 Bus. + VSOM_BUSACK : out std_logic; -- Host device granted Z80 Bus + VSOM_INT : out std_logic; -- Z80 INT signal + VSOM_NMI : out std_logic; -- Z80 NMI signal + VSOM_WAIT : out std_logic; -- Z80 WAIT signal + VSOM_RESET : out std_logic; -- Z80 RESET signal + VSOM_RSV : out std_logic_vector(1 downto 1); -- Reserved pins. + + -- SOM Control Signals + PM_RESET : out std_logic; -- Reset SOM + + -- VGA_Palette Control + VGA_R : in std_logic_vector(9 downto 7); -- Signals used for detecting blank or no video output. + VGA_G : in std_logic_vector(9 downto 7); + VGA_B : in std_logic_vector(9 downto 8); + + -- VGA Control Signals + VGA_PXL_CLK : in std_logic; -- VGA Pixel clock for DAC conversion. + VGA_DISPEN : in std_logic; -- Displayed Enabled (SOM video output). + VGA_VSYNCn : in std_logic; -- SOM VSync. + VGA_HSYNCn : in std_logic; -- SOM HSync. + VGA_COLR : out std_logic; -- COLR colour carrier frequency. + VGA_CSYNCn : out std_logic; -- VGA Composite Sync. + VGA_BLANKn : out std_logic; -- VGA Blank detected. + + -- CRT Control Signals + MONO_PXL_CLK : out std_logic; -- Mono CRT pixel clock for DAC conversion. + MONO_BLANKn : out std_logic; -- Mono CRT Blank (no active pixel) detection. + MONO_CSYNCn : out std_logic; -- Mono CRT composite sync. + MONO_RSV : out std_logic; + + -- CRT Lower Chrominance Control + MONO_R : out std_logic_vector(2 downto 0); -- Signals to fine tune Red level of monochrome chrominance. + MONO_G : out std_logic_vector(2 downto 0); -- Signals to fine tune Green level of monochrome chrominance. + MONO_B : out std_logic_vector(2 downto 1); -- Signals to fine tune Blue level of monochrome chrominance. + + -- MUX Control Signals + VIDEO_SRC : out std_logic; -- Select video source, Mainboard or SOM. + MONO_VIDEO_SRC : out std_logic; -- Select crt video source, Mainboard or SOM. + AUDIO_SRC_L : out std_logic; -- Select Audio Source Left Channel, Mainboard or SOM. + AUDIO_SRC_R : out std_logic; -- Select Audio Source Right Channel, Mainboard or SOM. + + -- Mainboard Reset Signals + MB_RESETn : in std_logic; -- Motherboard Reset pressed. + MB_IPLn : in std_logic; -- Motherboard IPL pressed. + + -- USB Power Control + VBUS_EN : out std_logic; -- USB Enable Power Output + + -- Clocks. + Z80_CLK : in std_logic; -- Host CPU Clock + CLK_50M : in std_logic -- 50MHz oscillator. + ); +end entity; + +architecture rtl of cpld512 is + + -- Z80 Finite State Machine states. + type SOMFSMState is + ( + IdleCycle, + FetchCycle, + FetchCycle_11, + FetchCycle_20, + FetchCycle_21, + FetchCycle_30, + RefreshCycle, + RefreshCycle_11, + RefreshCycle_20, + RefreshCycle_21, + RefreshCycle_3, + WriteCycle, + WriteCycle_11, + WriteCycle_20, + WriteCycle_21, + WriteCycle_30, + WriteCycle_31, + ReadCycle, + ReadCycle_11, + ReadCycle_20, + ReadCycle_21, + ReadCycle_30, + ReadCycle_31, + WriteIOCycle, + WriteIOCycle_11, + WriteIOCycle_20, + WriteIOCycle_21, + WriteIOCycle_30, + WriteIOCycle_31, + WriteIOCycle_40, + WriteIOCycle_41, + ReadIOCycle, + ReadIOCycle_11, + ReadIOCycle_20, + ReadIOCycle_21, + ReadIOCycle_30, + ReadIOCycle_31, + ReadIOCycle_40, + ReadIOCycle_41, + HaltCycle, + BusReqCycle + ); + + -- Controller FSM states. + type CTRLFSMState is + ( + CTRLCMD_Idle, + CTRLCMD_ReadIOWrite, + CTRLCMD_ReadIOWrite_1 + ); + + -- CPU Interface internal signals. + signal Z80_BUSRQni : std_logic; + signal Z80_INTni : std_logic; + signal Z80_IORQni : std_logic; + signal Z80_MREQni : std_logic; + signal Z80_NMIni : std_logic; + signal Z80_RDni : std_logic; + signal Z80_WRni : std_logic; + signal Z80_HALTni : std_logic; + signal Z80_M1ni : std_logic; + signal Z80_RFSHni : std_logic; + signal Z80_BUSRQ_ACKni : std_logic; + + -- Internal CPU state control. + signal CPU_ADDR : std_logic_vector(15 downto 0) := (others => '0'); + signal CPU_DATA_IN : std_logic_vector(7 downto 0) := (others => '0'); + signal CPU_DATA_OUT : std_logic_vector(7 downto 0) := (others => '0'); + signal CPU_DATA_EN : std_logic; + + -- Clocks. + signal CLK_25Mi : std_logic := '0'; + + -- Reset control + signal PM_RESETi : std_logic := '1'; + signal VSOM_RESETni : std_logic := '1'; + + -- Refresh control. + signal FSM_STATE : SOMFSMState := IdleCycle; + signal CTRL_STATE : CTRLFSMState := CTRLCMD_Idle; + signal NEW_SPI_DATA : std_logic := '0'; + signal VCPU_CS_EDGE : std_logic_vector(1 downto 0) := "11"; + signal AUTOREFRESH_CNT : integer range 0 to 63; + signal FSM_STATUS : std_logic := '0'; + signal RFSH_STATUS : std_logic := '0'; + signal REFRESH_ADDR : std_logic_vector(7 downto 0); + signal IPAR : std_logic_vector(7 downto 0); + signal AUTOREFRESH : std_logic; + + -- Clock edge detection and flagging. + signal Z80_CLKi : std_logic; + signal Z80_CLK_LAST : std_logic_vector(1 downto 0); + signal Z80_CLK_RE : std_logic; + signal Z80_CLK_FE : std_logic; + signal Z80_CLK_TGL : std_logic; + signal CPU_T_STATE_SET : integer range 0 to 5; + signal CPU_LAST_T_STATE : std_logic := '0'; + + -- SPI Slave interface. + signal SPI_SHIFT_EN : std_logic; + signal SPI_TX_SREG : std_logic_vector(6 downto 0); -- TX Shift Register + signal SPI_RX_SREG : std_logic_vector(7 downto 0); -- RX Shift Register + signal SPI_TX_DATA : std_logic_vector(31 downto 0); -- Data to transmit. + signal SPI_RX_DATA : std_logic_vector(31 downto 0); -- Data received. + signal SPI_BIT_CNT : integer range 0 to 7; -- Count of bits tx/rx'd. + signal SPI_FRAME_CNT : integer range 0 to 4; -- Number of frames received (8bit chunks). + + -- SPI Command interface. + signal SOM_CMD : std_logic_vector(7 downto 0) := (others => '0'); + signal SOM_PARAM_CNT : integer range 0 to 3; + signal SPI_NEW_DATA : std_logic; + signal SPI_PROCESSING : std_logic; + signal SPI_CPU_ADDR : std_logic_vector(15 downto 0) := (others => '0'); + signal SPI_CPU_DATA : std_logic_vector(7 downto 0) := (others => '0'); + + -- Test modes. + signal SPI_LOOPBACK_TEST : std_logic := '0'; + + -- Video/Audio control + signal VIDEO_SRCi : std_logic := '0'; + signal MONO_VIDEO_SRCi : std_logic := '0'; + signal AUDIO_SRC_Li : std_logic := '0'; + signal AUDIO_SRC_Ri : std_logic := '0'; + signal VBUS_ENi : std_logic := '1'; + + + function to_std_logic(L: boolean) return std_logic is + begin + if L then + return('1'); + else + return('0'); + end if; + end function to_std_logic; +begin + + + -- System RESET. + -- + -- The FusionX has multiple reset sources, Z80_RESETn, MB_IPLn, MB_RESETn. On the PCW-8256 there is no external reset, so we implement the standard + -- reset logic but it will never be triggered externally. + -- + SYSRESET: process( Z80_CLKi, Z80_RESETn ) + variable timer1 : integer range 0 to 400000 := 0; + variable timer100 : integer range 0 to 10 := 0; + variable timerPMReset : integer range 0 to 10 := 0; + variable resetCount : integer range 0 to 3 := 0; + variable cpuResetEdge : std_logic := '1'; + begin + -- Synchronous on the HOST Clock. + if(rising_edge(Z80_CLKi)) then + + -- If the PM Reset timer is active, count down and on expiry release the SOM PM_RESET line. + if(timerPMReset = 0 and PM_RESETi = '1') then + PM_RESETi <= '0'; + end if; + + -- If the VSOM_RESETni is active after reset timer expiry, cancel the RESET state. + if(timerPMReset = 0 and VSOM_RESETni = '0') then + VSOM_RESETni <= '1'; + end if; + + -- Each time the reset button is pressed, count the edges. + if(Z80_RESETn = '0' and cpuResetEdge = '1' and (resetCount = 0 or timer100 > 5)) then + resetCount := resetCount + 1; + VSOM_RESETni <= '0'; + timerPMReset := 5; + timer100 := 0; + + -- If there are 2 or more reset signals in a given period it means a SOM reset is required. + if(resetCount >= 2) then + PM_RESETi <= '1'; + timerPMReset := 10; + resetCount := 0; + end if; + + end if; + + -- 100ms interval. + if(timer1 = 400000) then + timer100 := timer100 + 1; + + if(timer100 >= 10) then + timer100 := 0; + resetCount := 0; + end if; + + if(timerPMReset > 0) then + timerPMReset := timerPMReset - 1; + end if; + end if; + + timer1 := timer1 - 1; + cpuResetEdge := Z80_RESETn; + end if; + end process; + + -- Create Mono DAC Clock based on primary clock. + MONOCLK: process( CLK_50M ) + begin + if(rising_edge(CLK_50M)) then + CLK_25Mi <= not CLK_25Mi; + end if; + end process; + + -- SPI Slave input. Receive command and data from the SOM. + SPI_INPUT : process(VSOM_SPI_CLK) + begin + -- Chip Select inactive, disable process and reset control flags. + if(VSOM_SPI_CSn = '1') then + + -- SPI_CLK_POLARITY='0' => falling edge; SPI_CLK_POLARITY='1' => rising edge + elsif(VSOM_SPI_CLK'event and VSOM_SPI_CLK = SPI_CLK_POLARITY) then + if(VSOM_SPI_CSn = '0') then + SPI_RX_SREG <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; + + -- End of frame then store the data prior to next bit arrival. + -- Convert to Little Endian, same as SOM. + if(SPI_SHIFT_EN = '1' and SPI_FRAME_CNT = 1 and SPI_BIT_CNT = 0) then + SPI_RX_DATA(7 downto 0) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; + + elsif(SPI_SHIFT_EN = '1' and SPI_FRAME_CNT = 2 and SPI_BIT_CNT = 0) then + SPI_RX_DATA(15 downto 8) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; + + elsif(SPI_SHIFT_EN = '1' and SPI_FRAME_CNT = 3 and SPI_BIT_CNT = 0) then + SPI_RX_DATA(23 downto 16) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; + + elsif(SPI_FRAME_CNT = 4 and SPI_BIT_CNT = 0) then + SPI_RX_DATA(31 downto 24) <= SPI_RX_SREG(6 downto 0) & VSOM_SPI_MOSI; + end if; + end if; + end if; + end process; + + -- SPI Slave output. Return the current data set as selected by the input signals XACT. + SPI_OUTPUT : process(VSOM_SPI_CLK,VSOM_SPI_CSn,SPI_TX_DATA) + begin + -- Chip Select inactive, disable process and reset control flags. + if(VSOM_SPI_CSn = '1') then + SPI_SHIFT_EN <= '0'; + SPI_BIT_CNT <= 7; + + -- SPI_CLK_POLARITY='0' => falling edge; SPI_CLK_POLARITY='1' => risinge edge + elsif(VSOM_SPI_CLK'event and VSOM_SPI_CLK = not SPI_CLK_POLARITY) then + -- Each clock reset the shift enable and done flag in preparation for the next cycle. + SPI_SHIFT_EN <= '1'; + + -- Bit count decrements to detect when last bit of byte is sent. + if(SPI_BIT_CNT > 0) then + SPI_BIT_CNT <= SPI_BIT_CNT - 1; + end if; + + -- Shift out the next bit. + VSOM_SPI_MISO <= SPI_TX_SREG(6); + SPI_TX_SREG <= SPI_TX_SREG(5 downto 0) & '0'; + + -- First clock after CS goes active, load up the data to be sent to the SOM. + if(SPI_SHIFT_EN = '0' or SPI_BIT_CNT = 0) then + + if(SPI_LOOPBACK_TEST = '1') then + VSOM_SPI_MISO<= SPI_RX_SREG(7); + SPI_TX_SREG <= SPI_RX_SREG(6 downto 0); + + elsif(SPI_SHIFT_EN = '0') then + SPI_FRAME_CNT<= 1; + VSOM_SPI_MISO<= SPI_TX_DATA(7); + SPI_TX_SREG <= SPI_TX_DATA(6 downto 0); + -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. + elsif(SPI_FRAME_CNT = 1) then + SPI_FRAME_CNT<= 2; + VSOM_SPI_MISO<= SPI_TX_DATA(15); + SPI_TX_SREG <= SPI_TX_DATA(14 downto 8); + elsif(SPI_FRAME_CNT = 2) then + SPI_FRAME_CNT<= 3; + VSOM_SPI_MISO<= SPI_TX_DATA(23); + SPI_TX_SREG <= SPI_TX_DATA(22 downto 16); + elsif(SPI_FRAME_CNT = 3) then + -- Increment frame count for each word received. We handle 8bit (1 frame), 16bit (2 frames) or 32bit (4 frames) reception. + SPI_FRAME_CNT<= 4; + VSOM_SPI_MISO<= SPI_TX_DATA(31); + SPI_TX_SREG <= SPI_TX_DATA(30 downto 24); + else + SPI_FRAME_CNT<= 0; + end if; + SPI_BIT_CNT <= 7; + end if; + end if; + end process; + + SPI_REGISTER : process(Z80_RESETn, VSOM_SPI_CSn, SPI_FRAME_CNT) + begin + if(Z80_RESETn = '0') then + VIDEO_SRCi <= '0'; + VGA_BLANKn <= '1'; + VBUS_ENi <= '1'; + MONO_VIDEO_SRCi <= '1'; + AUDIO_SRC_Li <= '0'; + AUDIO_SRC_Ri <= '0'; + AUTOREFRESH <= '1'; + SPI_LOOPBACK_TEST <= '0'; + SOM_CMD <= (others => '0'); + SOM_PARAM_CNT <= 0; + SPI_CPU_ADDR <= (others => '0'); + SPI_NEW_DATA <= '0'; + + -- On rising edge of SPI CSn a new data packet from the SOM has arrived and in the shift register SPI_RX_SREG. + -- The variable SPI_FRAME_CNT indicates which byte (frame) in a 32bit word has been transmitted. This allows + -- for 8bit, 16bit and 32bit transmissions. + -- The packet is formatted as follows: + -- + -- < SPI_CPU_ADDR > < SPI_CPU_DATA >< SOM_CMD> + -- < SPI_FRAME_CNT=4 >< SPI_FRAME=3 > < SPI_FRAME_CNT=2 >< SPI_FRAME_CNT=1> + -- < 16bit Z80 Address > < Z80 Data > + -- 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + -- + -- < > < Data > + -- 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + -- + elsif(VSOM_SPI_CSn'event and VSOM_SPI_CSn = '1') then + + -- If active, decrement parameter count. Parameters sent after a command are not considered as commands. + if(SOM_PARAM_CNT > 0) then + SOM_PARAM_CNT <= SOM_PARAM_CNT - 1; + end if; + + -- Process if command, store parameters. + if(SOM_PARAM_CNT = 0) then + -- Command is always located in the upper byte of frame 1. + SOM_CMD <= SPI_RX_DATA(7 downto 0); + + -- Toggle flag to indicate new data arrived. + SPI_NEW_DATA <= not SPI_NEW_DATA; + + -- Process the command. Some commands require the FSM, others can be serviced immediately. + case SPI_RX_DATA(7 downto 0) is + + -- Z80XACT(0..15): Setup data and address as provided then execute FSM. + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" | -- Fetch + X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" | -- Write + X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" | -- Read + X"48" | X"49" | X"4A" | X"4B" | X"4C" | X"4D" | X"4E" | X"4F" => + + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= std_logic_vector(unsigned(SPI_CPU_ADDR) + unsigned(SPI_RX_DATA(2 downto 0))); + end if; + + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; + + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => -- WriteIO + + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; + + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; + + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" | -- ReadIO + X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => -- ReadIO-Write + + -- Direct address set. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= X"00" & SPI_RX_DATA(15 downto 8); + else + SPI_CPU_ADDR <= X"000" & '0' & std_logic_vector(unsigned(SPI_RX_DATA(2 downto 0))); + end if; + + if(SPI_FRAME_CNT > 1) then + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + end if; + + -- ReadIO-Write, Read-WriteIO commands require target address, so indicate parameter needed. + if(SPI_RX_DATA(7 downto 0) >= X"38" and SPI_RX_DATA(7 downto 0) < X"40") then + SOM_PARAM_CNT <= 1; + end if; + + -- SETSIGSET1: Set control lines directly. + when X"F0" => + VIDEO_SRCi <= SPI_RX_DATA(8); + MONO_VIDEO_SRCi <= SPI_RX_DATA(9); + AUDIO_SRC_Li <= SPI_RX_DATA(10); + AUDIO_SRC_Ri <= SPI_RX_DATA(11); + VBUS_ENi <= SPI_RX_DATA(12); + VGA_BLANKn <= not SPI_RX_DATA(13); + + -- Enable auto refresh DRAM cycle. + when X"F1" => + AUTOREFRESH <= '1'; + + -- Disable auto refresh DRAM cycle. + when X"F2" => + AUTOREFRESH <= '0'; + + -- SETLOOPBACK: Enable loopback test mode. + when X"FE" => + SPI_LOOPBACK_TEST<= '1'; + + -- No action, called to retrieve status. + when X"00" | X"FF" => + + when others => + end case; + else + -- Store parameter depending on number of frames, either ADDR+DATA, ADDR or DATA. + if(SPI_FRAME_CNT = 4) then + SPI_CPU_ADDR <= SPI_RX_DATA(31 downto 16); + SPI_CPU_DATA <= SPI_RX_DATA(15 downto 8); + elsif(SPI_FRAME_CNT = 2) then + SPI_CPU_ADDR <= SPI_RX_DATA(15 downto 0); + else + SPI_CPU_DATA <= SPI_RX_DATA(7 downto 0); + end if; + end if; + end if; + end process; + + -- Process to detect the Z80 Clock edges. Each edge is used to recreate the Z80 external signals. + -- + Z80CLK: process( CLK_50M, Z80_CLKi, Z80_RESETn ) + begin + if(Z80_RESETn = '0') then + Z80_CLK_RE <= '1'; + Z80_CLK_FE <= '1'; + Z80_CLK_TGL <= '1'; + + elsif(rising_edge(CLK_50M)) then + + -- Default is to clear the signals, only active for 1 clock period. + Z80_CLK_RE <= '0'; + Z80_CLK_FE <= '0'; + Z80_CLK_TGL <= '0'; + + -- Rising Edge. + if(Z80_CLKi = '1' and Z80_CLK_LAST = "00") then + Z80_CLK_RE <= '1'; + + -- Toggle on rising edge is delayed by one clock to allow time for command to be decoded. + elsif(Z80_CLKi = '1' and Z80_CLK_LAST = "01") then + Z80_CLK_TGL <= '1'; + + -- Falling Edge. + elsif(Z80_CLKi = '0' and Z80_CLK_LAST = "11") then + Z80_CLK_FE <= '1'; + Z80_CLK_TGL <= '1'; + end if; + Z80_CLK_LAST <= Z80_CLK_LAST(0) & Z80_CLKi; + end if; + end process; + + -- SOM Finite State Machine. + -- + -- A command processor, based on an FSM concept, to process requested commands, ie. Z80 Write, Z80 Read etc. + -- The external signal SOM_CMD_EN, when set, indicates a new command available in SOM_CMD. + -- + SOMFSM: process( CLK_50M, Z80_CLKi, Z80_RESETn ) + begin + if(Z80_RESETn = '0') then + Z80_IORQni <= '1'; + Z80_MREQni <= '1'; + Z80_RDni <= '1'; + Z80_WRni <= '1'; + Z80_HALTni <= '1'; + Z80_M1ni <= '1'; + Z80_RFSHni <= '1'; + Z80_BUSRQ_ACKni <= '1'; + FSM_STATUS <= '0'; + FSM_STATE <= IdleCycle; + RFSH_STATUS <= '0'; + CPU_DATA_EN <= '0'; + CPU_DATA_IN <= (others => '0'); + REFRESH_ADDR <= (others => '0'); + AUTOREFRESH_CNT <= 63; + IPAR <= (others => '0'); + NEW_SPI_DATA <= '0'; + VCPU_CS_EDGE <= "11"; + SPI_PROCESSING <= '0'; + + elsif(rising_edge(CLK_50M)) then + + -- Bus request mechanism. If an externel Bus Request comes in and the FSM is idle, run the Bus Request command which + -- suspends processing and tri-states the bus. + if(Z80_BUSRQn = '0' and Z80_BUSRQ_ACKni = '1' and FSM_STATE = IdleCycle) then + FSM_STATE <= BusReqCycle; + end if; + if(Z80_BUSRQn = '1' and Z80_BUSRQ_ACKni = '0' and FSM_STATE = IdleCycle) then + Z80_BUSRQ_ACKni <= '1'; + end if; + + -- New command, set flag as the signal is only 1 clock wide. + if(SPI_LOOPBACK_TEST = '0' and VSOM_SPI_CSn = '1' and VCPU_CS_EDGE = "01") then + NEW_SPI_DATA <= '1'; + end if; + + -- Decrement refresh counter on each Z80 cycle, thus when idle and time expired, a refresh can be performed within parameters (256 cycles in 4ms). + if(AUTOREFRESH = '1' and AUTOREFRESH_CNT /= 0 and Z80_CLK_RE = '1') then + AUTOREFRESH_CNT <= AUTOREFRESH_CNT - 1; + end if; + + -- Refresh status bit. Indicates a Refresh cycle is under way. + if FSM_STATE = RefreshCycle or FSM_STATE = RefreshCycle_11 or FSM_STATE = RefreshCycle_20 or FSM_STATE = RefreshCycle_21 or FSM_STATE = RefreshCycle_3 then + RFSH_STATUS <= '1'; + else + RFSH_STATUS <= '0'; + end if; + + -- Whenever we return to Idle or just prior to Refresh from a Fetch cycle set all control signals to default. + if((FSM_STATE = IdleCycle or FSM_STATE = RefreshCycle) and Z80_CLK_RE = '1') then + CPU_DATA_EN <= '0'; + Z80_MREQni <= '1'; + Z80_IORQni <= '1'; + Z80_RDni <= '1'; + Z80_WRni <= '1'; + Z80_M1ni <= '1'; + FSM_STATUS <= '0'; + Z80_RFSHni <= '1'; + FSM_STATUS <= '0'; + + -- Auto DRAM refresh cycles. When enabled, every 15.6us a refresh period commences. + -- This period may be extended if the SPI receives a new command. + -- + if AUTOREFRESH = '1' and FSM_STATE = IdleCycle then + if(AUTOREFRESH_CNT = 0) then + FSM_STATE <= RefreshCycle_3; + FSM_STATUS<= '1'; + -- 41257 DRAM = 256 cycles in 4ms. + AUTOREFRESH_CNT <= 63; + end if; + end if; + end if; + + -------------------------------------------------------------------------------------------- + -- CPLD Macro Command Finite State Machine. + -------------------------------------------------------------------------------------------- + + -- Controller state machine. + -- When idle, accept and process SPI commands which can lead to a controller macro command. + case CTRL_STATE is + + when CTRLCMD_Idle => + -- If new command has been given and the FSM enters idle state, load up new command for processing. + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + + -- Store new address and data for this command. + if(NEW_SPI_DATA = '1') then + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= SPI_CPU_DATA; + end if; + + -- Process the SOM command. The SPI_REGISTER executes non FSM commands and stores FSM + -- data prior to this execution block, which fires 1 cycle later on the same control clock. + -- If the command is not for the FSM then the READY mechanism is held for one + -- further cycle before going inactive. + case SOM_CMD is + when X"10" | X"11" | X"12" | X"13" | X"14" | X"15" | X"16" | X"17" => + -- Initiate a Fetch Cycle. + FSM_STATE <= FetchCycle; + + when X"18" | X"19" | X"1A" | X"1B" | X"1C" | X"1D" | X"1E" | X"1F" => + + -- Set the Z80 data bus value and initiate a Write Cycle. + FSM_STATE <= WriteCycle; + + when X"20" | X"21" | X"22" | X"23" | X"24" | X"25" | X"26" | X"27" => + -- Initiate a Read Cycle. + FSM_STATE <= ReadCycle; + + when X"28" | X"29" | X"2A" | X"2B" | X"2C" | X"2D" | X"2E" | X"2F" => + -- Set the Z80 data bus value and initiate an IO Write Cycle. + -- The SOM should set 15:8 to the B register value. + FSM_STATE <= WriteIOCycle; + + when X"30" | X"31" | X"32" | X"33" | X"34" | X"35" | X"36" | X"37" => + -- Initiate a Read IO Cycle. + FSM_STATE <= ReadIOCycle; + + when X"38" | X"39" | X"3A" | X"3B" | X"3C" | X"3D" | X"3E" | X"3F" => + -- Initiate a read IO write memory cycle via the controller FSM. + CTRL_STATE <= CTRLCMD_ReadIOWrite; + FSM_STATE <= ReadIOCycle; + + when X"50" => + -- Register a Halt state. + FSM_STATE <= HaltCycle; + + when X"51" => + -- Initiate a refresh cycle. + FSM_STATE <= RefreshCycle_3; + + when X"E0" => + -- Initiate a Halt Cycle. + FSM_STATE <= HaltCycle; + + -- Set the Refresh Address register. + when X"E1" => + REFRESH_ADDR <= CPU_DATA_OUT; + + -- Set the Interrupt Page Address Register. + when X"E2" => + IPAR <= CPU_DATA_OUT; + + when others => + end case; + + -- Toggle the processing flag to negate the new data flag. Used to indicate device is busy. + if(SPI_NEW_DATA /= SPI_PROCESSING) then + SPI_PROCESSING<= not SPI_PROCESSING; + end if; + + -- Clear new data flag ready for next cmd/param transfer. + NEW_SPI_DATA <= '0'; + + -- FSM Status bit. When processing a command it is set, cleared when idle. Used by SOM to determine command completion. + FSM_STATUS <= '1'; + end if; + + when CTRLCMD_ReadIOWrite => + if(NEW_SPI_DATA = '1' and FSM_STATE = IdleCycle and Z80_CLK_RE = '1') then + NEW_SPI_DATA <= '0'; + CPU_DATA_EN <= '0'; + Z80_IORQni <= '1'; + Z80_RDni <= '1'; + Z80_RFSHni <= '1'; + CPU_ADDR <= SPI_CPU_ADDR; + CPU_DATA_OUT <= CPU_DATA_IN; + FSM_STATE <= WriteCycle; + CTRL_STATE <= CTRLCMD_ReadIOWrite_1; + end if; + + when CTRLCMD_ReadIOWrite_1 => + if(FSM_STATE = WriteCycle_31) then + CTRL_STATE <= CTRLCMD_Idle; + end if; + + when others => + CTRL_STATE <= CTRLCMD_Idle; + + end case; + + -------------------------------------------------------------------------------------------- + -- Z80 Finite State Machine. + -------------------------------------------------------------------------------------------- + + -- On each Z80 edge we advance the Z80 FSM to recreate the Z80 external signal transactions. + if(Z80_CLK_TGL = '1') then + + -- The FSM advances to the next stage on each Z80 edge unless in Idle state. + if(FSM_STATE /= IdleCycle) then + FSM_STATE <= SOMFSMState'val(SOMFSMState'POS(FSM_STATE)+1); + end if; + + -- FSM to implement all the required Z80 cycles. + -- + case FSM_STATE is + + when IdleCycle => + CPU_LAST_T_STATE <= '1'; + FSM_STATUS <= '0'; + + ----------------------------- + -- Z80 Fetch Cycle. + ----------------------------- + when FetchCycle => + Z80_M1ni <= '0'; + + when FetchCycle_11 => + Z80_M1ni <= '0'; + Z80_MREQni <= '0'; + Z80_RDni <= '0'; + + when FetchCycle_20 => + + when FetchCycle_21 => + if(Z80_WAITn = '0') then + FSM_STATE <= FetchCycle_20; + end if; + + when FetchCycle_30 => + -- To meet the timing diagrams, just after Rising edge on T3 clear signals. Data wont be available until + -- a short period before the falling edge of T3. + FSM_STATE <= RefreshCycle; + + ----------------------------- + -- Z80 Refresh Cycle. + ----------------------------- + when RefreshCycle => + -- Latch data from mainboard. + CPU_DATA_IN <= Z80_DATA; + Z80_RFSHni <= '0'; + + when RefreshCycle_11 => + -- Falling edge of T3 activates the MREQ line. + Z80_MREQni <= '0'; + FSM_STATUS <= '0'; + + when RefreshCycle_20 => + + when RefreshCycle_21 => + Z80_MREQni <= '1'; + REFRESH_ADDR <= REFRESH_ADDR + 1; + FSM_STATE <= IdleCycle; + + when RefreshCycle_3 => + Z80_RFSHni <= '0'; + FSM_STATE <= RefreshCycle_11; + + ----------------------------- + -- Z80 Write Cycle. + ----------------------------- + when WriteCycle => + + when WriteCycle_11 => + Z80_MREQni <= '0'; + CPU_DATA_EN <= '1'; + + when WriteCycle_20 => + + when WriteCycle_21 => + Z80_WRni <= '0'; + if(Z80_WAITn = '0') then + FSM_STATE <= WriteCycle_20; + end if; + + when WriteCycle_30 => + + when WriteCycle_31 => + FSM_STATUS <= '0'; + Z80_MREQni <= '1'; + Z80_WRni <= '1'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 Read Cycle. + ----------------------------- + when ReadCycle => + + when ReadCycle_11 => + Z80_MREQni <= '0'; + Z80_RDni <= '0'; + + when ReadCycle_20 => + + when ReadCycle_21 => + if(Z80_WAITn = '0') then + FSM_STATE <= ReadCycle_20; + end if; + + when ReadCycle_30 => + + when ReadCycle_31 => + -- Latch data from mainboard. + CPU_DATA_IN <= Z80_DATA; + FSM_STATUS <= '0'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 IO Write Cycle. + ----------------------------- + when WriteIOCycle => + + when WriteIOCycle_11 => + CPU_DATA_EN <= '1'; + + when WriteIOCycle_20 => + Z80_IORQni <= '0'; + Z80_WRni <= '0'; + + when WriteIOCycle_21 => + + when WriteIOCycle_30 => + + when WriteIOCycle_31 => + if(Z80_WAITn = '0') then + FSM_STATE <= WriteIOCycle_30; + end if; + + when WriteIOCycle_40 => + + when WriteIOCycle_41 => + FSM_STATUS <= '0'; + Z80_IORQni <= '1'; + Z80_WRni <= '1'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 IO Read Cycle. + ----------------------------- + when ReadIOCycle => + + when ReadIOCycle_11 => + + when ReadIOCycle_20 => + Z80_IORQni <= '0'; + Z80_RDni <= '0'; + + when ReadIOCycle_21 => + + when ReadIOCycle_30 => + + when ReadIOCycle_31 => + if(Z80_WAITn = '0') then + FSM_STATE <= ReadIOCycle_30; + end if; + + when ReadIOCycle_40 => + + when ReadIOCycle_41 => + -- Latch data from mainboard. + CPU_DATA_IN <= Z80_DATA; + FSM_STATUS <= '0'; + + -- IORQ/RD are deactivated at idle giving 1 clock to latch the data in. + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Halt Request. + ----------------------------- + when HaltCycle => + Z80_HALTni <= '0'; + FSM_STATUS <= '0'; + FSM_STATE <= IdleCycle; + + ----------------------------- + -- Z80 Bus Request. + ----------------------------- + when BusReqCycle => + Z80_BUSRQ_ACKni <= '0'; + FSM_STATE <= IdleCycle; + + when others => + FSM_STATE <= IdleCycle; + end case; + end if; + + VCPU_CS_EDGE <= VCPU_CS_EDGE(0) & VSOM_SPI_CSn; + end if; + end process; + + Z80_CLKi <= not Z80_CLK; + + -- CPU Interface tri-state control based on acknowledged bus request. + Z80_ADDR <= IPAR & REFRESH_ADDR when Z80_RFSHni = '0' + else + CPU_ADDR when Z80_BUSRQ_ACKni = '1' + else + (others => 'Z'); + Z80_DATA <= CPU_DATA_OUT when Z80_BUSRQ_ACKni = '1' and CPU_DATA_EN = '1' + else + (others => 'Z'); + Z80_RDn <= Z80_RDni when Z80_BUSRQ_ACKni = '1' + else 'Z'; + Z80_WRn <= Z80_WRni when Z80_BUSRQ_ACKni = '1' + else 'Z'; + Z80_M1n <= Z80_M1ni when Z80_BUSRQ_ACKni = '1' + else 'Z'; + Z80_RFSHn <= Z80_RFSHni when Z80_BUSRQ_ACKni = '1' + else 'Z'; + Z80_MREQn <= Z80_MREQni when Z80_BUSRQ_ACKni = '1' + else 'Z'; + Z80_IORQn <= Z80_IORQni when Z80_BUSRQ_ACKni = '1' + else 'Z'; + Z80_BUSAKn <= Z80_BUSRQ_ACKni; + + -- CPU Interface single state output. + Z80_HALTn <= Z80_HALTni; + + -- CPU Interface single state input. + Z80_NMIni <= Z80_NMIn; + Z80_INTni <= Z80_INTn; + Z80_BUSRQni <= Z80_BUSRQn; + + -- SOM Reset. + PM_RESET <= PM_RESETi; + + -- SOM to CPLD Interface. + VSOM_DATA_OUT <= CPU_DATA_IN when VSOM_HBYTE = '1' + else + FSM_STATUS & RFSH_STATUS & Z80_BUSRQ_ACKni & Z80_BUSRQni & Z80_INTni & Z80_NMIni & Z80_WAITn & Z80_RESETn when VSOM_HBYTE = '0' + else + (others => '0'); + + -- Loopback test, echo what was received. + SPI_TX_DATA <= SPI_RX_DATA when SPI_LOOPBACK_TEST = '1' + else + --CPU_ADDR & SOM_CMD & FSM_STATUS & RFSH_STATUS & std_logic_vector(to_unsigned(SOMFSMState'POS(FSM_STATE), 6)); + CPU_ADDR & CPU_DATA_IN & FSM_STATUS & RFSH_STATUS & Z80_BUSRQ_ACKni & Z80_BUSRQni & Z80_INTni & Z80_NMIni & Z80_WAITn & Z80_RESETn; + + -- Signal mirrors. + VSOM_READY <= '0' when FSM_STATUS='1' or SPI_NEW_DATA /= SPI_PROCESSING + else '1'; -- FSM Ready (1), Busy (0) + VSOM_LTSTATE <= '1' when CPU_LAST_T_STATE = '1' -- Last T-State in current cycle. + else '0'; + VSOM_BUSRQ <= not Z80_BUSRQn; -- Host device requesting Z80 Bus. + VSOM_BUSACK <= not Z80_BUSRQ_ACKni; -- Host device granted Z80 Bus + VSOM_INT <= not Z80_INTn; -- Z80 INT signal + VSOM_NMI <= not Z80_NMIn; -- Z80 NMI signal + VSOM_WAIT <= not Z80_WAITn; -- Z80 WAIT signal + VSOM_RESET <= not VSOM_RESETni; -- Z80 RESET signal + VSOM_RSV <= (others => '0'); -- Reserved pins. + + -- Video/Audio control signals. + VIDEO_SRC <= VIDEO_SRCi; + MONO_VIDEO_SRC <= MONO_VIDEO_SRCi; + AUDIO_SRC_L <= AUDIO_SRC_Li; + AUDIO_SRC_R <= AUDIO_SRC_Ri; + + -- USB Power Supply enable. + VBUS_EN <= VBUS_ENi; + + -- Monochrome output is based on the incoming VGA to give the best chrominance levels. + MONO_R <= VGA_R; + MONO_G <= VGA_G; + MONO_B <= VGA_B; + + -- Blanking is active when all colour signals are at 0. The DAC converts values in range 4v .. 5v to adjust chrominance + -- but true off can obly be achieved by bringing the signal value to 0v which is achieved by a Mux activated with this blanking signal. + MONO_BLANKn <= '0' when VGA_R = "000" and VGA_G = "000" and VGA_B = "000" + else '1'; + + -- Generate composite sync. + VGA_CSYNCn <= VGA_HSYNCn xor not VGA_VSYNCn; + MONO_CSYNCn <= not VGA_HSYNCn xor not VGA_VSYNCn; + + -- DAC clocks. + --VGA_PXL_CLK <= CLK_50M; + MONO_PXL_CLK <= VGA_PXL_CLK; + + -- Currently unassigned. + VGA_COLR <= '0'; + MONO_RSV <= '0'; + +end architecture; diff --git a/CPLD/v1.0/PCW8256/tzpuFusionX_Toplevel.vhd b/CPLD/v1.0/PCW8256/tzpuFusionX_Toplevel.vhd new file mode 100644 index 000000000..763873a78 --- /dev/null +++ b/CPLD/v1.0/PCW8256/tzpuFusionX_Toplevel.vhd @@ -0,0 +1,227 @@ +--------------------------------------------------------------------------------------------------------- +-- +-- Name: tzpuFusionX_Toplevel.vhd +-- Version: Amstrad PCW-8256 +-- Created: Mar 2023 +-- Author(s): Philip Smart +-- Description: tzpuFusionX CPLD Top Level module. +-- +-- This module contains the basic pin definition of the CPLD<->logic needed in the +-- project which targets the MZ-80A host. +-- +-- Credits: +-- Copyright: (c) 2018-23 Philip Smart +-- +-- History: Nov 2022 v1.0 - Initial write for the MZ-2000, adaption to the MZ-80A. +-- Feb 2023 v1.1 - Updates, after numerous tests to try and speed up the Z80 transaction +-- from SSD202 issuing a command to data being returned. Source now +-- different to the MZ-700/MZ-2000 so will need back porting. +-- Mar 2023 v1.0 - Snapshot taken from MZ-80A source for the Amstrad PCW-8256 version. +-- Version reset to v1.0 for the Amstrad. +-- +--------------------------------------------------------------------------------------------------------- +-- This source file is free software: you can redistribute it and-or modify +-- it under the terms of the GNU General Public License as published +-- by the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This source file is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- along with this program. If not, see . +--------------------------------------------------------------------------------------------------------- +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.numeric_std.all; +use work.tzpuFusionX_pkg.all; +library altera; +use altera.altera_syn_attributes.all; + +entity tzpuFusionX_PCW8256 is + port ( + -- Z80 Address Bus + Z80_ADDR : inout std_logic_vector(15 downto 0); + + -- Z80 Data Bus + Z80_DATA : inout std_logic_vector(7 downto 0); + + -- Z80 Control signals. + Z80_BUSRQn : in std_logic; + Z80_BUSAKn : out std_logic; + Z80_INTn : in std_logic; + Z80_IORQn : inout std_logic; + Z80_MREQn : inout std_logic; + Z80_NMIn : in std_logic; + Z80_RDn : inout std_logic; + Z80_WRn : inout std_logic; + Z80_RESETn : in std_logic; -- Host CPU Reset signal, also CPLD reset. + Z80_HALTn : out std_logic; + Z80_WAITn : in std_logic; + Z80_M1n : inout std_logic; + Z80_RFSHn : inout std_logic; + + -- SOM SPI + VSOM_SPI_CSn : in std_logic; -- SPI Slave Select + VSOM_SPI_CLK : in std_logic; -- SPI Clock + VSOM_SPI_MOSI : in std_logic; -- SPI Master Output Slave Input + VSOM_SPI_MISO : out std_logic; -- SPI Master Input Slave Output + + -- SOM Parallel Bus. + VSOM_DATA_OUT : out std_logic_vector(7 downto 0); -- Address/Data bus for CPLD control registers. + VSOM_HBYTE : in std_logic; -- Parallel Bus High (1)/Low (0) byte. + VSOM_READY : out std_logic; -- FSM Ready (1), Busy (0) + VSOM_LTSTATE : out std_logic; -- Last T-State in current cycle, 1 = active. + VSOM_BUSRQ : out std_logic; -- Host device requesting Z80 Bus. + VSOM_BUSACK : out std_logic; -- Host device granted Z80 Bus + VSOM_INT : out std_logic; -- Z80 INT signal + VSOM_NMI : out std_logic; -- Z80 NMI signal + VSOM_WAIT : out std_logic; -- Z80 WAIT signal + VSOM_RESET : out std_logic; -- Z80 RESET signal + VSOM_RSV : out std_logic_vector(1 downto 1); -- Reserved pins. + + -- SOM Control Signals + PM_RESET : out std_logic; -- Reset SOM + + -- VGA_Palette Control + VGA_R : in std_logic_vector(9 downto 7); -- Signals used for detecting blank or no video output. + VGA_G : in std_logic_vector(9 downto 7); + VGA_B : in std_logic_vector(9 downto 8); + + -- VGA Control Signals + VGA_PXL_CLK : in std_logic; -- VGA Pixel clock for DAC conversion. + VGA_DISPEN : in std_logic; -- Displayed Enabled (SOM video output). + VGA_VSYNCn : in std_logic; -- SOM VSync. + VGA_HSYNCn : in std_logic; -- SOM HSync. + VGA_COLR : out std_logic; -- COLR colour carrier frequency. + VGA_CSYNCn : out std_logic; -- VGA Composite Sync. + VGA_BLANKn : out std_logic; -- VGA Blank detected. + + -- CRT Control Signals + MONO_PXL_CLK : out std_logic; -- Mono CRT pixel clock for DAC conversion. + MONO_BLANKn : out std_logic; -- Mono CRT Blank (no active pixel) detection. + MONO_CSYNCn : out std_logic; -- Mono CRT composite sync. + MONO_RSV : out std_logic; + + -- CRT Lower Chrominance Control + MONO_R : out std_logic_vector(2 downto 0); -- Signals to fine tune Red level of monochrome chrominance. + MONO_G : out std_logic_vector(2 downto 0); -- Signals to fine tune Green level of monochrome chrominance. + MONO_B : out std_logic_vector(2 downto 1); -- Signals to fine tune Blue level of monochrome chrominance. + + -- MUX Control Signals + VIDEO_SRC : out std_logic; -- Select video source, Mainboard or SOM. + MONO_VIDEO_SRC : out std_logic; -- Select crt video source, Mainboard or SOM. + AUDIO_SRC_L : out std_logic; -- Select Audio Source Left Channel, Mainboard or SOM. + AUDIO_SRC_R : out std_logic; -- Select Audio Source Right Channel, Mainboard or SOM. + + -- Mainboard Reset Signals + MB_RESETn : in std_logic; -- Motherboard Reset pressed. + MB_IPLn : in std_logic; -- Motherboard IPL pressed. + + -- USB Power Control + VBUS_EN : out std_logic; -- USB Enable Power Output + + -- Clocks. + Z80_CLK : in std_logic; -- Host CPU Clock + CLK_50M : in std_logic -- 50MHz oscillator. + ); +END entity; + +architecture rtl of tzpuFusionX_PCW8256 is + +begin + + cpldl512Toplevel : entity work.cpld512 + generic map ( + SPI_CLK_POLARITY => '0' + ) + port map + ( + -- Z80 Address Bus + Z80_ADDR => Z80_ADDR, + + -- Z80 Data Bus + Z80_DATA => Z80_DATA, + + -- Z80 Control signals. + Z80_BUSRQn => Z80_BUSRQn, + Z80_BUSAKn => Z80_BUSAKn, + Z80_INTn => Z80_INTn, + Z80_IORQn => Z80_IORQn, + Z80_MREQn => Z80_MREQn, + Z80_NMIn => Z80_NMIn, + Z80_RDn => Z80_RDn, + Z80_WRn => Z80_WRn, + Z80_RESETn => Z80_RESETn, + Z80_HALTn => Z80_HALTn, + Z80_WAITn => Z80_WAITn, + Z80_M1n => Z80_M1n, + Z80_RFSHn => Z80_RFSHn, + + -- SOM SPI + VSOM_SPI_CSn => VSOM_SPI_CSn, -- SPI Slave Select + VSOM_SPI_CLK => VSOM_SPI_CLK, -- SPI Clock + VSOM_SPI_MOSI => VSOM_SPI_MOSI, -- SPI Master Output Slave Input + VSOM_SPI_MISO => VSOM_SPI_MISO, -- SPI Master Input Slave Output + + -- SOM Parallel Bus. + VSOM_DATA_OUT => VSOM_DATA_OUT, -- Address/Data bus for CPLD control registers. + VSOM_HBYTE => VSOM_HBYTE, -- Parallel Bus High (1)/Low (0) byte. + VSOM_READY => VSOM_READY, -- FSM Ready (1), Busy (0) + VSOM_LTSTATE => VSOM_LTSTATE, -- Last T-State in current cycle. + VSOM_BUSRQ => VSOM_BUSRQ, -- Host device requesting Z80 Bus. + VSOM_BUSACK => VSOM_BUSACK, -- Host device granted Z80 Bus + VSOM_INT => VSOM_INT, -- Z80 INT signal + VSOM_NMI => VSOM_NMI, -- Z80 NMI signal + VSOM_WAIT => VSOM_WAIT, -- Z80 WAIT signal + VSOM_RESET => VSOM_RESET, -- Z80 RESET signal + VSOM_RSV => VSOM_RSV, -- Reserved pins. + + -- SOM Control Signals + PM_RESET => PM_RESET, -- Reset SOM + + -- VGA_Palette Control + VGA_R => VGA_R, -- Signals used for detecting blank or no video output. + VGA_G => VGA_G, + VGA_B => VGA_B, + + -- VGA Control Signals + VGA_PXL_CLK => VGA_PXL_CLK, -- VGA Pixel clock for DAC conversion. + VGA_DISPEN => VGA_DISPEN, -- Displayed Enabled (SOM video output). + VGA_VSYNCn => VGA_VSYNCn, -- SOM VSync. + VGA_HSYNCn => VGA_HSYNCn, -- SOM HSync. + VGA_COLR => VGA_COLR, -- COLR colour carrier frequency. + VGA_CSYNCn => VGA_CSYNCn, -- VGA Composite Sync. + VGA_BLANKn => VGA_BLANKn, -- VGA Blank detected. + + -- CRT Control Signals + MONO_PXL_CLK => MONO_PXL_CLK, -- Mono CRT pixel clock for DAC conversion. + MONO_BLANKn => MONO_BLANKn, -- Mono CRT Blank (no active pixel) detection. + MONO_CSYNCn => MONO_CSYNCn, -- Mono CRT composite sync. + MONO_RSV => MONO_RSV, + + -- CRT Lower Chrominance Control + MONO_R => MONO_R, -- Signals to fine tune Red level of monochrome chrominance. + MONO_G => MONO_G, -- Signals to fine tune Green level of monochrome chrominance. + MONO_B => MONO_B, -- Signals to fine tune Blue level of monochrome chrominance. + + -- MUX Control Signals + VIDEO_SRC => VIDEO_SRC, -- Select video source, Mainboard or SOM. + MONO_VIDEO_SRC => MONO_VIDEO_SRC, -- Select crt video source, Mainboard or SOM. + AUDIO_SRC_L => AUDIO_SRC_L, -- Select Audio Source Left Channel, Mainboard or SOM. + AUDIO_SRC_R => AUDIO_SRC_R, -- Select Audio Source Right Channel, Mainboard or SOM. + + -- Mainboard Reset Signals=> MONO_R, + MB_RESETn => MB_RESETn, -- Motherboard Reset pressed. + MB_IPLn => MB_IPLn, -- Motherboard IPL pressed. + + -- USB Power Control + VBUS_EN => VBUS_EN, -- USB Enable Power Output + + -- Clocks. + Z80_CLK => Z80_CLK, -- Host CPU Clock + CLK_50M => CLK_50M -- 50MHz oscillator. + ); + +end architecture; diff --git a/CPLD/v1.0/PCW8256/tzpuFusionX_pkg.vhd b/CPLD/v1.0/PCW8256/tzpuFusionX_pkg.vhd new file mode 100644 index 000000000..3e3e6b305 --- /dev/null +++ b/CPLD/v1.0/PCW8256/tzpuFusionX_pkg.vhd @@ -0,0 +1,227 @@ +--------------------------------------------------------------------------------------------------------- +-- +-- Name: tzpuFusionX_pkg.vhd +-- Created: Mar 2023 +-- Author(s): Philip Smart +-- Description: tzpuFusionX CPLD configuration file. +-- +-- This module contains parameters for the CPLD in the tzpuFusionX project +-- which targets the Amstrad PCW8256 host. +-- +-- Credits: +-- Copyright: (c) 2018-23 Philip Smart +-- +-- History: Nov 2022 v1.0 - Initial write for the MZ-2000, adaption to the MZ-80A. +-- Feb 2023 v1.1 - Updates, after numerous tests to try and speed up the Z80 transaction +-- from SSD202 issuing a command to data being returned. Source now +-- different to the MZ-700/MZ-2000 so will need back porting. +-- Mar 2023 v1.0 - Snapshot taken from MZ-80A source for the Amstrad PCW-8256 version. +-- Version reset to v1.0 for the Amstrad. +-- +--------------------------------------------------------------------------------------------------------- +-- This source file is free software: you can redistribute it and-or modify +-- it under the terms of the GNU General Public License as published +-- by the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This source file is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see . +--------------------------------------------------------------------------------------------------------- + +library ieee; +library pkgs; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use ieee.math_real.all; + +package tzpuFusionX_pkg is + + ------------------------------------------------------------ + -- Constants + ------------------------------------------------------------ + + -- Potential logic state constants. + constant YES : std_logic := '1'; + constant NO : std_logic := '0'; + constant HI : std_logic := '1'; + constant LO : std_logic := '0'; + constant ONE : std_logic := '1'; + constant ZERO : std_logic := '0'; + constant HIZ : std_logic := 'Z'; + + -- CPLD Command instructions. + constant CPLD_CMD_RESET_HOST : integer := 1; + constant CPLD_CMD_HOLD_HOST_BUS : integer := 2; + constant CPLD_CMD_RELEASE_HOST_BUS: integer := 3; + + -- Target hardware modes. + constant MODE_MZ80K : integer := 0; + constant MODE_MZ80C : integer := 1; + constant MODE_MZ1200 : integer := 2; + constant MODE_MZ80A : integer := 3; + constant MODE_MZ700 : integer := 4; + constant MODE_MZ800 : integer := 5; + constant MODE_MZ80B : integer := 6; + constant MODE_MZ2000 : integer := 7; + constant MODE_PCW8256 : integer := 8; + + -- Memory management modes. + constant TZMM_ORIG : integer := 00; -- Original Sharp mode, no tranZPUter features are selected except the I/O control registers (default: 0x60-063). + constant TZMM_BOOT : integer := 01; -- Original mode but E800-EFFF is mapped to tranZPUter RAM so TZFS can be booted. + constant TZMM_TZFS : integer := 02; -- TZFS main memory configuration. all memory is in tranZPUter RAM, E800-FFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected. + constant TZMM_TZFS2 : integer := 03; -- TZFS main memory configuration. all memory is in tranZPUter RAM, E800-EFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected, F000-FFFF is in 64K Block 1. + constant TZMM_TZFS3 : integer := 04; -- TZFS main memory configuration. all memory is in tranZPUter RAM, E800-EFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected, F000-FFFF is in 64K Block 2. + constant TZMM_TZFS4 : integer := 05; -- TZFS main memory configuration. all memory is in tranZPUter RAM, E800-EFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected, F000-FFFF is in 64K Block 3. + constant TZMM_CPM : integer := 06; -- CPM main memory configuration, all memory on the tranZPUter board, 64K block 4 selected. Special case for F3C0:F3FF & F7C0:F7FF (floppy disk paging vectors) which resides on the mainboard. + constant TZMM_CPM2 : integer := 07; -- CPM main memory configuration, F000-FFFF are on the tranZPUter board in block 4, 0040-CFFF and E800-EFFF are in block 5, mainboard for D000-DFFF (video), E000-E800 (Memory control) selected. + -- Special case for 0000:003F (interrupt vectors) which resides in block 4, F3FE:F3FF & F7FE:F7FF (floppy disk paging vectors) which resides on the mainboard. + constant TZMM_COMPAT : integer := 08; -- Compatibility monitor mode, monitor ROM on mainboard, RAM on tranZPUter in Block 0 1000-CFFF. + constant TZMM_HOSTACCESS : integer := 09; -- Monitor ROM 0000-0FFF and Main DRAM 0x1000-0xD000, video and memory mapped I/O are on the host machine, User/Floppy ROM E800-FFFF are in tranZPUter memory. + constant TZMM_MZ700_0 : integer := 10; -- MZ700 Mode - 0000:0FFF is on the tranZPUter board in block 6, 1000:CFFF is on the tranZPUter board in block 0, D000:FFFF is on the mainboard. + constant TZMM_MZ700_1 : integer := 11; -- MZ700 Mode - 0000:0FFF is on the tranZPUter board in block 0, 1000:CFFF is on the tranZPUter board in block 0, D000:FFFF is on the tranZPUter in block 6. + constant TZMM_MZ700_2 : integer := 12; -- MZ700 Mode - 0000:0FFF is on the tranZPUter board in block 6, 1000:CFFF is on the tranZPUter board in block 0, D000:FFFF is on the tranZPUter in block 6. + constant TZMM_MZ700_3 : integer := 13; -- MZ700 Mode - 0000:0FFF is on the tranZPUter board in block 0, 1000:CFFF is on the tranZPUter board in block 0, D000:FFFF is inaccessible. + constant TZMM_MZ700_4 : integer := 14; -- MZ700 Mode - 0000:0FFF is on the tranZPUter board in block 6, 1000:CFFF is on the tranZPUter board in block 0, D000:FFFF is inaccessible. + constant TZMM_MZ800 : integer := 15; -- MZ800 Mode - Running on MZ800 hardware, configuration set according to MZ700/MZ800 mode. + constant TZMM_MZ2000 : integer := 16; -- MZ2000 Mode - Running on MZ2000 hardware, configuration set according to runtime configuration registers. + constant TZMM_FPGA : integer := 21; -- Open up access for the K64F to the FPGA resources such as memory. All other access to RAM or mainboard is blocked. + constant TZMM_TZPUM : integer := 22; -- Everything in on mainboard, no access to tranZPUter memory. + constant TZMM_TZPU : integer := 23; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 0 is selected. + constant TZMM_TZPU0 : integer := 24; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 0 is selected. + constant TZMM_TZPU1 : integer := 25; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 1 is selected. + constant TZMM_TZPU2 : integer := 26; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 2 is selected. + constant TZMM_TZPU3 : integer := 27; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 3 is selected. + constant TZMM_TZPU4 : integer := 28; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 4 is selected. + constant TZMM_TZPU5 : integer := 29; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 5 is selected. + constant TZMM_TZPU6 : integer := 30; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 6 is selected. + constant TZMM_TZPU7 : integer := 31; -- Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 7 is selected. + + + + ------------------------------------------------------------ + -- Configurable parameters. + ------------------------------------------------------------ + -- Target hardware. + constant CPLD_HOST_HW : integer := MODE_PCW8256; + + -- Target video hardware. + constant CPLD_HAS_FPGA_VIDEO : std_logic := '1'; + + -- Version of hdl. + constant CPLD_VERSION : integer := 2; + + -- Clock source for the secondary clock. If a K64F is installed then enable it otherwise use the onboard oscillator. + -- + constant USE_K64F_CTL_CLOCK : integer := 1; + + ------------------------------------------------------------ + -- Function prototypes + ------------------------------------------------------------ + -- Find the maximum of two integers. + function IntMax(a : in integer; b : in integer) return integer; + + -- Find the number of bits required to represent an integer. + function log2ceil(arg : positive) return natural; + + -- Function to calculate the number of whole 'clock' cycles in a given time period, the period being in ns. + function clockTicks(period : in integer; clock : in integer) return integer; + + -- Function to reverse the order of the bits in a standard logic vector. + -- ie. 1010 becomes 0101 + function reverse_vector(slv:std_logic_vector) return std_logic_vector; + + -- Function to convert an integer (0 or 1) into std_logic. + -- + function to_std_logic(i : in integer) return std_logic; + + -- Function to return the value of a bit as an integer for array indexing etc. + function bit_to_integer( s : std_logic ) return natural; + + ------------------------------------------------------------ + -- Records + ------------------------------------------------------------ + + ------------------------------------------------------------ + -- Components + ------------------------------------------------------------ + +end tzpuFusionX_pkg; + +------------------------------------------------------------ +-- Function definitions. +------------------------------------------------------------ +package body tzpuFusionX_pkg is + + -- Find the maximum of two integers. + function IntMax(a : in integer; b : in integer) return integer is + begin + if a > b then + return a; + else + return b; + end if; + return a; + end function IntMax; + + -- Find the number of bits required to represent an integer. + function log2ceil(arg : positive) return natural is + variable tmp : positive := 1; + variable log : natural := 0; + begin + if arg = 1 then + return 0; + end if; + + while arg > tmp loop + tmp := tmp * 2; + log := log + 1; + end loop; + return log; + end function; + + -- Function to calculate the number of whole 'clock' cycles in a given time period, the period being in ns. + function clockTicks(period : in integer; clock : in integer) return integer is + variable ticks : real; + variable fracTicks : real; + begin + ticks := (Real(period) * Real(clock)) / 1000000000.0; + fracTicks := ticks - CEIL(ticks); + if fracTicks > 0.0001 then + return Integer(CEIL(ticks + 1.0)); + else + return Integer(CEIL(ticks)); + end if; + end function; + + function reverse_vector(slv:std_logic_vector) return std_logic_vector is + variable target : std_logic_vector(slv'high downto slv'low); + begin + for idx in slv'high downto slv'low loop + target(idx) := slv(slv'low + (slv'high-idx)); + end loop; + return target; + end reverse_vector; + + function to_std_logic(i : in integer) return std_logic is + begin + if i = 0 then + return '0'; + end if; + return '1'; + end function; + + -- Function to return the value of a bit as an integer for array indexing etc. + function bit_to_integer( s : std_logic ) return natural is + begin + if s = '1' then + return 1; + else + return 0; + end if; + end function; +end package body; diff --git a/software/FusionX/bin/k64fcpu b/software/FusionX/bin/k64fcpu index 25895fffb47c3cefdb5d4ec67433c15718db3bbf..ab6e2c48a6583667100a23eeddcaeff2a5cd5ec3 100755 GIT binary patch delta 14454 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 diff --git a/software/FusionX/bin/sharpbiter b/software/FusionX/bin/sharpbiter index 618f28913a2ceb56c33a726166da54dfe85c223c..5cfccdbf7e06c7f338af91eef21808079b41b857 100755 GIT binary patch delta 2062 zcmZuyeQZ-z6hHU1UD~m3eT=?#UDprWxg{|A#ugocLC40Ifa}Bv ze)rsSU++74lnozcy94a@o}kNC8U`p0Gbr6t(`Kn8P}zU{ve`BmWYFbi(1hD9m)SY3 zHLEd8x*%%083(4lMi3V`*H9GSc0P)6#VT9X!o=N3T;FRlT`@s? z>?5pETzlPSwi1h!5PMHR^eSPtKOO5dVlQ(@A_4Xsex~HJ5Aa*%Y1V>eu0qy~OI@8d zwC>Y(mS%n)|fBjgK2b*rN=5fyCbz2x|6Vf)DDv37xTvBABV zX?W1x7~D6Navf2fmn{*`L0d67;$w#d_b0R$M5KER=M~J9PYUOmyUB5;#`SURE^q~_ z9*{gtN$@T(=+r++COwiH^izU;9zQHtY(M9SQ-M@C#PyHS<|(lai+CLSJ^8i)u1T>Y z_@yUgla#o@JdE3jnPW<|;6;OZKrmk+W`sSD6U1097;T(UwnZ@7Y2G0s&BE74Gp}w? z_*dhG!jhiB{8Sr8gm*Tv`V?lgCY!3NBJ6gha1P;0!utf~RjV0TB0M7lQ)~6o44g+e zOt?!m=@K0!3&dyY*@4d!Wel!;CkiMg0vC>C5N+zE1VE!6#2RnD&1_CJWeT=<3zb~T zJ?3R_swnl0`WTG*@eQxfIdLaB-cDy!M<*6mrzFfY2k>H%-+YrIc*PrLS25@-VWZgZ zFOk0?XU$})`~`Wt^WwUhg!1S)i?ZMEJWbx>|CR1dOTS}CALP<(7jE>q*freltB|(} z!P67T@iX*v8-g9={ejSbYi&ylHXDNVhTuZ{-XF2frcgD&HSF_-<(UNjoh=7K#wOiH zVlGdV%y;m|fREkBiGaWPuiO6|Mz-kj#Ro~xOX7e)#YFn`<^K~CEs;Jv9^*c9hR-Z~ zN!%Qaun{~GoNqZ`XRt&2E!ZhZrDNKf@cY@=5w1jQ#LI@X;>dJnchjxZ4yh5)T2vEb zL)cpGXHB@Ryo3$o@$$7KmOX13d7heeDWBh2X5zC$xkV)b8Nc+{t#wlad&{eylW*3w9(W{#pc5sq;46iSU1e=65fE`|^a9;-ItU;`<^~+Au zv>OF(@OFzamFDCm?6st$00AB@Lzb>Z042DwG30)0Il!t^3yjj!=HIzFat{Ci delta 2106 zcmZWqeQZ-z6u6a}|9iy+Cu&Kbh4+e2VJ4z6Bd<^!-P%$xsh-Rb^ zAsHxPcw&UOVAaS(Si*dYm=H4~8i`StED?>7usv#EA7Sc*qR2e=y<7GeH|e|Qch2wp z-nr-AciY!ZPju7H0KIfD=oFj6gfxXIX*yW7$<&IVwfES0qc|F*WP^*64%lltPjAZ2 zIf)#>m@l8ob5NlmU;e@JdyXLG!>NLO2%*}(4x{j%{k}X`|B4|Zr3UR%eHw1s!}KH! z+MO1?I|2XML-fd9{x;SBI=t&Bx9|FQa%8t;Py{Ka5DC&k`Kv=GVa)NQaT7+M&*7r$ zq1o9cHVA@3NldAM-<->q?k+{7rtD8|;P&(PqAw6r%vMi&D`$I9q?6lWv{|f+;{sL~ z>~u-=bLe#?g5Tz2zBQ&8NNUiGT&v_s8?33pe$G$B+`@=3Q-IZl^B=g#o!|X4IYOnB za*LATcBy`d1X4=eitFJ*OQ5IFDPHF6e)zs{ndPiCg+=0eNlN(=g6<-5g2#_SqkFb^ zhG}B#IBanj)A!*pGGYd8`5h|b2xsg^MhMKFJh~g~o+5E4XKiMzk|bxnfO8*@tmnSX zI14VKjB@`**yAZmj?UIvkm24+WF2x)wJo_=(GtYOso^}tk05U6m{q(`hXur?I*e^q z=Iih*#1A6g&Uc>%Qb{E@@Krj7t;{oO%PmF%6JDSriqb%ukc9FOCAryTGy2$GRq#BU zj?ZG3rJ5ll<{18X=Pf+)!<};|IpNiQ1%65@0+ei*;DXOB8ZfmDcQg#t>$lvvF*#Dp zkTfGLK-3qaV-WNfQ3ZAdio{l;X#T?o>RgDt0kN z`UdRqIq4O6(^n=YkvXNl4bhbPbBNki^^IsvsjoqFr+R~`Ud`0=Dll5bR|7l!VR0Th zv{%>yA+d~*;VCKST~aetDaxdT>lyec;G8=(s#LQcNuk zc)hg}ZLE^lN(M^Dp*iZO9k44}L?_@_bOX&mZfQMwR+kRUW|yB4`Lt`M*ML(E`WKb0 z!<+2wGN1crj26(i@De^7h3)uUDZGqq`-k#C*(U;OcPt#ijIHITQM;rfgmxo7N9Ao5 zZAQ~Lrcc1wBF{Y&Xk~&FqG2!0TihRI5jG5DraEJc<5}v=u4y(5#H!9Ma>C~blr zl@YAJr!tA}E{R3(t!raf@zP#Vb%5I1@kDFGLhNXE!dP`A*uk|4v5?m=d`X8HYZMmO z_-!rxjfB|9U)YHj*iv)An{7`%=B~m%^qkoPz$jV2f4!f%SS%WH>P2SDH|mufaY8sX8Z++8@QfLaFFV0@s)@(PHo>=UC)@v zeAaArUu|R-d3IXDD~MTBT2P!N?1RHg{YktVNZh0~ka<5~St#4iStk*9-d>iIoJPD= zhtDErf1~${n(8^0&j(w^uZZ!UysbB)%g1H8$@OUo<{T_Y>u#NsTIJ1k69%EY9M(Vf zxBPVdH-hYY{2HYxu*d!CNgIcU;~^7Ucm|r$Qvd2BRZ6ai9ts^7| TdlDhn-UdR}Xp2^B&6@uM7lw_) diff --git a/software/FusionX/bin/z80ctrl b/software/FusionX/bin/z80ctrl index 3aef72c7ae5909a3c6a882313c85ce4d223d4f84..fbf5ee30010d24f58f3dc1b6c233aa46fe95b10e 100755 GIT binary patch delta 4580 zcmZ`-Yj{+}8J;=2Np?dLHVFyrZnC*-lFfCq$>y?#kPA^tK`{xGpj8YeSfF5`prke> zplH1S!G2WuVdL7O4?^U&_Gz?OY8zXsVxvtd?Sph-yEO&T2vMU3(|699l+&o^d1mMR zzW1GP=6o|}&Y2y2&@lL*VW%(J)Wn#niK9F|`_9N|u%PC@P+KLj4McIenJ4a% zE#CfAYvD)u`5E^`e1sBTdi#rlXE)1357| zevy0pA(LlHI0Ak5TI}IYPMflA5!)EEHPSklJ1u(^J{jh;PB)Afob{C^gScBd(O#;dl(j zQWx6G?BNVb&2`3`@;E!4%h_C%wJ3Y3Dc8xJbWg6AC)0EIHPbt}F6;OwS_0T`dWd0A zqK-k79K>#@d6csYyAY`leFSWzr}OOQ-@#f5t3kD1gw+bGSy+*WVYP5-&YzbtB6IRAT+9;; z#URaCR2OnR_V5hgIif-RcCcmeGUQ8m1-m3c%ZY*c&vmJ-{pT247>Z&nTJX2&_>E*O zu*bIRJZq@FV0u>-G9t9Ab9AB2ycIvb@PSRrTyVRSp zO7T{mxPFq@CSA4gEz_cGr)-?4I)YP9{PEnt={#Z&++FUi`mxZOP}L>snzFrc(PXBN}AN_ za~z(eMUGq498Z&CeUASzNj#Bbv()Ny{AZmOkBQCXp~Z!6<<*brp~8ISrH|=o;VS+Z zr4}{t9ki^-X?_q53K3|Dy8Js~IbnH)#n;oxA}3!(9~L>|mw_>X@yp3tN!jiObDy*p z(>ixr(rjq3J*hiDzhpA$8MiC78jRQwPfPDi*zZZ~qyM-} zD8`Xw0g7h+P#C-CM32)&kK7y+y? zi9)EEOaB-inmSjO>X~H_Gz>e{?YO-&i28uAjkKj%&nwt{h2}N*@0b3=o@@So_&ebjzE(FKEORI;-1H`@wKep9S+cF81_Q?9 zW?(dAVD}HTO7UhlIm#W%!)~fUwbM;YQSEWlCRBbm^_1I`ULi@QfpU}Wg-KBgWRzo& zRAfe{OS4~?tITi6BxTX#6=@kKvFjJ&mc;_)Y<=kD7vnZ%1`7>J)i>y^ih|e^7JL{X z;S9UTY%*86tig#WxJ3`XGn!FjsfLQ_`AWN4N%pJSJX!;7;Z$u}rbz8|Qkx8|i%+G$ zR=RAfrRKvcTD&x=8M{KszA9z-3N`zd=JZNcKUAW(w@{1ZuGKAw16Syjufp1b&8`Gb znV>s#^lr+&uF~27HdV5lb?gJQeP&vQ5w|OD>LF~Wu~PN0PPJZDF{!#2s;e@`J9Vly zRmG+1E~qX^)gGN{p{l}4QhQts)jLw<*Qpv*72fu$Y7SHD1+^?B;?DSy+gl^%~;mwE(fY-nNi?d|F9%kN#; zw!A8PeRLqY{;bJZR#J2OPv)ctV&<)gh%mi4-o*o3ZvH%$On0OxqqTvYJMQCMg^lPN zvBXsd+=d!273KrJOX511kXyQNr$fQfIJ(WD z1Zye3Ek_xvrRKIf6jn!pwt0dZZ+m;1I7J%~gFhLUXh7A#L4@0HukwSa2JutOMZ!wP zTc+ZZlI=pl(UT>f06mR2DjV_Jq;%l7S@{8oq&>CNxx5H{1`aI$G*$Hd>;oCfNNwOx z4+JB`zz#fe0i6eSKYG7Wu=eg-#mF{xXNw{AbZ17WL!vF+iP13#D5eX+&UaVLh#WO=5UU54gp6b{u$LG1dh(^&aHSWjNz%S1d~c6zARv`!Rqe;`FLs8ZS<@6Sm+x zyw_~eeqR}FI#`q0A&1OZEEBRpoh@W%YU%7jM?z5N7P66AiaX>;u*e%?ES?!LgO(<$ zIkZDTqc;!rWcJ~Zya6#15c>nLSs2nT9MFXQ32Y>O$6@t;WYSz_~z#k^2Jg{;EDo87imz;|_IRkh%rOGo#hK*D)3q fPxpb*{<)!^}fG19u169Z?Ck1GEKuU7Ik delta 4215 zcmaJ_3s6+o89wJ;Ko)mlc`59|vOHv0o|lIR1Q&c%6ET8HHBCgLXseE~I>`9zQ531uOkp?2yY2wkE9C7J! zU_^0*N|niD)iMa0lUH!H0zge2#nlO5UPwz+ws;R?e_Z}PJ-j-9NvbOF9eRNwlV>5h17z-18zky##mQ1_l zsll^^E@llcG099pk%4IzvE~|LajY^g2;_`IOcB2w@yCj)-uN+;AN`-ICK)1qUsdG> z9$VX{6b3TI$f}I>7K(b)zpvhrhA4r0>5thtO438Rm7Sr)JS1Dr67Hwj+qV z7sR##;ZD-`t0vh7vS_Zv55S0fcY^fpfc=WZ+i04W#2aaamTH;zWZ>ok>2IXnT2lNH zD8pEruN=%^#uf1C8F{T3rBpnj&t0}A>xGwlLfKHi**es}H4mj~6+WhI_Ai&XAsIer zg)h`<_pgyjQV^V^ROI!}zC2<*WwW@2Hd?hfB1q@#ia9U7c zZ~^cJi8bKc5@!R~2M076z(FAa>;m2{ahmWC3HXzN_e$<_cnJMWG3%>VOdkh$ zf!hl?JB!wf#^8@bD@J<-tsbof?I>Ctv_fxev;W5yTs`6#H3rxKEx@ z=_V(fad7Sx&g^(t39xqJHWOJzrbY1KiP@d3Rr)_P_~+BXQJJ0$up5Lo#qLk^RQ4#S zM~q5?f35T%(VqJq@HfCOeCM)gSdl~N%%Y+qhwW+^ejV;Ml~J$C*7p8XsGOBO*UM;G z(J-Yii{6E}KZ`y__cV+Cj4mjf{#oRhY0kz_+>}GuHVoa<%bchEB{E5zw1n@Pj~0Z@Xj&cO`9plxwGlj(HV*+x)f?3{i3PZ0wq2y8tL)qq+}P~m06goEIiQh z2J@Z&_kc}_W4;Qh+D#?JnU*tFy!WwW3{Rg{T2btZpE4MwAGZnKpa;-%up6nPIF+xX zzeCd^H4CMNq-HrZcG61Bd@+qFaoMg&U8U5mmb#h!)L2rgOzx-SC3Di_!UJ_XB%6s| zO#fBGb_(t%Piawn2fl*vF{ZE1bpw5eHkXc$-w8HRvV8{j8eJ=O#ryDZwMe$#z@DeX z7hLgw1N%%)?NbALl4`-82K$X>jY^B^zg8H&bGn zE1tlflx(LV?>kgmRuI1i>^{k!Gq6kO6R-(j#f}$~HNo+`s#*BGNy#BtTCsq&Q2k1( zrWjO{geql-RJ{zmGduIveyXNjnYn@O$)NOomMq zI#JR!h7v;VQK`ofKHK1Wa+kg;b);BiSsWt=cl-i_>}Eg~E@gMH@^+bEsX_LoE;E^= zteY(5nc4Mnzg@nIw@!YDPr*&iH1ZGL3*zx`8=RQDXSA?9mv5vU<>e_?ot#}ja}4Bm zo3)Sp3{h3Bxr;f;f9meQjhGbggYrhj+cxfMC@=87Q~7>yaO?4bChm=#_R!*WPHW*5 zIyXVt>hY$}eVaG6OvTqHK32um23&_8uNSr#7!Ma69{^q;@loIz5}yFBk@yVoJc-W% zFO>KK@S74}0iGan7cdSuO?rY}7*izC2mFS_kAU9-)-N24Gzmk2@o3U7D{#KVali!< zJAn%&b_3&MOusbXB8iKuDmfc1jS3LO5>Elfvr)flfU)}eH5<5I;swCj5-$ZVrO0W~ z^mJYy=9X^EcPKcmh1EHfdJkpPr7PP#G`()F(&8a+-DJTH)Ln`Z2fHA|;H}}&D!TC; zgnvP8X)y>KZrBd2Mbu)(XGLVe+K#pfE&6rPF}zw?g}>93di}ZZUlZ%^7j`>Ll!}QYBPxVc-?9*L}JCDM>mw1e~+Z!Cj3%y_eZtM_cO(A8Ra_H-V rshfH#B}~kFi-)lXGsv=Qw#7 diff --git a/software/FusionX/modules/ttymzdrv.ko b/software/FusionX/modules/ttymzdrv.ko index 689ee126b9342027853d67a3b9767e65b2f9d253..6bbd66c18fbb1ab1326058bf01c79191a9140b68 100644 GIT binary patch delta 144333 zcmce92Ygh;_W#V>o9#(AAc_cx5)?}k0xY29|2_9^z*l|0_xb()zR%v7bLPyx#JwQ3qI=mWYM*wmtJl$uu69^{+li>KiM_tC&82Va_i)n+`5ve+`4(8g;z2~ zZry|?xpk9lpfU0C{h<8pO8j8_2H|%ve#7t^@YxktUzB&_*9SBvafWB^%)R%7dlpN( zRwcW)_*iDRdo!L3cW=j&ySMq6C-bH|&6`_ioyO1bj&kL4AKMvO!-IX5y=u7mB|sW} z*DZVl=xNAG|B!1i6f5UzrHjnW*2Z36dLvSFtqGTCB22SWo#tQ8;`vw8@XQAH&fGNb z`Y**A>6sUDtw(e%v3?0FlWYZRqT}`hL_gfbQ*LzXpbyeuuJ=5g$UD-;UQ zTied~-cSTvuBWcVPJ4=9UuZ-K%6@WEy4RH$;O%*(p~T?jaW`EyFEoXRd!Yq=8n;ta znr0o*Uq1%@p%MCW7ip0dH1U0CThYGcxY|B1g!GI?CM{}+b9zH&oznkyj5gZSr3!pG z{CxEXk;d0QV=FPy+pR8KmQ}`hg28*!<@gtjRsIQM8sk{52V6D8M|c}|sCPVbE%a^9 zD0!!(ki=H@&&Br7*!!=f=K1f$mh(T7T6n_0l$pnGoV1%q|3qk-P&FO-qs)^3AhXQ9 zw3W1_w3YbvSJmvF$&0r0%0S}nt~!bR`~CbgMLXtKH2=i?%Ds-TajItbZAgsv%9yKH z{kbv!$yEQT6#ng08~&rI{#RC2HvRw8stW7E^Jpsl|NUO|P|f_$r}}U9YRuoNg{FVH z|ARICR}=j=EBZS{&$*(lDd7Ltn*MB}|AjSae*e!E;=i#d&)+J9OLa(&KM~%4x~6}O zd|x8+{Y}V22>Q1%j~MDzKPu;c$3y-jL!Pg}QDgjzL`?pl2ba6*41Xf8e-n^BYN~K6 zBKrd}b7fKpuJFA|xlF^=n+=rD4(U)ff|lS#n z9A42onO8Mjm2NPXZy&25CZuHPgyGeOs|T(b_7U9thnilCce3)XQ6UxkcN2pub0{4Z zRCCSl6=kpZY&WqsFlvSGr73a+M$Ztn4J{wZJ+j;KIiT!vqx&T?bJaK5IY1EqA@_V@$&smJp^vrK8b~+#1a_E8sF4 zg*@D{d^Zz0HQ@}SGaCD5JYTim$Mz!ywU*EX7^#RhxF?~K5JxWAl^b7S41Y9IJay^^5h~r_{p=SdOW+Y*R?}HiY61e>dCenTNl4|3 ze+VC$k7RzP?@P9prDfOm*x@Q2Rq83MHYKm9z;rI!TL@V_2Otp-CRo!5sfzah2KJw* zX#YW{?)u`Lqn3b~>^0cU0od!!8OD_@1~!ntL)KfE@M6Do(mE<{LgB14A@&j2o@`MWGwTX%MiREij|p2sn1&>x z(I$J_KiM*0<#@iXjnLN)DlDi8*J1n-;<-2Sc+*}#hIiZKV>G@&DtxXjl5BMn?|(8$ zS+!;%tU{L@eB$&4R%4A!>)obH`R>?ux(GU11((;wX5NVn%y=yKvo8miGHe+M9P*Qx ziw0xR$6X1x}t;u~DbmP6_ zN+0&U9Gg?K$Q!2`cmMKdBK3}>Q{qr2DTWr>@AOUySctR+nsLv!SKkyf>XXIav1^y zqTK6FNdb08HIb6xpoK$WGSG^0)34^uhke%ly=sJoDdk_gQ$2ar^}&om)k$`$s*B`@ zc9MdRoW1v&$V{BgFZV8xZio=5`nYFzvzq6)+@t$%=+9h&M6wuI0j#vXKf`h2+x{Y}5iOk?#?vlVb*T%N zek`aS#4>VfA{KyD-@D-4J-`}5Y8$e`E4cgIfb#tbVV-mM@U%Rifw3CL0&uSGI)%0O zs+#4&^wh(uOff`lu_3m~1J=)mT@8TsIbe%6)afMJCvq69(282`-Mh7Whcs`z_okaC z2CC;y=^qZn2y>o)dm{e(FgLTB7JYUYPw~`G-OX`v=i($!Sr{_iD*$bx-+pm zY%Q@1=49O5l!Oumegh7u?0QhW#V;;lGCP3dlz+jmc0m8v2l&Qw6~!5I{amz2~B%v?jM( za`oK089~tPm|b79-L&MW{@SsN=V|&0G`)KBvU~UK>0@YmtABd`Nm^UlU01?q(DZ`j z#NufB-qC-UUd+-YaclZIPvgamHvKM4e;m_qQ>Rb)AFjUZmGE)@e)a#5?0Nk6LfZ6G zG5rxtfB*T$^bWcr>k5RcNkO3M2;*muUeYoT-s!Wfyd%5gOGo9e-JMWjLG~113SX5m z+FPD$JHj&VtQwtPNV(LnXzLVvz3jJ*KCwUSM$n}_m#pHfRGW_veJYpv`0%HEpWW^CRo;EM>cHjeqhsIPc2OruH-gS# zz;bhnGvWFax(>YhIk{0aL2Q1lGJZBqKPtbzioE06$ntI}F3)F8u2p?g&D@8pSw#S2 zOoyL=l3CU5(EZ;+qNneVL#}1% zIW-%}r>pK5_l;elIZ+98LpzKt4dvZVI5|1Y?V$^|fpk|YC%_BjkRo-Y%Q@0wRoNch z-NMV0ZpI~%8p%#lBeXT@aNMlVRCAg@-mni(LI&apQu`xDBV&(S@>jKeUKq{O;Lh?A ztNmv8S&}g;bhdm0iAd-T`LejkEoDBeJ)oEMinY7D)P(aC+&{X>E2z4jrRw@^Ci$#S|Hxzln6z4m$=-a7$|Ya&vBsskV!C>4 z4w||zT( zS`S(sen$NC`%%ZwxZenXB@k2P5zSNio#pw>-O}|bN6V))3oZ9Hw-nvBhe5mknMDX# z1ZCL`S3@8p->Wh_1IY!mkWGlN+1SM4nNHf8@^71G>Z3kCsgat6PaRr_RotRXp2%gV(X-oSLI#tZFE1G*;t1P|sV&8bQWtHIuFOR&6y7 zwn|RlrrK&B)mG__diieQOVw>zk6x$_bJlKY(KBWWs5Vs+0;ryGf#akWlJRXfa2Mg z-b9_YL04QlZC2JUKz=X#a`w_a<&!g9UC&hVjwg4Ys=jXt4YN7?VOV_ftW;Qhap}_B zhhXtXKVRwFlFiZvR<0zQkH4>!rF~M#<81|G>nCm;EdM^Ux#yK?46aT~RPJ4k?R#%9 zH!)FMN^GnlDwPGo5r)j4r9Dv@U%=vAWMsOAt7~ph%fq*5Sv=u~s3Xj-=Itkz&Oq?6 z!T|j0FYWhWSR~hC&9J`Ly)~}nk0^qivhIs3kgn91XnJo&B!OS)iow)4fl_67_0A0w zbOosscv_R?tmb8}NXxA>;*H^j1+iZSqMW!er|v0lBxL_$OQo)Eh17aC~Xh^H25zoE=a7vC!3|sEmWqOtYNOS32Sn5 zYMx!_q{Mz_%JsFGk})l(uK9wFm^y7?XRT%0ca_;{?cB7}mF~IpGA3o= zrrIu2D^D7$`(okg3n4We;pG_rfS(YD#B?!H9Fr=plb`1w2ByiBmW=Jf@9!rj`0sIzj1m42`h1Qb;rek`@F{^~d(aqx~+&uE8z1@%a zwt61HQJc(?&bQ3{7hjF%_5Pzr%swxm`!i@*)l0DW7e=gEGIYsC-*nf*z8RSl&+qk3 zw^Hj0-wfA&l+3k$#A@#l@38ZxNHrP`y4{(sG-7FzyUv&YH9N+0$9WddT{iGGPz1J&R_fC8lg|Bc`y9HLI#{|^&~l&+{A4L9*rrDv~iL?%mPH6;rI@Vs|H;|e7T8GOiWFvcTi zw=3W+*>av08Bd~Xr|#r5bqH1yMFTUIoLygh>rk#Ga1HgNb~IcadHr5LrSTT!z(z`s z)JWPBq#bVDsgh>b7wjO$oDgxB9h!hUVMRQJ)iHdt;i+%4!=N(J0`y>16)-ZLSX#>W zUNDqN+PE~gqCRd%ztR~0sF6EZ(~br5#jX->TMnihu6k}ZjGB96_nO@L6Ba*_@z&Jx z4yNvH!>zp?F_#jZ!MG92*+mSd?c$j5G{S=cIV^Mcs%gk137tPbVy=rQn3TK(*QNV+ z^SIxyet6@nTO+Z!h}pPp^Q30iS8eAVB1#Y6Zc=qLVm5C-sPwVicPe?Om ziLItMxz-Ai+-%sni^1ZU+dXaf-{4uYr~a?oM$Z*%21kvYMq=FH8{s!iJ*NyL92xjjT05<;m|+|rs5d>p_f86|fI;E7W^o}Sys zl+;;&cHc;^Rr#6*445|S1YS#O3-;8{@;l7w)nc_AuUl*KZn*62C#{k$8r^hm>Qz4? zm&dK$EqUd&5qM!0u-2(vh^;nxyxN6D>YskCL}w&z^2F+QY8!%uDy_@YADCHO&Ev89 z47!@Wy>h1F>Y+&6f*E@azqfiol7iGGuP!Hb@lNci$s^T9>o#23_5duXK6K6n{qEz{g%OMau&Q)`>hm(9P}M8rN)o4S?n$PpGPbH<~iwapdBjoJf3&( z5o`tHsjQ~Td(3wxTiy(&jnd_A(^jdGsc*s10=#_V#NwmSNpJrCnk)EVuOV#**x_m0 zzFY}bdD`Zx+r2$wvi?=)l}BV%)&Dr`N?*0-1K*k8#ns1rKOrAsH9vSa)Cy_O^@W#) z8b2W1TB+uDiNm#y-9hqdSD?KQI_9g%{z_j|;jZCQSNuKz3|fz$xYv#g)htenu(yZE z+i|K|TGMZqHhjO5+bwN(R93IV`yH)i5o)*FQg*ywO_|{DS6#l_*q9x!SI>>wQHd>9 zk3x7)1X*};YF2oe+ZW&FPWcSaa-=ybZN4nmTe&_mJJpqniwC64ZlC)cb`tCgU%iLk zpK#o>gF-dTp>P*V5%wrUTU>Q*w{{?V2n>Fo!BY(tQd3hUm4U=<~Q_NVzoIhB9E_)$g}%g^A=$v zh@C%f4sFak=eb+nOe*=SU-K)V`OgT(=yefo!k7B&%kevC`{XoBg$LyBgldstxW!qM zf<0Axs)gHG%`>*&!m_5Dt2S4&wb*UbTeUnm`-)@7Xo_d51gx(@CbILMA>qsMUMyl4 zZ(%KP4zR*kce_ak-rK$$bbQFLRpHBg^}*I%+|+VNf4b>uxZ0|GY0r!>dcoKDmjCyw zqt%ychJEEnd**qZvzTkp2$piKs$?(SalqHp%N4qO9{?Xumy@^N4K?1P(#MkF_`D3C zK*%z_NYrjU{_#`E|KQdm2yZWLzq}~f^ZZ@efu4^f#x60&DpA&>Jc_aw zWbIJ721E|ZGbr;=o<&(C@0l}fm37bVd5K-a+O}?;UyzxfmDRRQmsagtwr@}o7C>Ssq z<<1?l_e?5dodC;e8r{Pvy8+&eatj!};K%Px*ZNa-5dC`ZAb+_+Eq8hk`b(c$Zt!;N0=m{aH+Y|+3MN?ZFE>*8vd-Hqm&ydMwJS=j zpdKYLmRz`J>(6^6hZhI?#{V3s2-dc^ytFoKh;x!4HN=0!`*LVzMnJwOevh1rv(#FvRsL0cM_>ApG0+%`CUA#@k?R)26Qsd2>BY2 z{$t3{JR?o5e|rY_nS{qh-d=fnnMmPYW&biUpHEWmcuM5!?1wRnNqO`s(aK}mlFV7K#1obq@rIRD z@Hi@(lA7jqL7U8j&jBUNAL40Rkq*>5T%FjYtA@(&^8@F<1MsdN2sVSP!Vf(DGg|ih zf$`YY;P?E%&mM>)_E*?cRq*#b@O2YJWPuZ5mOAVjw7hm?vClcdJJO}DqvJDAn;r52Avs0I3Z%I!%9Za#35*~)ny|OB(q4DEmFFdXo@;> zW{a{?ompp$oWyL=hM$N=?O=;h0fSIFl#Ax(NUJSDT1nmZD^ts{myybc<)XXArYH9% z2f;7KD$y%M2ZwdsWM&ID&I5+IoOl|TPAOg?B0M8HU}wU4NQB`#pv8_r&O)NpI?HW& zjCx0=l=2y+LBesz;h=dr430@Aq&5MbjbrOq?rm~vF1(aUXQ_5 zFBi)uJCH_U#!P*1AXDU(&@;1=zEV8VI%c|&GnX9N+)y9r7g1=2CNI~yyC4MDwO(M| z^&-NIrGI=MU>q)`cBN>>+bJHf567u^H?IW(6$cK5(?`DoZkXjg8Yh0u|} zS1Id20<}N!wCKV0N`fK^!N4R%M0Gt4XXAJ_Y!7DFcWBR9lWk$NTl(=$P=CcDQ$67_ zJn|05UW!BVTX;^<&p|_z9Zd;kQVuC1(PN$3iiNvW2{7x_)+{_ut;^P_ZCH4`S~pmy zwq@a(bS>7Y?O3=bTdQ?ydlsIk(m1TsCb94&wH|IASHi-Zs`V)AG}JZeyR0RnS-2+q z1o2)Q>}IMmWtGrlQ8QPGSbj)RR*9C}s(i9a^lX;(5wtlx+dcqmG)xd1QbC^+LfhlZ z{Vfqdu8o##MeZ<2>AG694<=1@o(NEP&%&QnmaY~zdA9P{8WC;#9E#=?I>+0Sk@)g} zYh9E#)`<4Jxl*@AH0LW6*IE(HKT_^ki!BmL@mkTr)2i@W#+=DIGufNWV@pJ<{CJF# z(uj!xW-J1mGqn*D4a@@qn6ySr6fg%Jz+^OHVyBB%AHv$4nT?njV3N-R)3Ff~4UCfT zjJS*cRhjmT7|e$$ho2EEd7Lu(SrkRewP!^ozo>lj9JubOTzF1Y@et*$bz%?y?Dfg( zg`3Bkn}O@+kov0P2xcV{5bDDFhm*{VH*%Dn&x=GJs~mq`#6?HS5hkM?0Tn!g4hX^@ zNt{JAk?H|ODvlRKd=LHP0L~(m%F45?JKR++h42BDO|3tO^DayK*^; z3boUqOq{G?EGj}}L>7-J3tkWr<8BgxOBw)zSZh=l@g^!^F$~DuCWK_ho`_b~s2uBI zK&}mhVnIK506~#N#F5r$v$08}f39eYwJDLPb4A;3&BZr}mWkF0qe@teJM<1vBYfS3 zf_^GdER2Ir1U6;R2I$~iW!46f#M6~^8$>uyRNmNt6eC^n5i(JU-6$gXXr;qOSdRV| zgaEfOJQ~@q@3t5 z?ngy+rHP@2I}vBG-YFP2F-+Q`kvNU#ZjmV8Y!q?1)`^p+u%s|O89cL&89Rd|HNnLk zEJkD_wk3s|?Eoy|xljO3b-`Ba0~5!vqzDsDhu!M(qId@W_?s6+*myP1<1v2GVE#i0 zQpqFReT5^D{wngF}^oj$HFX9x`q;7 zuMFKJvOH!Ng5BENbTQvULs?GB1jDO9TJineL_O6oBIPXyQSD5o$Xc6DVh}Y9rj+P9 zBVo4;Lu6%^hY<6)s*+Rf97R6AI-cR4b1Yk8inrz z7u+%@mSg7j2ttx&8}?7O%myz3mQ`OP0kL$2q3bPkvbhH{<24#)zJ^d{vP=UnW=jJc zxW)1ij4IF)cosWg8PWGhjk2d*I|9mhU0lP|FnyXJMA7 zu$m^8D_|(x66u2Hu{?H_u?S{#{eU4-jv8DpNG3+7m^UMdHY#`3@>6cYJ z*G)x>r2-+GSj0Rm4cC(--8ngZG<6}AC!n!M22u2ReHQ4B5h`Bo7UJa5PxUBBBGm^3xImQ z5lxYrBx_b)ens@<(aOzN#Lz(ftB94&Oqv%_#%+fyidG)qF2X(5Dbq?>^Z3};F+h}< zi+yh1R9%vb8DEFd$mzk>6G_)tkasrw`HUosqkz4^``lL;nh$P2VQMn>Pi|)U7irsz3lkq^##{*iLPx zNpL9XUx*-TP$?d0q;UEB*@qL|vx;Yj$ZDbAZQ?9*sg~SK(xYCc)h8fAwdm<2zo*{< z!)kF?Ggt*&%eft*MGyT1oM?nmU6tu1j7bd9(rJAJ*w1RME%yUhO;#JNem6#H)mIyP zUkK{`w0gcWe5Y{Xtm)l}^Kl1d%TCcgTGhN?ll@DTSkMP>0&SWG-T+FcH0%^@S}%qu z+e`_f6EYJyIRHuwr{M2oF*X=Lsj6w3lWsHBq&ew#mHT#yq@d2!b%shECGwQ#c8NjZ z=7bc+ZsLbi-xX^Ai}Mm7lM=LBbjE?WXtzijVmk%KWqC>}8s@_V0arW25I7Qkpf zuo^tI8{-F-!xY<%^#l3vckRacfzc3WyYYTN`E<94;X38wZZUVb{x9+Hh{uyUreWx@ zEnzrUMCcJ{peYO4Y)VH-l_(B>jI9QVe_KtzfVq_MlI2nAHXjs^`#HMeN1t-=(zJD_nQzuVd>w=Et~@;OM7+50ftlEyY~= z82I-NGqscJ4VXeFd)R4gh@Qb`cg_kT>FXycPwo|AJzM_{HPn1l=R0+WF_NVUKAKtT zp>EuA3KABU!|>x0Gq=SSo&trs4#Dh1tzQq%)VX&g;q^-0UXjIDDoL-3q!FrBMl|alHXx|vQ%Vfi%bg0XGq)T0eDqCMg0`h_K0jAyBzsIe!PKnz#x}?h)y!D zJj{d8NI#XM2I%Syw2YcKW-3e0Pc?jq7O@v0E`mC$oa*!=9kaMsY~-zA2nRpWxV9uqpbj zR)}lju=3KIxGsuOzI_v^wpD5ImI&|Unv#VpT=8Ni2p5`2YjV^hZNVZtoq_Yz#H!#C z3)yUentv56wk}3XH?c}t^p=PXwoWOT#tMc;-iv{l*r)7yOAO_gmEgBUPnZ2TxRK?- zXtHg=ExIg^K{YnKV7_=3mG;WKx3MQS<@vX95~@&+z75sVDTa4MOp@v{+JbROeeAU$`Vufgh;<*Zu(nTddLF8}H!x5yCA4I+NhwvGiRd0ryO`+^hmUy>j3iE;4h^kIO%|4B_BP0#frd)6z=6HrzsB}fN$8Uym$a(y`+48K-`BR?>HzT64iB$>Es^`V+=rj#EXd2D-7M| z8QumY-csft6wW)}Ld5UxQAJ&59reIC*4?W=iS}i=J${7%7N{-D49ra2972QNJE|}d zHu@ap^g)rIXa5N(wNHo6nEs>26@iB&Anb(Ik+cT?eRDoF6IQ28_yCq+S04WW=XRa) zAwfFjrw@eNqyGX9se5^>8RN=w9H{j=IP&g?!%1xVH-1O!XIi}sv8#KPR$m1t)%|m= z9-V;tF|}^&3FmGt$}~K42LwWw3Pml|n3L zsGadFqML=;hoGyv-d9nPXk`-Bz+E)@8n-C>|02SJtoKhD!-@)QzpLXZ=l>#_#;Wsm z4>$m<017N*>_!y&-;kdb^^GT#J{4!%qQ0%EZt4JKDjLF* zsFSJcD#Q4GU>m$g(HK8)0Pdz}q8}KGJt&&20v={siDaZ`n!o8anr4 z9&7Q~+3c=n!E?JnJdi_cl(upgdEXLLgC?BD)k4!is3S8|+j9I6LR&WFtmop0#eno} zFniA>YCYz`U*K8gUP*=z(UPUS^RegP$qQl5W0JeirP_ttHM5MG<)yCLd zRhtB{9x^nh+ey=tt@q=NM@DohIy4hS*aBl_=vm~6nYi94`9zF^!F=(FNJW9qp~;Fm z9ip3zO>_(`bj?IJ(-7Py8jV`E+$9RfXU(A+!?T+YMKh`HJ;~Mws|L%!dNdt$(xvxaT#O0ES!HO z;7mRA{fbcYP~$KprLMkq!<>9r^Bu{Y^$oGt3M|4pcJdh3*P#Nk>azQWstLP6-~`(F zu*h<~i_EldcoKDu0uM4u{WM%b_1zS%)|F+4MN-o{q2K*dLR=I;#64#j>z5kb4}4gd z{xPJ-{o}PaD9WkBi0O{kt$!80xz27WAafq~8A7pBS@oIdYITl!U>X|`Erm2eK062* z*~QNgOt(RG2gFONiI3r-2xS8jq|jIH#Oq&vRB=~|O~FoF=nUv7saNiw^7oVzm7+AH zRV>`jfFY8RVjSms3vZx^!TM|sFzt{XBi%5#mC;9V73)-%9TBM!w#Cq8S$@4c98Me(>Ph{l2}6?}bR}{0 zh!}>;@fM%s6lxtkvV;u`H%zl)ds$F9c50wgY7aVle?0U}kO2&A7CD0IrOJ%Y@mB!8 z4$9WgafDi-T=*O(Zl}`ms5rt8DnZ8}sZ~n5W1%wi3p`C>nHaYEBWds`?m?Ye{rBb9?EggZQf_9I_o)Ghth7*Ebr)7RAnzZjqL#Szf=;I;J(#TzFX$;$bSe7ggLyuTFbf;K} zN?7-=!N4x{cBH-H{Sq0bOFB$?1b#ud1<%cgtQ;SW9ASug%keEY>`U>+o zl%8MV9S2nME4o>t1(G~f*C&DKy{|-+O}$$A@itng{PLAZPf(8lDG;|xox*IN(JL>R zFtU6n$ZZXsl$?_y!s>VmMj2I@#0?*z5%Rw8q*!9Hu`@*ZD^L#Q=1K7qc7M~?U}~@O z#n+;3d^`B+Z5?Ey2mQ5PQx17D2Fs3G$iXrdc+O(JLT;UD7&pdnmikZ} z8#gw>g1Ui;dL$|1#svo8mLXgmRCawUvZ8cj&{l(=L=SL9L&`Gsj(PeV?vVB?Dd)rh z>e+VnIq@QYU&;PXgokC#gP$As*Qn{kVBRCpZTL_+ZruN!h>SC>zk%6Wa`l44 zHWvQWl-mHzXwH{|{O&?!%XcC@#yTEC>Ebed3mo!udSOtDs-}&40PeFSZ^>@8ZGQ(VNA{v7AjX6puEzhGWQ|a7$i1)^^tjh{rl@ z8Y`XbyrK(LkHT^E0qv}B?@iY{__#vGP9&>~aNlWn(^_HYt z7Trh`FSV<#sz{7Qh$t=#?U0Iq!eVH?8bk4E1I?J_f&LbsjV30sgOqI-pHuIKC#duO ztW)|`)fo}1K)(XJGXF(^r|QKfLL7>`ZWI=#CW0xt(iQ zpx-Os{2~U}%w9OUobi8(K;CCRcDssFC z+zWoJ%rRMMTLYoD9j%nqh|zejaYOh{UZ)uRqf&E8#08lyBWHHD zk^KQHwqDIuyVr{-E-T~fVJ+>HSLzYxb}5JIA(a#b|Aqj`sK;fHTu~-m1~2WD_b-d7 z(DBw+M1dt;c5Vp}^y(k)%okq~$yR5{$T7_MN`USlgebA;O2rkC(9HHQjvDqepo3oF zX?r)4Sg%3VO!rLYDgIUAT389bDndP~jhikNkEtpk7RifwM|4sljUVpeGf_a zBw1cd^`qyBViQ$Aq&O_^LRFT3x*LeM)ST;mYJ1`v!X8G|d;t&o`8P?He<6aqu~oKI z9GqqO5@1t2oTYPz!9fxB0VUWc(weCT)vct_Bj}mw83@Ta(iCY1(sUJ$*g47+d4~;v zGR`ONO8y4!Y?4Dy`A3wnLMW3$Rfp?Rz3-$j?ads5`qw^@6=Lhp(2d%zxx zU?G{;p{zP(?segec1%Jr${AqbhWQ}nbW=CL$hE6Fr?TrhH0p8X$LnHVM<>k`#nq|W zIWc++cRo3tKIxU^nN#7BXhjMJkxC2kn2&2Gv@8Tdr#yc{#75bOJ^MZ&WchtOZ7l>X z?J%ki8?SMI2KMqsXLi2#Ly8D2!JOY2gETg(mXs(1?7R;6p zO|erW?k(}O?Z=7FK1YQ;1kb-tO^>1KSO(3`8L)^O7Cg|n>PNV;{WlTHPbvq06PqGc zUe^3!xzBJ_nw`MxqTj_G5a7YzMVn0Z7Ew3Dzif6B7 zQ&MlLuKmuN$b5Cm{aW#qhTI9Lnd3J^Xv2UCVYq=|`HP710}cw!>vH}*Hs}NG^{os~ z-*8hz-D~?w)zR>ojLjH*|41<5M%A_nmwvLGPSq9IS6OaP)hoysWI2zjKjA^VV}I0m(r#E+zBR$^b4U;bwOpmvT5)_Hij3uf~pZ zDcxdEo=vN!C%%LgQZv3>=`5s3xXQso%I9|}>x2}M;M%nkcSU^5C4Am6Uzq4~k!E}c zbT#lnVIB!6lfS8)5>g5us^}%@Jie#>MUwi5&-@+zN_fEU5g>g{nC4;$(|N!$Wr!>_ zi?Unh(B$+qw?oTw^Zee$jRf%pS!Ml@EYa(n?`0_qS(GzC%89lUoh%QW51u#SD@GQO z6PO)fSm}i(G*@N>fQxUG^#RgIyr~d6=_eq6)k&R^nKjo-mGOL9m{O;_X^;|8oHj@)S`Oh@01f^avtaT7)OAXXQR=AI(S`Pu z1xjt2qgAP3Yz&KjkJKz$}oK_w^b zkx}FX?ItNJ**Y0n?Nn`QgSd&%JJlIF>LFM(vn|AtMV9XYU~Hq@Z^E`7Qns70$vWkj zN$M9BGqVnB%bAvBphXJ0Cz+P4UX$P?nrD_eLRY_4Gb{w3e_l zGhGn3eb~PvNaK`_7HM^X`o-hfMl>!MX-jJxVZmCTg45C5&mVO0{ED7rb)dM$Dt56)icf7OK_$SQsxI? zq34x%gCu+kG#rE8L=V;IWqeU=$FAnYJq(n-ZyVHe)nkwT^=jyB*XTD1{|({uw6rZ1 zZ)pSbBFQ7`58^YJz9adn+83hN<8U=6%HQ?DCFHXovZ6ggdBrNtPw0y*Y;G2(Kciz_7BmE6nHLhV z5bKDP=Y<*v0c}<8vPsUu)@^W6sR?mz8{MO5u+&`zqm~sIf?GD@_$*k^8{aZEH#cM* z8e-+S`QiZR+S8X>a|_g$zwMRnHYqXGwGwzHI>N)w?QS0f0Mb$}*rWx)k6};d9yj-f z(&aGY31yaD8Xp;mbK=};6U~||mUIDdLEVB(Ea$pi8rCxo(Rr?k>*z%>;{#yYbIm+_ z%2G&)nOk7l9#qwBL8z}m6qy^u!^?of-vHog>Vn114dzVq*0v$X~;Xcp{R`B z8$O8lOLMciVG^JyW?u(7)qvUpmdMPtZ?WORfxvmdAvvJ<107N!XUDc0(U{~ecZa|d$6_$RQ4AVk~qW>XfPM902{qxgP1AQqRh1W6Mwcd16uj@fO{BJbkn(gBcr%BxzlpS2 zXMYEDI%RUWKD8oTNSvF8=Na>tClIl zBH(B{DDR^1*sC#!x>c>wL!xhuC8~k7jwo#Rr%^DCLscajLPQhW_$kp+(AYAaS}ygv z-F-x3%cImn1A}}{Bk~nOPHdw*7m3KGQ(ljR3h0!YNQ6S2;*OH&P4J*7DK$xbJ6aCT zrM?iEL^X`F8ilPAN+-)6RCUVcD7Z$Q@@bSB2+l`I*_y!HAew1x`8kLOLZ{?JL&P>^ zc(fFkYHE&bJ@9#+PBN2adfLYRO4Vhcwc*QcRM%2f*%~cn1dcYyf$M~zIGJ`qIUg;J zYhrs6c{=VRp9aw=7ghVC+D;iABR!56{IxOCOLqHkpm5Ap=;<|*A#IJ7irPE2e}_*- z0|!cmfvd1*im&b{FE#?lXT=U!k%yJLp975g~GP_H>wI$bxK-_ z6w_L>)myaw&nF6qfTHJgY9WuJPLHl5)UFSvsK&D@MV&s|A$;GRibx~mr^DA+957YCFzdBrK5yu$%aN2)bxth20o?$ zt5cSzNePRetUD(8@Fw)4mhy6x*o3vt!u8X=dcF-)LiPQaoTQE<9htw zGXV&llAA91ud}8R!Ah?>*%-BLe!ApLvz^50Tb5@NhSvCcBboGhGCEMAist9k4)37@ zWO*`OiiuSJ$jS7Qe@yA(Ln_@BmClhNxjkk&7w3V%AEr!qAsq=EXV4CBn>e(#z^LB; zo30@93>;>RBJNC0kf{XDGbWOUGUaChn6ClqH*zZ*2h=nBBGf|{5yiu%NNO+z!jlEw zW2U}snCUfquM{}ZoJ1ba^xM~Hn&}5h5Xl2yF*l|2fN2@dw}HFVPROHw%8(}FQ^$ds zQdY3~9^Mn{U|k>s@uRXTQ)=eXS2iFQGe%6>j4bfAv#6W2dNQ&=uUV@{;H>GjX!XcK z)C0A87|uW5Ag#U=j@BEj)l*R~OwcY0M`O&wbgdqQ1A1YG>P+?HF-~D-s+MxjsRmJ& zRyXCNp}lj*IZi zg;TVJ-<+0`;jH%{gdm;WVv&0jv~G*Juz8SYer}PU;^voyFjOziIlBq0P!| zRlqs)ffwm59TLak+YsNLZY4#f(v4+d7cJW|;D%xhE6n9aYPUE`rjKQX`P?`cQn5Of zf>u&ichyiHL$mr_m_*mOu#p)+s4etCTH_aE%NmjNU7AMTr=LpxBDE+7T1j~@DMM@3 zJ+){pIXpU&)xcL8$^E0}0-@iIoBoH}#5~cNvBZ3w?g~4Jpvg!-%uCVg#Sb6JJb*%$ zC*Wycgoo+jIM8($Zfe%2A+1PB4omhtb#NdXEA1DPb?6?osXRQ&3~D_h^Ks z7-7)}9!WUUU%rRp73ZF#1H@^u{TWwKb@lV|obLd*SZ}txr?Z zR)hbmY3Fs2wetfWAAt6E`=Onu0d+KL!}UX^RXEY;zD!pBK`Q|FX@HqtAkN?J-|RqU zdLKumN9;WEe&F;;NIVzkMA7H>dSy);==ve$Jru3=kub7HqofyzY8m3{l4dHa=4>*e z9qQVDqc;3~Hk0g8gwiQdZKY_u@$Ar6iucUYAA^ZJrUZ{elPq_`Q{M#_JWF46x+HvA z`&wS(ajYze>o%g<^71(hZdrE)!P~MPjzCy?z*;5NB@E{*?dVp94`99}+5wk|w#_d@ z`y&!SVTr-2B}**jd6wN5J@mI8hHt?Y>v9ek8vgG169~snbbJ*;W|lPQ4!1<$Hdj~% z!$2fUbBI`GUGAy}xAJ04QI^Jat*FO5wMR%x^kC_{tQ|M@1e$Mxrz~sFlNJdOv&2C7 zpt24e|Mnpo(|ecQ!Hv_1YzMZxESo100Cgnlg{Z0)*o!3|g(oh{;nEQ5Whl$z#@A7E z(q~R(`ToKAqcG>P0-gkG2akz$IJcG+a%mYTXKxEG>%onlU6|@)EV``7)7a}ZtgEai zPkIVd3VsdTm-W`#WWMkRkmBDACUye&(^zy_f1X66=mKtHGs*^N&~bghXCde~NF&`8 z9MBqWscf(Yz7!mgNz22xWi-&{;DB}C;_rIO?$O{a!2z|9UD>@FxHULn<4I8C<1?au z8Mh!*F2khi+pW*^EHO<2{Rl1{#M124l3r3amK)dNT?^kwJdfi^1TfwQp0sQ{Puc~@ z!N_>-v2x(OL-D$m?w|VqzR|;r1vm= zH*Tel_;`+nnZOqy)|XA!po#o5+})Qwph5TX50S8z&CnQ~q)CTXpz4a|gYkDRGz<-_ zV*PMcBOJ>TJv(4JWiz?-0>o#hmUOvn4mbXW8t;>Uwz=92P+I(pGpr03ndi`ECxUcQ z=HbTHdqBFBw$7`u#`xM_!S(_T!k@%FNILQmmx4fPC-OOdqgl*7`iX!o2AC$Z#9<@2 z#RQQH+R8_`k?qCUS5q+dQtmDUCF2Wlj3_J9#+LaQ0qAiJ3c&w&9w2-;NUPTI2Qg^b zlNzMwOWLh%E4s*Mv3CJ*mO#x%26l@ z{HQE=a{jt5(g+6qsORf2-8;I-=)L6sw4Uv0u>HEo+*@R-6TjBT0usd^P%n(f%>+NF zb5euWHRJbWbp1#dMJDJ+t--$Vk9AR`Q2`SDds-Q@E}`Qmx+v0|0BIg(%`mm~fb5Vi zisTj`c?mZ^3aC$YQKTmU(k!65jlsPi-x8#we<=moBID0&coly=VzA&EQ{B1lL7$V#f(rP*t^DxXEjDB8)(pu1#&|H+(9xTb_N1>#JHUnYH< z3uGo`?QuXv<9EkdZOHbN-RA?5jbGmqqFf&2pwW4M!){DB;RB5F5h~*jfD*3)F><%# zL11%EkdFTGmrRwPN6;hs8PA7|Jq;XJpTs*~6cjKGZ6VvyWOxB>Ee`{2cnr1nhfvvs ziVkgTUNJleFAdU7047Ksw9O58Sbkqn05O>Zbdrkx9peq92cZPJU)8Je z1l}3-I&i%MQ&0r9p+{GG^rpu>co@c@=lLTT4c_fK%#42)s7}I*(V9#S4wx`nYiKac zhaU+HBpy0V90Qqu9vG;$ybLqrNBw~BE6nIQra_%3hVkS6ro+e<_!k<$+8jr!#9IX# zX$bmPc8XB)XZpW^seYu$+%_=y_UP?2AY2{2ePA$|>kRDFWc)%~l7s0G7`z3@Bo~%_ z)&uVUA7fu09!0Ue-96nishRFccalj`lT4DCkc2=I2tx=12_!&(u!UU$MD`^py9lV@ zAR-_jiy{Mxii(JU3JRj&0`6B45djeu5mE7qUUzZjdrwuu&wIc7$IbKfq~5Ai+u5qR zx~p31P^H&RICO^~2k_%=6k#0uH z1Gtn{uV&*tY6K#eN&b)|&meiPAnA$O@m!D$2T4?N*7RmjF-MSoNYEV0ku*16F2<51 z-y``OK~mE|a{pzLBuV0fx3O_4tvDL97J-*9lW=b`RaAoHFp)%s49|l#4y77BQ(fNt z&p^#CAWFz^ILXk3x*Ww+q%Mo$21^**0mbGP3TPv5I<^4C9R-<#$|l*N#T*cgx5u8+wR~{fHI{_25AqGe&xn7exIcD%==V zj(~`{0)a1yXj5a-tBpyg8e=XX@DtJGgIS`)QUoZMd>yA5I?*t7Eqx!OsQQ;%_zXHa zYc}z66Z#rA2j?)-y^!BF~)ld$ZNieZm$N>)zT;CZAHnWsAT^K$hFnTqJot~ zoPR+Dr-8gDsG0Z`1@A(^g|EW?D=2}YyzM~c?M5X}K-4YoAP#L_jgoIe$+BNR8kv{- zRSGJ>_HqP72V2#)K@+H~=kR~gX*7kK8CM~q6iq3?b>&_Ez{mtDP5eetxS|G4f6^Yr zCvC+~1*KB7_eCJ@+w}7x{d`G3Khux)27YY%$-obriVX8%CHTwx7CDEY^5hrKq2OFu zIX7_=qUK4^2d(IVi*JNSi>gg!>@>t&jf-7@!pdGpQLWCSS1>f?1{5b1qKg?KwlW83 zu7&0(tyo*kCK1>J8fiTBW~MYA2)-Jrf>EKwzoN?aM#kvBlQmGChDKnDhhG;} zxzfm3^6yk(YW}8Dnp2-7#;i3m9{G0)QQLIrkEYOXG>r;cWcc6yFJ#L=5UHi_ws1)rXq)O3d%tevs1Tp=JejYM1 z>Z!Y+M3Lt| z^?WUIH!y?5xehQ%{ECVzjf}bfUNNKX&ooD3y%8W|WW`Wu5mSf2rDKSpll(;B2m%jd zX3owbP*4457Z4KE-yf(c?QM*U3{$8zq2H&fBKAoiv?#7i8~H(Ro{uoOL;QhD<(>q|YgNIH}0`d@|1? zAXKk&N4@`{8|^9KKa51$$YaPS)j}!V@GEi;k+X?WU_=mws~btI)PW)H6zaewRQWqD zHVYIq7h&@dAR?voyPuFjbvvV10nyQ(Z=f6MMv@Gbpt^ljLMREAgiDS-x;vium>dm) z-_8agCd{q|=6s8`r@!3DoDObrN6`rdRN%rL2Nbyd<>XG34E|q%v_X_E1_&1>Egq#B zlxh!m!Xi-rCxa;MzcR>&tPOTUe69l2#V<$UYO33N;{Pu#T0vw)@L!9p`}ZQR0*q7W zw~&f#?e^w_VgJ#+g$+uQwx-`6L^@S zzA4L6X&yL^p7bK7fYzMHjh%`a^_wh0*_E$tyX9WOyk^CAkJz`V$WbrCuaSWdEO zI1nqM(T+-4ce-Q4QvqtGN=hJ0@6Ix1!%N6jI2+Pw5g{rGoJ=f=O~3iFhfxmP-$WX=&v|oYv+;Y94@6j zN3u-1B$GTojHb9Cz8`-_p(o;qOHoDDG1<(wJ{3_-sIQ^JC^lIXdsgOELZ4iY`m{0X z$IDS)Hbjk(nf}=Tl^b6mNBg#@NTFlP;!(T0v8?th{5#NCKTUS?PvoI%2javslv4fq zFCbVF=c2=q4+*}}gs*t!v^f-=LO!cGx{{BpV z8{nf@z6!?)e~~ZP6pj-%kSu!EMph|%RN4SUkJ>f>p}Ua=AZ(yr0}y>}-vER*mo)&< zn`pU2p$=$=RG;hqU7N0N)TZ%wla8dI(X*+|n@H{{aVa$Z<_l#_iOVIRlx7j(Z!X~l z&cnCgh)8uKEgp&+hZ<;=rfda!)VOGz^Pec~Z+8w9|G(Thh#Q9iD5~Mcfh_w9Q8N9S zRHYT?^r2F@k~s#kawYS7G)b&vdi=0IF)1rXq6kzs1eCH&4?1yNc0#3ltI!CPC0TUZ zC8ixpXvXEJj~k;FU5+}_7!|u5^+{vYwuUI!|GCDv=Pr|d));l` z)@XHVfjSmgcmU-lHKQ$_pL~*}!*6KWaZC&pL)}RH570gS%`k&GxB^kSWqc2z zw{WG=4Sz5NZzthOqZ|K82#mzlgBAePfoZe^un%}E5ztaVyHrKV#U+*m(r8KG8KB2; z^&kaN`{gvpzfDd&r9|CnEdhQ*;ze8-5jwnq{vSYMT1O;U&kL?_1270rH` znoY~$&5@doD~H_ix5zhz3jG{l6kJ8Yms&hrRVGivR&u` zB^{vk_hpD*N@)|w@%O}f%|d{*cLDB0^gWcMWRu3y;q|UK;^dWYV?I}mV!okb`XOyM z$etk5yjn#6iD>UwL@PN&u>ev0FeoYNAE0`TD061KiT-azT5C$1jkME<|4^in9-l-~ z4JF+Q@B^a16G^1c49%ECNgD~kYFxOK9MbCuk`__Y;{X|mP8Ug}-t|bjkCI*l2qQX5 zmZ#*9p07vRK1w?d&>hiTMH1=z>qt6IN#_CT5IsyJk=|cK(zlfKJHQM?PZLR`|M~DF z2KbZ~53m@~i$oIHK`oN^s$40m?s<|YFb9hLUDdv z0a{q1^HZsH0lE*r0f9NV(rAq-dOt!NaEY7nG+JzWALuxxUj+CafnRX3Q>a4B)z{`h z*U)lV^BJH_(N0=+5H-_23O_DUb1?!f2vE&k5$J;JZ#M?f|6AVr14W73mu#BS(q|e5 zh=PYx!L;;Ihpe@@*z-seZE-YfYzt*!K(Z~gbSkpU1Nrrog-+xX3nTSnx(*~4>!WRt zg0v91?*Q2?4@L+jY@f&N`wX|#;}YXJV|C!ij*o~;gK@yH;0yZ`}NH#(qXh_BN zx8?8$Kt*j+>giGAq4s!Sgu4`V$RKA) zi&Pq~*L9#f^QWnyQBQnC%(Wj0#Z<20YHb!enO1H;VdEK zh;!qgCO@X((BmFJHV1RwD*nZ-1Eqc*KU@v4UFd^zHGrDou0&XIwm?=j(@n~(JM#%- zxhsP}%!z$yiW(G2rgJ%E3W-d%^U>JbBed*pF=}6K_CZj4U@n+ssp;}{2c_dgl4u0O z6JAM~1hUMLsxoCFb{SRRaVV_dnvtN_H9hXSSlf|WFnauEG_#uO)hFOrAA}!GWrMCk zG)NJ4m7*L(4H=54nH1%4zPkufjyQ%Cx9q=j1yVeHry@Qrh060R8HaF2;ZDlIoY@O- z^2CxEh{|mKl8B0{oTn(v(lfmg4yHi^ z6;D0-=qgGBQxuO1zPn1(kXiA(F&E*ov@0q6#BhXfNo`6+KaKXg7N<59^>3ODa8IUS zUH1tH-z&qf%tCmF$Pll)9&?Yy+`>xhhseiW)N_>Mr+)0j4hKvqORmF4I@i-IMr4f0 zp7Bvwv8rw%kWN4cU#1bY4_ww=#m};*iG1N`P&}7>hR78=8=0Bw03#?%Iw+H@?_y_0 zYyON!hJiqLoo30TKeucQ z^k;U`807KRK*;Xd1tNR^YUG|H<3EYrH%3bty15QfH*z|v!+REebVuV!sNT2xBD_G> z@HKS6y}}w!G^0l#ywZCRG%ix?e<3nO(a%v@6BO_9BvF(Xr;v=rJIwych-zulF?HUL zP>iRGS5%b)P4MhCiP7Gh(L&E28SaQxB+pYKoLY&h<{A&Vn9Bu!w*$!7;!60UJM!@O zAwUnijH_zFb?!l9jjb+oB@pB5rvp9WGM}i$=wtGiYXG;o%+9^ReC~qSjqMWo_-tsc zvBSjwz9~C47gpARkGkJnnMaLorhFL$DTOr$qUD4m7~n%*4qXxc+QUw$(=C2azlP)#_u!IMpvc_(i7~yqP#U2-1h_rHk8j4{&Tf*bN=zMwA((G~sTliQtPrRlUk^ht_RD;OqP(2o zvo3Qm5^MR7kgf5Y%e)b&j&Fl-@BtgP6=)Q{k9zTf%Y6uF96tkRX&iL9&jQu+BuLhH z#bw&Vai_^&f!{M;bFp?n)A>m#oAD2qIRsZZrEj_vxJo4f*Fq1&e^gj6Y7lHd;dt3X>)|fE_zVoW2LCHm!kGrT9pHc$pMfE=bQ|s_PWXB+J_FMs4#V;1aJ))j zB^<6AxCQp<2#kTKwZI;5LdUcnuFQ+Cz!;$Q&x7)M@f8>{8-L1pb%f%@XJ801{VEWc zBT$%qBfLI~ce5B`tTZ^vcu(TBs6?^ufp?5ISQtR~9}w6Z2Z7p}!CoP174~J#be}<3 zvHHednSfn5rLiw3Xw9Qjz-p#{juh{2@H*Dqz&#Yc6XLUXq%=ha?{>(`e$-4u@!lR# z5&LmpCq`}Sj^7OLK$KuV-jt&J2T-e>rY6#0;aZQM8HkOmMgUTN7pjNDhY^!`6nO>= z;!SBXZ@ZxadV<%p6DbxMc|s!l3|Zj$Vml{lDa{`QL4q0gC!&Jqp$7@cPP&6s;`IcN z`z%5s$_jTqGxqI7YzrU-O9;4#9+3wjgM%9FCc2b5c6XMT)6-nnuBG z96s0@C1lx2og6U)f{($^By~=uNX4E$pDL-e$pk_-QIz6YNp5%GE|i>FnnWX$>>(lUT%o6y#%G{e#_oQ^=yxAo1|rQ_!T|(TV<;d<-H3{^ykWa z0|z2pn=0aup$z{}8IIH=JWPh)jOkZtLC3FG$MUYy@}hm=>->3|qdg|swhQIzX)W!E z0M@1i&Yv4x-JGJ$;fGOMs8oiJ zK_jvU$jlv3Q1(C>z8n3^o-Al`gL;!Djw9$!a1%UolUy7C0g(4d?8wzxM%yLumxb6y zp2>>5=<&1#Ajx7>sbYy$t9r4rHh{>A&N{uoV2|AWP z_S%_B6Ksk=0jA)3la@j+ZL14aUo29BRyO=tlUBYCC@x2_*PtH@nmowpWlHN-Ohub) zc9W-6tWB|FleM6w_~u+?Uj%%i~RtE zxqT9AM6p%qLGHkmUjdk6o{8k75gE&WgJV1S3>&k@V4S1^F(Zpru~f!=y7 zo(k}Y(7%Y)3#O90-A8I#iS|*w_Bl*P^xP|0nuSK}uLZ%7ls6DYx62{hek}JYMp(Fk z+^ZYhz-?*JvfRl6>DCYoKe@9-HfE`@E2nC?Exy2*nmbRlFBsZxgPYa{7*y`f!YNc* zqW!ld($v4o>J-~KRr4n=#3V3xDL)PB-e?~+$oS+L>G~vtrf=xxH zprR+DZl+TU%vQ0-rfFHxk;IH><|QyZI1#=pcR}C?@z}f*;$iyxAhi>qj6C;tRtdRT zr(l!0cd(HNE7n-5YAss?pj-V(y6f130L)^G0oJpp0ZhwFO0a>w2Vh&jU=EUdH~SgD zr^I?s*D7&f!fn&Fh3e**Jp=l48p6%pWp5Yk5R!R9NQPPD{B!r%G%T1_71jNe0L4~$ zZ-BkRt_E54n7ZUXEi|jv^1#pJKH~vbl~Iyw`;ChL<18E*t>ixICZ^R}ElIzg%iW)Z z*N-MEEl$UF%+NBT3oxJ?*K5|LQuK+(Ft8doXbIo9LO<}aGN5@{uy0>qhLOyn*z=%|JTIU9gmf!B1RsrR=9`7EVl74|@|wFZ z0q9o!4FClm51#NaD;bsN723@KOlvgsEw9LK0|4ztlk-}*y8-yt&BFN&onx(MX{Dv3 zF_~?eE&cvdC}PtlGF%JsHtjFN{V{Sk9U#Ky9JmngJF)v_Y1ue>?nV6bsxX!ObnO7u z(>eCtEF5sq39@g-jXmg;Y4wlgUW);G1eCd10H5WgIRjaBvkVVeb%YvSA)~1$Z1zTmq z9TfCxh{z1K+6uKO=-q%bg01fA2i&It2ZF7p#^%q~0(})|bfEeD>^wD>TuSo?lI}vI z6v4*k4+`sJ*5~jE%{L1bm9DM%LuLvZVhjJ({9yrn)|}Y++1mK@pHL2K{vN9bg*69` zu=z=IF~W*9KUP0S8yrtv(F;@An}Q(r<{aEP?27$s4z@KKFshuQDU2TLSSjFr(X@EU z?t3AyqUmBfs#|+t7)3M0+=dB36wUNVl{k+PrRW-;jr_K?i}K8}4+8isy74Z$*8ds+ zxAuX}MYBa|0qY=vIRvtlpkgCMpbcDC%drihEqd1ypY;(bEnrA3@mU`MQsGouP7olf_`kzQ z(GuVF5lI}wO3Qi)yjNTaMaY$_B`?lKxS6;Ivq=dn3$`-SLgADPfVRJe=Gp^isyHvSU=||{wrE|V;9B=IgmG6+IyyV; zS^(W@M|tNYlj1RJDrxK7=BtrnTJ2*auGfY&ONNNovVwW+4=U(aDrmk4E7o!JuHZ&y zqF&Ql5&PtN9E&g|_WSi(MgCHFIJ0088-p}!0~lOzi**yi4aT@QbrXPYjgF1ELA$=o zb=0CevPe;xPFcKu@U-A9PKN3I0Jj?jZ_98h8QeQE9Dt1%yeq@Cu`CQL-krU1v`}F} zQ3kR-66-w==T~fpk8CwMk?u)wZ}2f>&}wvK7!k~fEuV+u+NQ@Io~MOc-#$Q9!d;S| zqLj(V6PB*gJ`0|dgom-;kzm~mzZ@Q(L}A5R6#IQ1HbQ?L!=ak_{gUC6?C>#Ghr3?u zICMPxQ9^|3v>rh3!lwnmgzpcCu{`_n#8SFsN59Q`36u9?;k6_QOnF2 zh=!zx(^WPXIk+GRND_trD>ertQQ8Xy6rQZcO+=c_A41OX3^gUI2?+Tp%nZXb z)h4+>0shS*3@RFhhv{>lhVjl-|V zoYjs9tzU#+SH+~Kx1(KO_<=CKx+n53a71uUmJ&WHI}O+cZaaKJW?tlo9H(xdRD~`r zbwu8ShYWwBifQ?35JQsTvjn?f-;^V=19OG&r>dCYt#?FbW84j&SHM`UMg@B*!6^bbs#4#ZW4uW;DJ>$ZET`9mD>&r;u%h>Mt1>qvYH zP;o^jQVWJUgyL~mq#Jc+xWhV*p+4Y>^dgm=?hpbEx*{fu4&z&aG~DO7B4cI%&Xh3U z75Rm9cve)FRp^RbO>Df@A#QhyT@eFj6rL?%wJXw=#ELHr(t5=xSLAzY7QQe@1~ASQ zSx0(xy(3{M^swF)8G?Cdc%Dq1jAo4jY^Z3uE7FtNu|N{fin=0|a7E#pWKDBjkuB7& zg|epAuE;;(gu=Hwgm$fUwG*mvk3)*{fGaW$Lu~k7hmbZP8Gm?}BY~X65m)3oYRF!P zm=2h6Qd98hLt=;>CsZR4-rrDIWnAO~GJ)qEIUf=4;~=4og<7Y|DbQ~gZsB0_5Y5S? z!Yv&Z0fnz285hf*(&`}@Vrmaq3lDd4u=^;(ow0it;(VQ3V$Utqy7Zfi2IjQRV4bmK zEAA0nXEqF(v^vx}t6|8r(5=?N1X@Qk^XapQt+TVd@UQ`~Zq&M|kV6n3tc&%#Su2i? zg(8|}Teg)-oeB*yBR16_AAgwb#0s%)rK2Zid+D{XC<*h=H#@Tj=_bCA_cP_I zCl$aK^624(e;>w}FussChdhl6jn+##v1()$4{J&~CsUY4sv`XNZi5!L@r!v!7Ax)O zn1{$`Ap{xxrVo5!X=PlRicIz~I8?i|1KS3&&@d{C_6UT1?HGFq3D$YI&310VVBE%_kaivcbgLw)&@w^Ok{hl;ZHoCRS`;^wNoKj|QHE7mwQqgxyU~7{$#)Sq_5}07yd3Ww{N#TL3*N%M+r)y*s5eO}ZO}n%2MI zwafB_L~Uyg#`v;k!p41KZeP|sbp@td9Ce35Zimq9+Ok%xHovjDaKl_XN;idNtp(C8 z(Z&)_9`Z121cX@DMx>b5z0{J@RO*RseS;}lSwysiTW>*{W$gqAh=rZ9_9;|P(4q-? zS(yMi7CtJcl$8sRFXonI9cXSDRSIQaI*PvZ6O-t&3en|3X#V|Zigy~+yKI8r!t~Jl zASO;_ivv`HHv#e~TOz}|G0QAlD#H^oc9z{L!`BhlmZg#(iD%_?+8e|IT7N1Z5opm& zsRyF_^72vQo(CrqzkCIKyRm!-L#kq3fY{4xZF;({SR*LgPytloy2^*i`>e7O(CW-e zgVB`BNxyA*QFHmk`2I-Z)|Oa{rMTODB-Vc^&PP2AX0Y^`dEJvfo_DV}^xny{z{ zagGsV!MgAfNRJ(M@-c2jEr)ftl_b00P!bu)%SS070u~Y}c(?tMo$sI2>$ptgok<5A_b0!Ne&tvz zVNQ;R5-TBteCsHsY!O)ttv@K`VF8M*#Sp1utA7r-TVn0lyM7tYES>f-4A*g8CUKVO z#3pkxrV32oi=Mcu!Krr;D4~Gje;&7pjuC1Ok=fm#EqaF#`xWUHJ>u_>%|k^*XV*hO ztV1p%C=9%Ri$k_KZ_>E9Qz3P_=vEU9iWTJoFsl}Ft>_?tX&pl+D+)Twd9>o~4Q^CaW_*kqvRK7<2kjUNZW)2d z2|l`02)bie-lhejL*XterX|x055e~#%8KcnIFqMC?<;2bE+8J}c@b?`&ngD8t6QRZ zH$iPH1`FxHcHm(vuJF?H8r>QO2VXHHl}cdNc`&!4Heoul*w(WcaVmz!Q;)gz1;kr1 z%*NW`cu4*_Dz47Hh?T@b>ov+WGGP?x`dMF7aig+d3DXi~3Wn2)(J7bO&=O@aHG7Qk zUX!h5aO@Rhg$hm&wMR#1SzC#NlZ2+uv)aY3T#03@La6srl^Jyo_BHC__ad8dr2{KW z5CGo~i!nwx#8S6|cZ3}pBOS(WERE^B2c?a281zCm;}1dgaKg8kotu0KT4IcI_*f0P z$@3v{W4uH@{u)u%J3>9MX33-c2u4a{l0$R>AB95`jjJ4DxiQFZgIh3W$T?z8XmB5x z1iu8QXy9ydT6inu55f%?I9vPzP%%%2M2x6IEWJheQ&iRhi7NSYO!kbMB&y~g!Dx+z zj`%34Z9m?bin-a5b_+T^iq}BQ#v<95aeN%jPn7Ga2oiRKZt7O`G{vFiFSS`^;{&gMD?GE45#VB+W|8yka9cUZSRz8s! zv{9m+ylfQEJrX_5<6_0D@m%;y_*~;bhgi)$!n5FxjLi=5MB^kcfj%1#N%SGFB6e+& z=nTJ#WVX#AmZZ<|Op?-eM_?l)alY+ND4DUtky_jn(!CIALCeRF%6jY74)=rk)78i@ zIAr6ge1~T}I5kU+{ET72ct*mxYGfu9+Supt(bkT6YNS`}tJQci-yVIhtgN_+9e~bS z@AiTgE@b{vlwh4l?<#Hm}0cTi7H3(>jcSsbUGc5x}-SgX^nU%I*O0 zS!2cl-0I#8z^x+OVpJ>>AYk1H$5pZ1y{{Bj6SVq*;}t7B?}IMK>Ov0Vw&bR5$;Xa> z3@cW$P5_10pJQSN*J!EHWiSiHS!v+Bc(N^i37*5KbV@JJ^Dsadot#1k8UF!uRim>r zfktMN_rq9ZbaBf28vX=4m{H{v`pNmn#M7?M^mhiMN&FrXO*dyKYY5uJSCM{pcZPC- z@}vAqbj7H4W)uJx@*MQi=;6#R11jeCk-U4#DoXfZ(!pL5MfmHagS{mx=f_BQ`#2NH z-Bj|{B>ftv(9bG<20S(TI>ilhJ+CFL>E{$WG?_=2QJ)4mvwESz>HH|!;T6s%qk(4e z0#e>uXX|8E}Hrp! zOl6!F=}k4wbXrlmr>fT?`}+XTb&3h`3N7+3d5s&L;;pjPS|phm5p@bzyjF`mN@7{$ z6f>dqTBHqCyx5s|v@JGqYLTfV`&*sj2J=}h@~!Y3lK6lY`Hgt9%9&1Ur!Q-f=n$%S zwNn_<>!^u}jye6ep^JyKcGr^C;mZWCV9l*k(c5hxhTPkLEA@^u$6l)#_c@cqKvktj z_LEX30B z6e_-z&+6^Y=%3xmV6TBWV%pisWOCa1e(X_{5DmeTbn+)3LORx|*TCOltrx)l1sWFY z)QXj%@78XBPT|Di2rE_whNn&?0_c{FR(5LJa5qLvgqeQ~`eubU{b}AQU z?X#-E)=nK0Mx(S?*O=C_ZP6^;ZFf#*^p$3@KHWJWJ%Du_0_mJ79>3!=a~+{Jm6e_I z7zxBGj%|-={?US@rKmKZ*bhQU_0C~-hA1we+|I2zg=wxs#TWNQiE?h-#pOzEhpFe? zV1W{t3@?F)XtOa}3%mxU(E@GYgE1pt6U$w%<#cwV2VEkNqbZB^_@G$D?(oxJ+bgb+yUysRS_t=*8T2aAhNWV+hq`}C;bo%v{%tOrh z3@Xz*0bH$07LU&C7h``RUvxS2y2`J8i)7ts51NnA)2hPsR0>ar9aj~JupL6vq1eI7o?9s9B@7D4rje}qB=@BbTHtqd3d)2bu9U%W?xaD3$ zg|ASo*HA*QVFKvZuMkPE;R4_}$1(IY&}#&{i83vP()AiC3}3P6My1y%0d(tF?52%) z&QUOfTjcV1nfd7#HJetGwP~9<9ZF73qYV=ga z%7c$@fKM@cNzQS_rgvK+#!u{J6lx_=+RqL5Id70k{bKAviMo|ZW7tIx-5oG%BCOr` z)k8N2rnQ(R)W3UWN|N|pJO$_0Bp8eFhX6qx5dU!h)-N@bj>*nwF}#=NZKZz=mpy)%dZMq|(Q! z!d9yL0(vl=`>8Qu`GjOHFNb(*@MW0Qpj@DM9*KQ=ua@0<0))^vRr>^uFMa_fOCMKL z>S_kE05+N8_jWW~4g3iEa44SNhsPrKX$8^6U};UY_aZW42@x!1HC1d8GKJdUM=m7R z^i0$Nbj8bHzcmMw=!VJu9MY|>d5a}=0@;_~f6ZYJ)rO^*n*iPxfLTkaFGmEht%nJ` zBLKHX!RJP6jtUgC9;SZ1D?q-LkGF1WjtNj~l|mzGjvMr%VZ{1~$W91Qsn{nmDUH;e zX6vX8QMiSgj{_eetXRFNrZWvIQeWWav*xUD5X>46+1Gs9F!o13#eBHtoKR(-)f}#* z<}(4fbqwZE^Laz-Cc<26zG!IORBFR{aa&!iq}GACcFh5mQN?@$JXOs>`JhbUE8%== zUQ|P$U=FMEso+@6OR9L~pYa7$bBmW%(O?s*NuB644+a!eYuqZcI-|JZR23d<5>R+r zEdGA2MYJz!tigA3$`Q%*Pm#?}*=q2e90IAk;nssSZB=%difBWkiKy%gQN#waxa zbPseXzov`Ip6i0xx-lTBk|cQllseK?O+0}loqtN~?54WE0Al=h5^#4(Y4SMgO0`5b z55X>LdPwBs7fD-t$`*1yk9yWe^*XAc-F#0xw6~^4W!Zp1wCULgwCkd8!Yj|M!MA%- zyP{1gSAb+lqjUyQ{#w|N&)VLKA z>HKEQKx0onfww)hMLt9*?b|jdyPaszJ{#nc8PeKNmHjOHFv1uwWT1Z zcbIC{ss=5TnO}uuw^z1IDed!MBb+qQ<>Rt`pxn5^IrsYwW|#c#i%M3JBJ0 z#s{6cf#f05)`uG9Me9p7TVw{E|4u6Sutbb6A(=cPk;%JZf>N_hBKT-(@OFuOJd81= z29LR^TF(EZZa*qfKuL`*ABfj{Yj&wD879l?$X7g}vIqd491=61Y_y#p;l65it84($ z7@tqdw?`tA&mRW#l%%xzE7a1xDw_jJAAgFh>}h-)n=0TPsg!3_wjU_K^8(cH{W3Ag zr;xfotFjM}n8Tll`L<}E_c(;!*lG=PumR`-r(2mCw|u0ExgU8jk3$SVIzNC(LyaL( zK0iqMW;$%j)|>xP8|(70HZi&uH4ZZH;ijWKP+D^+x-m;-KLaTIHmEk9J2RV>gZLh3 zp)prwiM^=9Lm@rm29-4h!ionvY|K|#FCd#gPWcw7Y#ES`AA|`SH^VCcab8dPma5+O zfCAhTT>yBi>i!-u$Vb8I3@ESJn>y?QZ;a)tI|B%F0V-yND)v1U@|WRVj5R8ofV5&h zpJotusO|+oB|Kpy5MG0)d=WkeUe~x&Hnf~~qo%D>*?mZ>GW%6!-AMz zg38R^5FgVkD0mj~!EGmkU3B9*hvRyLs$3+P>m4arT~w-$Q^RhMYpP0DrW^Q}6|E^& zF%sA&hdAlB+ji);alfNsg+2D@4$X+>zz&Va9qHc__2KaVJ00SdqUw2!g$7)q4 zkQP4SFyJSYs@3>4o^*JHdoc%!)_n~s^9|I7r=S#kTQpz83U!NB7z`Fv8gF4Q8!bwA zMjwsCF5gYV(efzAchGS+ zHIBPjMjcrKJti_vxbWbQETM{c^PY=c0Tkf$jp#jXVka(YeLc;Cg=0_E^1qkF1T zk~E(eQrkXov8Rz($mzXl<3ktw1gMy=A-g#3V(OJ-2}cNh>|%L95&mm0pffJBD^NMN zsG*;@*fgL@eiRc(<5L$~2~@=&p)$|8*t0;@Siqznej!`ao1ddzoOiJgkl0VrG}OS~Sgi8)_TNu7idA!v-& zSUqJdA`XtxJY+CH^ux94(pNk?3~&6##R!1|2rjVZ1s~F?}t5b8e<~<6CJURZ_pl42KzS zY3w~}XC|5QVa@FyK<#`WcH(iZS+pEZ3!ekf8O8F^H-f~W`!0YU9vg~nXp(C>uOmmK z>*B`69}QDK7S-iy5Z3U zV+;8MWS@o}e-x`d#k?nR%hbhP4LmP#(yNR6wch+UNWh5K1-JU~RI*e{qFVk7NgzS@ z--q?wQG6GPK1mmo@^O4U#!|y4Q9ZA|IUO)X&wU7_lleuGa;l!c9cViLI>vWtxzQ}> zYmRYLW6u$T+EPvLYV3sIVIBrii#FQ?!!F5M_&{egi#`<-B1F}~&jTJqq zrCo6|iqF@LzjQ_d;E#}`E;Sk{?F}>(`BcE>g=7IZR&NDr!4?MMn8V3MHvS_inC29# z3xT#b$L@Jj3$<7TVa!!^qn(p|O@+tE%G*2LRDi;pQsHIJWZK1}x4k$xcHv3vFsXv~ z3mD(UF?uW_-b6IMkCQKj@Y6Kb{SYVAPv?)&{NdNQq^2WDfQ2Na-{agvMvwr-p6Wm1 z#0{3s=?xX*&$s{;>BEo-^jDl%P3Qa}niL>^GdBsaup5oFY9ky(hT>>M-ALgy#*0v4 zfWJ2b$k~{7h&0C4i1x;g?8bw+f1$s%#w8a^8-l+4M)s-1C4UQVsqk}ifYdlQcEv&& zU!`$Gi!)P!7++03Uyoz0L|$NSEnR@W z8>OzfFle@!kuQKCxz%gnZbNfK7^zsRtC5>@k0sTXN+>`P?q&9_iv z(&OA37~7AZjrpI_audeGKJ`{$9MenjYfg*xeM(CX^oA_;AtJhtqQ647m_Ct$$*~1b zX~CL4=!|XPBZ;RF$>))ok8m~ITU<**8!3^_S1te=B@yGp$n%YMWl=V|3opS!Y|e|Z zZ=TY+TAd+D+gRvg{cBOC73;iL+pA95``cdaKBpRr-SwQdv+@#I7rt&Mwp0|1#*WPw zNjmt%T^(tt-V?w6{Zu7BbVm!V{tp)d;6rybSo-zYvga|&dV|D-ui0%N>I70%e9cY} zXe3U2%#H?F|4yn5AF~tSxhMcWW=Bs{{W&z6<70Lr&uip{pO9qsK8#tse}D}h1-H^4 zeCeOz7IPvin?R;}CO#($zcdIl)Baf^iCJw41StticQ$-{|By(rt(RhhU(m9<^hC4z z=Vp&bp*<#HC{cq>DpVdX(&Gbo2{rWhmKVLyfHLP>3J4TMy)W<2#U?1=ye*lq1viAOQaAh5NA=dy3sWjS~UoqMv|XeJ~LBM zuEJG47_(kAQT5YnTGek)21rsly=v2ESSeIe^{J`!?vu9WZhf$DPwW2+C~G>C5zc2=55YpQy8jmDP~I4G2weiKkR($wjcmZ7Hh7=yIY zi0wDEAxWEyB*&lbzRMBOH;SMBhZ~Zd>nZ7?+d-K=Mx-)eJ$|o8fsB59h(U7SJ!OdP z`v`spHbxIR2&Apq6dk+>gckq?uSDP%1n5sRY#$=jov~2|wd5#;wWt4UODM^CQnuu- zG5^+*14vTn=WzHCTi?Ef|74bX{(97CY7A%lrKxcNl;HX~$DL_oBK0_PfHlq+5! zQN_u}g(@+Bh!Ag_nnmJOYUg2|qFxfqxM~%Whl=D&O+@OjGZ-nYj_phi~40t%J%~Beq249VPsana#JS9n%bUkk}6zq_r_Gyv1U-+ zKKMuVYoR5df%+p{gPWt_8R&t0&+0HTHoV_CtS6=hIJa>Vn&J>mQODuS27~D4S3MV` zYSn&M4#M0`Jy$GHe`VMKOd8dXT^ldw$rE`f%g32Ai!$s-j2Y_tu4)hs5C!)Kk?iC9 z=|q&C1?PZE88(Nk^j+7*N}9D3JLZi8|0V6a0irDiT?;|`2btVq*UO*{3)+V-7kgNc zb_H1%LE0448GJZtEl7(6>Cwxi|Hzk?NJm&?hA)e=foehCo_3h48`bfRt$10q zcp7M@2>OmwK>vcE4}zYal&TwxFO$C@$QOZhA(0PTO7h<4I$wbkMFr{b%cT1R=?0L+ z1nES0Sapvpx)Zdr*u|G=_Xt{gaeN2Sj%Y=`Y^U%wTS3l5(MKzVIaQ30#98tH3b=q!%MP!DXx4MOm8=8I^^3U_LVA_8iLiH8Oog8MlC@4P_j* z6dqmO;#!LcJX!!7I#M2?%UeWy9YI9n8oUuCqOy@+QJ(uzlB)%o{PB1+Nr=?Iv*V}g?oKZNNaxmwkPvDK!C0h`c z+5A=!6?f<3crtTMMA58dJbzW!FT}mAr;0F)g;Dws;lgyt)#V#9Mg?J9OvJ z%_!$)iYk`XjNguMD;b`$9N{i99DW$#s(8_?v#Svvl0;2$z492swREC_;?b!B4W31D z^|~GJ)tc4_A}M+TJ9wNHJ!w*0*PXk zac~tPoI!EImFrjZ*I6V^uX5eC4&hR9+Lde54us3Z30JOtcOZOA>RBrK4m8)fScdm+ zqx$d36l}a^BNFZvVMBL5=57y{X})e^9DuF|9UJI{fE{x`Z?OgX5;)6qz{0!+3W%lHlYe^j*K@SqHr7c%E??-e?^JgN7pj>wsuc-SkaLm2iB+A4k z=z@EX3}eMyaX%%(ssFqaH;Nt{h(#yk@(5bu!GT!!ltz(>?^p$h6S0cBgXjF>QXrg& zH3*3Db?CbXCt}S7GWnB~h6AxKV&-o1?a*Ei4#Xnl<2%524-Uk-9h96Wpn)D7h(##C z<6##b9Ei0SD9G!n6dZ_kqC1AA9PU{Gh!e2@vG&{pn&iQWSlc=<1h&(tEi!u)4BM9L@tn?o6uv`awJvb1n z3aE}RdH@IqVoe1a#phBda3I$0K;w8Fm5Bqfo&>7rw?on%9EkM^&}5s>hlBLIhF)Od ze>$HVYjsFVDm74=8;4+#w7e-Bup!3iz?!=t0_uv<(YwqI!I^iW&qhU7-$F=3@h^*A ze+VmeSA#7k9^?-Mxkqn{UwBwq0vv@> zm9~IxoQsJwu*j+ScT)*x8tBHM z&nC{mBC>Q7>Kmt%QJOddt7#ncWijej0`DMM4ZH(Ias>WFPqn~cZ~|vw?Ze1m;tZ^x zsBc4HZ6?mZBFpi2x?3FxXXFsb`zi(r=5@2#DASwr55F0SWhlXVyeUQbZ%3^b4!)w{gV6xd53z3I z1`eOPh-P|pipkuHJk^8vSvvU2`!;}Xdp)a=Vv;f2iEIn9KwzjC&qY z!NU-~o$Qdl1%&DRe(z8yf}Ud8Wt8XAgRvWr zXn_tNqpc}cSrBoF??b97xss*zD-fLG?)N;t>p3 zGoW)RIH~GQ6y?oDv3hDt`y$o#4m2&bSiD7sZY+Z5OfAtqLP`@~Djv}lU=@&4hE$wX zMbV1zi>WxNin@f8s!HIcQ*lz24odGjI0NSOu~7i#Cn(yROj80380L-eVBQolLC_V~ z33P$SSy!RJ#9k1j-_0Y)leijgB5k=eh{u;9yh4VzL0{7Q_=RzN3WH9sDg1`=7J-N9 z`z6g4u-t%KI+eY!kN^&yA`g?;Jc_zA+DLF8YRf2<;Zd-H%mFg@U8p&8pbU?LoH8d1 z8t)$HtezEOIk?pp6OycK-Wy@`_FgC(%gSX0#nd7zPbP`^NLF5O020Vm#5NwqC+5js zf|-_7g==EZAI0vQ`LPe^UsdelQ7u2T6YLL#1gno*r|81)uuI=ADWg-v#Q`w=wWC&CvN0#h9E*S zd@mthSK|Ak?)5A*kZp-aOJdZl!NQ7g{33KDbcL4`PB#sRU*|0ZxZ%6SgCZ zZO*6e4~?fU?sD^KxaiO@yC{LS+L)`TxH`La0>*c9HDwx^upD&#%%`ciQQ1l&22Jx- zm{DkSN_#8<)SF4v>@oIPFlMs(6E$J1u-)mQYthkJ=GSoUp-I9T=b5>&Q^)X<>XR@h zY_;)Vt4%)Y;%?A*u+;`N0=)z?@?fh?F8~LhM6(=hwc)Ag7QdR(u+^pvi1Bk^lV`NU z79$jYoJMbKwRygrWR9oCRBLsDaaY5>keAsG3Y3unM_)ZVD+7Q~vPeSk> zY_(Yxb8%jIFSKNV{91nKhI6ErJIev&pvGZmC z(D}mapaCB2y!jlG&@U9JaJ3%nyrE{)t1X70=ILtDo7AhP9G(%Vd6rsq_f|k`ydgMO zEt*YS#m1Xi$TLqZ8VX+&IIPJ>?IbfyLgRMBExcmip77w=K5PKBvg){bGD-P)>GK zXMt?~Ehc;(Y{en{<$M(}8C!9dRFU>~AYoxE&hf5j5uJqz7AZz5^=en@Iz(6^E)Q;m?qcVk-`z2!EW+4O?*tmGcp#qu7c=?x>OvCX2>a z9Maz^{wfAR54Pgah*HlJsWI4!Lz;uFICH2^*ovceBgMau>=avZQh{di)}-9nisOd6 zn#+Bd`g*Vxhd48jci94jtvK-^+KO{KRfw%P*~qt)ZzfI0R-7WB75q(1&OF$PLr!=# z|A{(?tvI=8%zEA}N(O_iI1iS>d|D;hHYsTSQqZU9?xTBD$Rt!M+?J76nL z7r<(*D2-T-tvIBHwOUbxSb(iKRC1kGbc*KwM>Tnq87(2CAcGPdFnT&)#l5F@Y^hg|hqt>_673%24AL$RyjGq`RK zw&KJ%ZN(`nCfQ>v&KWqQXSJekRPqW*d_XJufOvzgICo?#*osrsaxEoeD-M~~>%der zw&Dy#7Y}JICs7x%6=#_rVpa5(-^MPT(z336fcl0VH$zcKZ@p*^2^c$Ws4sPT(HF#G z?6{%kEzn!OiNVB!4L9TpBC7;xbF5Mry;REd(^`Y2R zA8J|Y?_p?U*>Q{vC~wNtSxPobYJo6}JGS{lZ9w1KXxuuPNV6?}Ygm8i|MB)F@KIIg z!~eZ=m)uP5gh?jJ+({;x$wCrB5)uf6K!5?lVpwDm2#O1eii$fZu7ik*Yq3&qYt>r! zDpIs6?#0%%3*xS=3vSg~TerGYt^I$Wb0-jN-{0r=zQ6bV^Wn}t&pFR|&U2pgoaZd} zoG0ADk%EiGk9loP5I%}-R9hQ_Z^tOs&JDt~WUig3!bJ~LQt=i3lvlYu{;|KDSLDT; z`1W35)G;nalD_{czS6e9{9uoBRbngZ{NEC{_m2_b;AFn^(WLt-Q?(5af)ao?v~H;m5Bl1}~sK$j|%Wf6m5L$_+~61T>m^t#cNeGL2VHdZ+MND+e2 zm<%kQ`v?lySe0Lo{2Uwy(;+jN#(GB-P^}0Wb1Q=S6(_9H8k@?RNr(oyhN;q%T4X<( z-fSH1+#s3uqAN9y@FXltLMbs)_%Tv3hfH)H6O3)Xvy-{nrk~?h?SSMy%4%+h# zI#<&%X<-D$`b3WA&^qU9L3|B!HFU7D$eKHgjy|-$SQ1Qk4%TOAg903cAY>yyA4<#$nIU{*1y36tTLjS5a zjJiS3(lMGxD^ttGu~GhxHwk zIdO@5wO{q7(UKjD@o1hByAX`9DZj<%Bk<-!E7uT?98Z@rTM8X9Jnp3^XG<(7MvFWK z>AKe8&N@jF4q7|ZkfXVO5@4jVH>UefW|Xa?a>^-7`_|EA3s}r1)oQvmJ6@?kS}T5r zv|-`EY<$8PJ*+^5BkvHdYpZnr(qB$nm9o5=BHfl!q)k<5Te?Us+Uv#3PFqc?oGeMT zHg9WCg;Gq2Zr@%Ske+=xvnr=O9xblK_#J}dhj!1&$VywYXJO>q6UG;iFeJJ7W^}&2 zxuQzKMey3*qQa344BDfIM;)I8cN`)bUgVr1B;02yYIsZmjypn9N)-@wpJ&EDyiBqb zp{D!Mro-cEv!u~I&wu$XnuRmJIsF(BwqJpC@+WtRe#}++aS*1Iix}pcBnqYx+9@>YE#b0@u7+;jARte8#zHKZ90XfkIdv&aj!6@M@Nox_5!Q=A5h^)5iF0K z6n&F<7W1~f=p-X2=Qb6J1%2d{Dxq0?2$PDDQ%e_1p+o%J-!>{1d_#{PRa|zoBrhUk z=ZHdKs<@RRJ3A_WN_^P-Ga15aZ>)Xet{%|2m;Dl{$oaucd*3I zV@NyaRJKC1P(?w zmRgW&)(StMldm+p3Y?dLhsce*E;lB;n40@D3hRm_Zi3e~_a5}Pt5D6@I^5IXvultl ztj~=}N>mlbDf~;Y16PK*kXKlZ4SNes$%eU+hjamNz}AI-%TaLv)9a(`FgNm8h%$No zR`#p8ktefU*Sin}3Ueb*dT!Jkwh4$EdD0>sUPSV7BTt~w-ZbiawAXX5}uZsbW? zpLaTTGt7-VDPoD2FKOJ!lRr*$kar9E5#~mo)h$c7+iAM+Ye9L-y}MAy@H=r`DerOK z%QR(}8+k%zrFWjc_q%K(E%T3i7bl!CRIYB!(auY-=01qv#~fQ|h|Ju%3~^(QQ(A1g zzozPAj?WFLGS?V$LYQhW*SHHqIOfEXy~!VSmr0(LQM*RgC9>H+W>vxk@Z6uE`D0E} z)ROLv0-P+sU@eVeKLw@8X3&#k_H!h&dn?vq%#xgG=CA6!hnK}WQM)mZC5lN;yvBGn z7FV1iC;K0_`{%vKxt7t8jdm|{E)fdCe)pj{lXvA!0Kr&ix~Cu=-13YPEUhRcax^7N z;xB@QFHV_)CYI9Ay79%SfEO+SSe64e`~m=9oGQhmFLmRK6QEK{a2*2DxukcHOgFwb z1us(T_xzd7h+-r>KCIY|MDJvboY3bisf7_d!{d`g%}g}+NJN28k~gWG>8?fRC+w#H zM~gg|~tG*7VbD_)O$e@ZYvEAle6;s$CO z&Qv!LS9}{a2u}(=Q(Y|K$(6!U@vnbQxF;A`juJ)esSpJ!{MP0@Cg- z_X#Jw%5WrNOl6r4=Tjy2D!V8zw_m3YZtI!`w1LBkV1&X-YlL=)Q zpVT7vEgsi!v@kxY#cuJ}Dh0wPwLlMer}#A=7(+(9fmAY+OTU1_;@9a)nPANldHOB} z(agS1cRd2X5_U5C#U!k`FZySGfD_Xd{*@o#mgpq#qM7HMs|0T~Es=S(=u^-bS7?dM zYYITqN25F4nKztg1W!lynKw%$%)IhafVV1E0W`W-)6AK7ibZ;(HAgsolX&&zgiYx> zgp~P8eb|oY!FXL}1kO!IHXwMJdu4ELDoU++cQHC;aBeD)>B*uY!yQ0@98dR8`m0ej zv;%$Z$l%2EOtN>16VnV%O#dbA# z$o~qItVQPzhT;r9Og{uKc>p5Ol0(R=CwnL|q&2+^pc$FWe!3%nX3+bb?vTNUsQ?@_ z#DZm(>dHd6-ftNRGWal+o#d$Z4LLLTFr70LMDKEjx(q%{7Xl@{`|oBNm%)eW5@K1><|AEq-AStjG5FqDrE(}O8L(;{oL3_eUH=k!pEY^`VTVJe+u zW@s4KiL?wpOjX`Bp_YkKDn3jF8?YTil);Cofa^jn&7!0DFcrO8A8P3lhTqWD4#K8YSK zw_8?X8Z!7WmDCk>OBdEPlPl;ryQNq7z=x?IK}oCamZb274^t^=gWYlg z1~`KcQ{j4}Jxr+rK1>5C+AR}BYw=+!t-Z}|k-nedijmle9d^q|sRTYu#VERYfu`WY zRA@!>lxie1_%IFhDzBwR#EB2nLZ;)HJX&bO$M`V)Gu_0=;KTGb+Q5@8mBELp0GhX6 z^cx?hQd9Z5BONiGMH*gaV#wJc87}mTKEa3SDSq20#$^3Df8{4Q9la5ya0@z}3nY-dK61jFj-A+bKmiMRHizu z_>VAE@It-NPW(u;4yU7H`V(hMhsWvYJiqo+qvVL&q`G(+eMt}(N+aQAR8E8>${0Q} zcp1GJI}urfi7J`sI0v*)Y@A_pV!q;tX&)A6VmPS0I~Qsb3j$u#x-(E7mrqsK ze2N=|-=H%SqZL9XaU=a~65au13n20d3F*l(=K(1|y6@x?wXO`7xsyxn-$;A8XJBV0 zC!&JoA?iDbEG}oDnVihcBg92r{4=`j6FoQwxUdzxYarc&a{#tVvw%}j%pSZ0h_!dT z%3Fc(4j@p}`#$7*@D3ml7o3n=58eSpZfVb;MSJiLAS?bJ&lL#o0IF+v4{ZX%Ie;t! z4)T6L!}j1DATdm4*DpzVI0twN6Oca6yPgK@!8^d4u)f-B!u0gu9bhV`YczYU|Hr=@ zHN#)W#!QJDKHRte0Mdbx?-^ykPE7I}2x`g8VO&pkQdCH@4S(2YMss!t6788D*$fun zUNI_8PmePO($#;FDED6UrKh*}J^jwy6>Y_J+CJ(lQXLB5mM65oQ`!fEcCn?G#^e- z0Nzr$jrc`3g zkw`eL_uw4hQ6aaN6vf0q0IpN}_7^w@m_bMF!8yPuM25{D@tcrrJvavtApR7JmhQnh zz`Axa9VtzNbAYP=xJ!nf_jCmi^A1r$oC8Rj} zB+dcWi!S3FKoUJ~FaOama1QV(y42W%bAS_v6Wb~%I0q0EF1<-ba1L-0Nv3zNh!f`k zR{=R*RvH-R0D|dyu2co*00Kq5lZ0QK0|->&bzm%da1J2+dY&&F;vC=}sPLh)P?QGe z051Wi;pxsVaSrf1CWfo=j_3S-1X-TPzHlcq*E%=Ray>W)kcp||-6dTR=Kumly~Wb{ zI0q2O^G>`R2C>72D1Uks8qe?wE2N0Ue zy(2`hI0q0aY)gz}lx*wae)aMW7(aO*VF%{`GLmWD2hzYe2N1~gG@*)f0D&B@UaF6C z05#@$Ph(Dca1J0))cYk}y$9z20+o2L`q{78?fJ1(20sAu6_*REl-q+F0D*7=(1eJ7 z*n=B@%Y=<{#YW-=K=Nzejl%ymdR&rNEK0*%M^H_n@!fwdbkjY!2dEhda#Y$M_W)8o z?pBFq!94(kveclQSxyiB0fa8 zK%SS8n&TfpAp8SdgN`+0bDZBw`GZ97aS!k}0L?pQD-iAh&_5~vda)Py2XID7`4dF{ z@DCsmmxQH6`~!%pM!lWV8SoF#LRyLUQ)vhM1Iz>Ryi%cZukIWVl=Sk2@B4$aw6|5v zGwuN{C9Tf8MRerBpaehujq%6n)szy|d+-$Cu&VWBPTPa00I3Rp?@bEDQ-BOcrZ?&W zAUp*K)aYFx%7>=_$u`ql;K^R8?PjpGmN6^0E)A0EK zI0{IzQPt-CgApr?qkvIB9o|*++%Tt*B;RQ7k67R^jsk?yZto2gCXAzi1z={p85@QG z;wa!Gz#i`gSqlG1cQyb`_tpr6qkwHdGrb=F$!~EKFv0)gTO0*^AB8#H4C5$3q~RUF zm+ml*0%R$!c}I&7aTIV>xrl8U`V+=cfDE9n7h;wX#!-Mw50c)&s9_jK0eXeh{&Sj| zKRcr)YIWYpbjL7`0%Qg--AiHj!Z->L2uA@&OIzS5K)^ZP@tD>yjso6>(|wdegmDxg zX?@;b=*M9k1xWErJo%gw#!-MwrdD|Jc_xgb0GY=e=PkJ_-^5SAdH!VGd|1!(t&myW z#81+i%X<_^W{0*qYJ5>_#4-lCFP4BlNA^Zb~hYkM7hja)0155=KfuL6)4lN`Fx&$OGc#G6j5W+cKD@{%kh7KSeek(9+-DRzNfD@glM634qFMQ1SJJsk68AOoWf#u#j+I8n zUw}YKZ>*GyzW^CM(po%8hfjy`7a)6R-m%hA@fYwWiJB+htHbyU5M437^F@#F7a)-1 z?Ub&GzW{+;Z;`Y%{sQs@jB+*nK+8-lJXo3p=K!}RP~+j!t~dt>&b1Y-W~4QyP-fWd z<+tt=>1#L#kUg+?HDY(dI0v{`m@1d`wN zT?@YEEszT196*F=daI;PI0uk39hsPP{lGcEg2^(aIZreK=K#_a%e-EJa1J2Qaj@&HxTm;E~ivM!8wfD#iUoq6W(vWv(Notp0)Mx?uxQGIGy9fc0gN>45Djv~QSpZTY{s!{kh zJmxYenUW<^2ixXUd_GEk`35_+sQgmGSgBh?T&5({x?#KnJVYq4C3pvr=EY}(|1Zb% zvJZU^Ok4r9gE8(ZVYA#!91oCLdf16txXQGA0Ij1Aij8I%_W;ra-V$jm+yex?S58oe zwcw;u)U!n4aStHvU*erEBEvm^%I4LJ3Bf&pw0N?)vI zE|q>##y%Ve2w~GZRtzYP1G3`z#qrLP2E=nfBajHzQAUGrdX$j#e#0C%jNV91PUvZ4v?DN8l`=4zCJk z4h-W+Knfh~t&@y668JGtxA%#tF^&WT;sB`>fgb^Zdb~$euMeQ<-hEPE{0Q7f+Dz{( z!NiZib3k*v=lvqr96afEdb05@f(x4;@LRMF#WcQ9aNbmj{YGLxqy9!j!NvR*^@(jT zDGQBFXi?|gK$njGC8yG(Q zHUD$hoD#hg6_18-BoLoQzJIRwXGF}q_1CwC3eB%|{g|!a4l*|z`pN!nF>}j|p&%z# zi7Me%AU#`{6(2iApEIH+E|V6+xxi=@U(au1tQ3rM0og1{ynZJD&IRO>PU0Q^<`T0y zd#bb@&IMKp_T8e*I2TX|C&-A8a{-ysBE+9ul6U(F@<6J=HiHJ?Y zxqzTW3}hdjRuOIAn_e^@b3d&zpIHFFVw!qdm2^`UL+Sv~w3JG6+*h!<)6xnE*Ppa> z1@3hf;JWfLcUp}C3N?Ep)0%0u#`CakNCA;`NWhymIC`>_b`xzets!5+n%jk)nAWI_ zLewq19bkw8N;GG*|9Gi6NWa&Au@vKzhk2RSTzMV2oN@g8l85VVck(K}l-8ftl9Z_8 zlTo;7t?JYFq}!mU&($rlBaJj5n!pX*QFFKg{Wj!Cic6zcq&2OSXCfZ&G=09V80a}l z<1Z?y8cioH+r!V4gCMeCzNTrD{)Nu#;{(!vr_*;BLOPGM$%@K&UEK}2gj|R51m%s_ z>!iH3)p!};nOeJ#rb*_I`ZYQp`YPbxF5j`(RF zKYK}ZZyTZaA^0ZGjBbG5>HO5yaMOop`ilTNp)yEY9)U={9{E;^c$?_mi6z<^Net_e zin;xXGbQJkDKwK-`9n$lAkz8oiACBL|I)ZwnkSfL+7te5aWhr73Bc0clU&b6ez0dQ zNn?~K^goK5C3#;08Cs){-ytv)}HSWeTvhK7TgAUVSR%xI)r@A88ROs0t_^ zCC%2>OWO9xUc1vihX3nHGqf#|wj;8hG(Vj%OZMGC#I$copB6^c)5=8Nnj*GY74)8D zcsFwWZek(~NwYo0R zhc!M#o;lYuvC$73iviN+3{ptbRDJf|#MDJUAdrrbpCd*}KcG@L))w}XDo_4(mUa$# zP3@3ce)aJp)2a8d1^p;Lx11oipY2iivp23gmb^mNx$2cy%gYEZgma0!oqSyVu2rwR zVdOQ>03oVZL~o}&s}ex3%pWd=TVIo`SLJ62o7R<*l2R#-r3sK0U=f40SV+BECAn6) zWU5g>p_VsE5NnMEkZ_~`Ge(4`4~~8TU|lDv4fzt*tT_TSs?ws?2LHSYvr^yBzoEiR z4>?kjnk#D&K>mZ^P9T7Dqz+#0ZIafKlqfgl|Ekck_cp{@j+bLHUT%%`NILVn@ck$db;y z$9YuHekf@7mPnXCsg1zQdn*Kx?|xVDF1#OEI;Q!W^Y>louX_^a%Qn7wF!3K+|I@9|l^eS1f2+%uR#X7McrZkE*CO zF>4!8UjVI({Y0R}Y+ljN*T+JL++Lzry*gCWE{cs4Xn#Gv#pBCt?0&S(K0qHNvfLPZ z*)K?$<=Gb6#XdBM+!ni&Cb5?X&?B*_G>UyhK26d1K4cS%ho;zyg{#+h{z7Yv8QM;o^z#or|V^hO0k#g z1p=1CsY{U7Qj*}sdR^s*sR@vHRsSAZMzvVElJtM#(00XOPp8%4%H z)st4Urd^|#gt**y?QheIWOHwmUUmTbV{Z=F+M<_j z7pCvlOMlYHu^YWCyGV3mYmj`8UiPIl{4)WY59np@3Y#1*6peXAFFRf8^rD_`SX_Xky5*5GJe+u&17q=5N%)L{h9)k=X z4^&a*G#?%hq{qh_;1G`oDs0j-@OU8Mia$_P-q++zek@eDOfS=b$g@IgQ`sSr{zbzN&(f$50Sn zUjtwj9T%ss0T4xv@c9}5&(oF#+j77h+N$8;9Pn3aQ^0+CVLQn+eXW4|^a^+h<;fOs zpI!u(tiePSaGzcpIQa*vTCg*R*D2(_vt(HxDk3CWoxPZX+J%c zL5V`C2dS))`WM15wd6d8iURJ_Ti_;F({u&gr+ z1>lzCjNelFfSWE7+s@hhwP>3YIo{bUcw3NhRQfoG4HzQdN zfszv60auaLNueA+3o}``sZ7jj{2+1{-save;g6AR;m=E&I9L)k+xSh#e*%TVpEpQU z@@ty8kel=}?o|8|$+5{te6>bZC+y=1xk#F@IVci7p9D8sh(#Mz>}(_@7#;@26cEyC z%s~aATS2PrBgY^&?*>q6n-R^RQfn#kXvCm!{{4jNgcestw~8 zAg5@N3P;YS2GOFDXf+Dwc4Q$}R9Y-VG&f1(7L_Y=Wx5gCt0)+r9ap$4s!+FQT+Mg} zffS{U*U3~9Et(jb45`}Z>FPz3;u6XI^OvQNOjNYGu7w|%dkN->W*UNQY$Uz7s`yfN zC$z}B08XsL@kwzHrxjzR!9eQHraQ*U6kuxc2!bxj_l^ZE{wG*1tqE#(5=~uNn+qfJ z(z!vng*Ggmr@|FYzr-44P_?q+s%Ie(=|p$EctfY-vZPR*Fy2@gbi$vaqwyii!bLSh zr?T;(HE)xt!1R{p4-@>){ps~)<>3oq)>}H-BPu=*xq1i3CJ?SV5{?4(_s-Dm9}t!L z6Plr=K9K2A>UydbO09*ZQ0hLqO(=C7Y#XVcs;=M&i5qzGj}Wo(=V1Nc)tlu*Z=s87 zDH9W?8rjp?1p&E((ViMNe`}_kNm?w7oXEd0N*%k(dpcaYa>@Pt`-HK$i)YGaDe}2C( z(xobKDn`agO+p%G>O3l8rG7?Z+o=TX0hKu&58+V2bR zMP$}emnx|Zh0{>#T4IdUTX1Klj-gJL7CDVqH1V8M%+l9=8*UQgUoF~>1T=RoEF@l2 z04KPrIH4+#89Rr}2@awN z#3?oUwZenP9-2chQpuhH$ZNlCqgaI{n!ntH00a7ZiO2S772iT!k^ISSE7 z=qd@$s0yIPvfroa6C6?%^XtZT&~gb5sdkeXjeVc22@a|Ly@;K<*qNwJfDVDclrySAr7rd_R4Bn2Re>5~4T8xTRe_pg_tUTm&Zr917CRyHT3fOU4+F=Em`ziKhgA1w96QJ^yHH4TNLBJK zv&*)N4sb|SRCKvrRxDNLkgBAvu*F6XO}G&o68|pLFSNZSsykm!68*CX|-LJ z7QQ&7DkW{O%L?hK2@a_W*Bk9pr3xHU4a8`dO%kp-q${hmb#NPT^llq znmP$itR4+cOgdPC6RQGfv74mYoLH4=%gR-{<&gPw9WTL&)eVy25&z~^jKYuo$6L+G z**a=&mKQqzBFViFlU*KDO9aN@`%#y=is8=e7)fGuDmvUt;X`wOFThAuVbh&SKdBg{ z#$+z{z-Yy2CAg%Tv{Z~&2N%*>d^@Zq!@>AJ61$l!AS{!N+mKLQWus&^(L<1> zrbt(&6lqfxTA40VlOuH{v9c!hHe4rFp_L7)(6kmgkmfL}21R!WxzB0nszK#4Gr$au zqo-938Y>0Ge?-Nr2G0!m`54Jo^~F`frSMj@I0%1?j#cd>wvvRr{&1^9a-$OLDdl z_kkYaELEX2jtSIZ$RK@T zFsbdJMbj4r;hV5t)w2?}Q(pW?7^t4z{G^0G1i$+J0OuXzYw}e^l6P;*0^klu6d>L} z?Q2H{;Zd}0?WiD}qLu3A1-XwC>CX?sRZI}-Rw$f83w%D`)H!qcnBg7?PlE?5t>r>7 z*$3AbOM>aPVjTuIC_t|2RumH(Ii5&!@KEOj$!qx+wwpur1N^(%&5rCD7}C1>qn)W` zB;A3E)gP<2D)?x@h*5u>n#r2(#Zag}Ug@#p?v1SKPY4f1`CRu7L{fiZ$%BxNx(7&} zmC=`|Fnks_!=W z7Y}CvG8GZjKbAO`^yCiOu7R6n(v2(n9+2h3EfSwc12=Lx?B@b}K}T!ka+m@}!e=9w z!xS(TZENImm;(023N~^%Oaa}9AUO)r()yXaHn$;OHX$k zwmCdH8YLVoO2S=gO=`MDa1tI9NDOaG@Y@)k}ewjlXi>X_g(`CR``BRC`!730fk@<#;mN)#Z$X>wU@CvOz%$Z7JK z4BA|aV)tl}%TH9cG_-glW6O|)5?hVaG(I$jq}pa!ZJMC8srCrUY0Bh@hHH_fC`Ytu zoU#6z3D3rPd8)InB+hqF!6g4FZX?cPkUyjx^Υc$XA7uk-=JkwU^HEsk>tCr{k3 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 P+atXAdfa2D5N7@lhtj46 delta 155782 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ਠ-ppW$g=jsG)=>zKZ#n@~a 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�f?CUfkG%cM07+7(Cp3hBM0w3b27 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 z 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�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%0AwN~Nrrrc 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 Zcg4C`Yat_thH0W*y4mT}MqB{t{sNDmbgKXW literal 688092 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

pWmMhEQC)T;}5 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

_o39nfWSdo1KL*@WON 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�Ek}}NM#$D#g~L`d zFW|HI@T)#;230kaykYH8MX6eTBoq+Pu|H{I=7cM zE-R62k(>w3QaaqUnr_7_da^+ZIGD(nRFX>FLB2hdl~IE%d1JN`6_M-@Ey`-uZuZN8 zTVko&M>@6tt+T>3Zf{FMQ@NadqF6J-wh$XMRQHB-HF5h~^6DG2FtaSls)gbWvOB8f z;X4x~K&D$bS)SSNd{Z`QKta5UaYP-$-dvRqIOkc<1kCnIT#jibQze^7*)C#fQ!Q7s zw5bjdXX&pBq9f;-6w=vXhE^F>vJDeMHOv1V^i|ayzdk!6G%nF!rCUVmZ>9(ZRE?v> zF~?uxIeuy`kVT5;;mv-E``>#~@cc+KLBgbk%q=Nz2z)j+3veU}PO%HfW(quM+Z;?3 z8-axX&8dMF{%NcHcKZ(Sl3(=gJyu4n@h1?DsF_Dx;slI)FjfW znjAvY_V{^2Q$oVA-F%Q?^_mJ2JTeP-YmwElnel>48MFY zT=<5AmIZBDD%P^#LN^;`Z|Bcu@SdA(or`l`>^Wk}mJsl(=Ab=ri;BNbS{36e4p451eTtr7C%pDFYxKz*)Y$_lLFKAL6G*- zqAqnKhzeu*-VL5m=4+Vs=(uHa{GM`uWvZic-^6ssb<67^TCi{=_Gnz#7O^xPWZY6h?D}5dNZ#mF$ zXGi7KM0x7v7j#*t%E`bx-8V7Z0c`C<<+1YQaDOIh?ogM(xANsSr&Q|j|6DO0$I6pq z<&lno$>HPW$*GR1sY>5u$I!&^IJg@e9viNl+BurbIeaTcsb3w2_xRBhz3L|)Fg4P5 zq;!<-%~g(0miq>J$vjSgNzzUZPWhzMef|CAsVN^>oto?eiE>I`d@D@X21+ zr9L`#0!I1d#Q0=|VB8pdPmicz+(%FJ_f6oWUBZnIoS3AV0NmJkWq5EHJh|i^x!!$b zdI$&gR)$Aa8Z*n_1lX>~LwOT@L%}sVnm|RB6GN50BO~SZog?Mr<&pLnNOFvpN5>~m z`P@_eN6P~^$q-{JDTJiL-auK9&y|3%$rDPFWKq47RxU$@YOk=>KRq$vd(QhRljQ*5yS){1+AI%nlo7iF67!Sf@DUv2a;REI4!~Fq=jLNTSN5_jt8 zsBlTyAXUBEn|3(}P6%e|XsBaqcxbF|q&0pR8mkc(Ob-K0WEu(MN)AGD9UtCtZloS7B`Q4v+W3O2?0IRG#~DAk4&mRsB^M!VEANvI&>I^qmI*piqT=% ze5KqAd4)0u$|HTJ$^*UqeIudz;c-9G=tb96Wb-ZUb(n}Ny?1)N(ih-D+KBrJ+VADx za18NKxwm{WjLFI*eK#?#nIw>bun4p6O`0oK**@sT323Q|92p#$o>HUn1@`SdN2kda zkFVpSDeF7tCqiHU*c3pIj>ZhiHP*+(PRdPZFS|A&R2S>BVFPqNiz8$ z+4LstjEtk@F?F@eIshVlbvet)IkIt@@5Lx@ZF7swN6p2^{PKfEYnQd+GbedNbI+j7 zo0CObyVWk}wl*>iCA)wOby1gXF>iZ-tSgt>&4er{64+U&A^6{(Cb)H}sUzLes8p=& zDNx+ME#z%82^W=YLv&+4s=bJlRQX~ayHXB)6>9Wjy7lvnew;TAOS}uB!$cRN62G-d zUaRD_ig!PCJ-Dd(Hvg&9r^T+*CsLw1;B=lYYxOl>y3YkKQ`Mj-Zv8I^^fbs5^ubGZG;eu%g zu(X-KJ$=S@6z%2hRMTcVC~8Z|E-%>?QRk4o7H_F&50>=XT_XA0O}E`-K7Ly6KggH7 zn?7kU|D~oxfm_X=oGvQ2dFlb6G+(Ky?Y0{zW^?qU-6}wDA%DhhrBJ;(XXIz3! z=Attt`*1!|wzm}HOiS&i7Bcd70$ID2_poxk00h3+yIirGqkF>l*^?oTtVo>3t9T2j_v^ zw`J2?)LihrTP^OnuuPjHhkzbs`P=;AP?1^v^qf^~dW%B@D2O^EhkbSILXUk)IQq0l z4yZUv{qI((&pa3~=I~=TJfxU*H~ zkz&*%NJRUe%NL_kWWIU2JL(CvyUE;^CRm*$4X#1EDGf7clszu%6Era{V|C&(pU)*} z+RR_%iU(l@U&s}w8FOO38{z-vTa0wuQYhFB=6}L(Yg!Z;nTHqT?cz-YuOt}mX&0Nf zFX#^bV(>Fox5JV*h#{A7><*myRzR@PO=9St+(czPg}`af1-MsAyY$92ULa_Mfp$&9)Tv+olb+;Wf6Y%N~yogN4P#XqmWvcZse6 z<)1h7Sk!9QUav{9%eGMsrK;9EwKnRjm=21ivdGkzVpRRa14Vf8GV@)byLIU@TemVQ z7N4hFlD!d#)pyLZg7hb=AhnCS!M)-4MuEMg$h7cNQAc}6yWM%aZNl4W?}ap4o9)C| zDqalWn;q0H-*K*u_W8QI?ecrby#8F7!Hc=~FvV-R^M`7ooIg=zEY2lI9*pfhc% zDdyAsLL5HwGjA~s7|M&m{uGNp9Q;eTW7*nTuZ0(lAcEKRMbrmcpddUeoudb{U zll*>>F?69%O!9wsKR~(w{mdYF$w;;5BzaL%k0`<}fgC<~K5`IBS|jrW-ON1Njk^Q7 zK$Q9-T%g|3>{WKDc}bGt(&csuAx>aC1R*aoFFHbAZr3Te+wSYOdjy=RlW1h_JrkKV z-GM?nZJik|+GV7)(ajnT)J)R%ogRex^A&Z>KXd+E#I~TvE)^>@#j|dB;rfsFJD!#{ zy?P9}(cTn&@LWTEj+Ta@ZT^!JVi|mHz%$n781ZU4@P7OL=aIZxp^USF@PC4VcxY#a zzin};uTs-Aq8m%#Yms?~>8#}txH%V35-;N6u0oCZ2MXUVR}VE`X|IZ4J{v%cf}*Q@ zl&j}XyU2a_xNk%B7AANn5dVLtdt5IjoW2`4Ad&ab-+@NX!)ydigZVpVnl+JGRgCu9 z%eUJVMZ2b}-3~I>u4%R>&oWWLmCR5@i5iPY#Uxs@-mchT*L2!(5K6Lfm#k#F06%a3 zF-Cbq)M+<52|q9DHu|ypSptXF2T5m>`{sStD>>Xk3G2P@b{Yh&{SCsxYiM(m87|q^ zm}Ny<><&yzb9OhUx6J^g*V5d)9GWt4%~}aQd2_tSt`4q}3uEvfwyO_=^80iT#4DZf z!kqQ6ALR!Cz1FXRw0iN&A2*TZ?bR;8r)xgP+{0;ek>1j$H3_il>$VHb|H&}2<`40< zMCK!>30>~!av@WNhA|_$g}EMA9ucZrl>Fh-rTm#Z4;#29=DVNC=ULZP^eyJ?-B3Gg z7?Dp@G~5IwYQ1;}N^G`Ny}D>O0bILnXtOQr zh-q`}H6?o$%{J7tT3^<0`ZyN!sngxd9RzO_{TptdQ+GJ<0JR$2$EX>2#fb9ecY&7% z|KjPA&tJ!TYBgQ7Q*DKlCz*k|Au_*7-j@kwo%sxc=+hJ+D%-)8!sfjU7|6_thF{Kz zhCja`@8rP0IH@7E*!*IU3%{KBOFgk*xH!rWiV8K-^U;1Q2}uHojkX}4I0B8MJjYVh zW{{LOW#}L>8;kV5mYoPkL(Md@$>uNd#h708VW*cZkz{-UrtXXjbs{E9O{{TFbVbX>sjrW_=qq*p~e^zsh!RvMrDv!sL3OWk#SE7Ixchc#xFvAmr>y zJoVl7I`3I^P1p7=yTqJSf2^m8dHcGeJpdnOB5`fsU|aQ-RG4qJuXABqpstc@vk%Z3 zw{1g@Z7$kXU3PW5U3CZZ*?a7AimNjxG}zZ@SerxL&CFy?cJUo{NxNO#W|v$+htwDC zl67`*NBFIy`7nwt!lAi(R*nO+Qg*wPZ;VqOr0ilSyupZVN4xw0j3AZWT-R+j2!m4K zG&;)o+=*gQv{7e1S?g)#(^2=xypqV3eB*p zrf63oGQD{sTFgd8P!76=J8_nf&^MQ&E)v{XGQXfoDY%9?q6IApuB9#)Z=%$+DA9Lo zN(L;2+JP^R>VYds0q{c89|tsjEQ_XpRhFY-xrzc9vxq^wtciZpLjS+}8AES@*=2_ty zDSoy2?3}n>It$T4a}|#NLxSTDa7;U9;rRbBCG)8q%)-I?hx{_wqi!(OAe3wsHBV>> zeK4>Ev$%wU0iBkUgdN_z5Uj;yU9?XkjMQ~COzf`FN+RAw`z5Sdmf@1t&_UMqIE;Y1 zWAO^qNkBzSaBksrbp(^kyUyamSo0SMd>R3+$QP*Xs&u`1Q0skkm`~e=t-w2blA@ee z+ps2Lvf8R*T-^=P_KX*pb^)~GQA=#?dcmESDzq+IrPAMA0M=iEZP=C-&HAGZGYPX& zTi5ig!?CzzCUG-knbn9frd-<4v5m%exb);$BOI_+MDm%+ckRS2@s~yDaEpE$m)W^qX115A@TKzfJrr!rS%c?@7%P-f4@QJMB$7?B&dR>+RO{cJp<_bV?C&vCHsCjiam=$)QPk z@q26$AuJHYC(%~G6fKKC^Ep<0i>2$;^5o4_`HU#9rKj0;Ma%6a%w_v**EXmKcH98L zFOF8&jpmg1bW!vr68LsHh%br^G^9mPy8t}|EUk9qcAN|p1g@H^nZ-M!eA^X_n-068 zh0v+ute~%)o*njM`PpG}WWBJUW5rguW6zuHPGH%~YK3v_p*eOzwHz%ryJ>9D7t?IF zKo%F@PC18g=J`(3%;t92O4`fJ*-2B$b^)!wuqIqss(a?Gv11*IrqBtOMA`;uitb!y zR{?BO^h=H(uSX?Na{g({Qf<@fjoaFZ>{#0Dh!Sbw_o2!)wVln0cK zIYO(gm(MLeYtPt4@<78btj#U2XDVkt$c!r7n0LHMl{-N4r@*J6H88kLn^p`ww0C4;cVJjxiftwEvEWA`2Q3YE^;dXs;8V31y6 zviS(8cgzU&CF?$~jJOg7U9z8N>spzB@?N%#%+Di+#2O4Z&W8a`8(#qUjniiZVUILI z-+Caw60RWa&>fU-K0-2rfBXJoRq(HyJb+Wo|C@tw%q6vFZM)wtyb|@()%HpWSeyw=s!S_H+S3qqP?eR*ZZ*6H`$-apJfa#M%^i@F<(6`MFo8CTNJxKWZlPiy~wkR zVYJDfW_x0K8M(BWmzp$s#I9&v2CB3Jy5fXghN5DR3OUv-bs&v+$Af~FkiVnYtqj#I z>+p5BcEU7$qO)K%y=lT8Q&Pe|zJP=sO87??kg!t;zi4>qg-!-A$NlziCZf(qoM7}+=ZGU_ z=2)p@=H$%ui10qYew#zJ$Zl1IGTFP_lRa*G1+hz`;mdC=UGTo|<>qhL_tlQCHj>%@ z-O}^DZu&x9pRGYWgW=XS( zgdA=CvE6(&+3-xZn?GnOM7a&;g-Ub$u=WfY12_?f$xqiSJ1+J{<=Tv*SFCB~4s>Q~ zRXoG(7`B-^efCY!kEdaSNs%v{4*RWZ=h}?UY%@C$*?&EEkigo+X7mlnNng*=eh);m z`Ac}Shuv?=hfQPJ5bvJ*{ZCqX%b6Lij8n|{btwLDevl>IEy>RDAvRm*+9dWRXk&Tn zTB`PY$Qf_aNW0Z#&+g^UwRQadWLr4iex4IXs@j(=Dq{&sWfgXKnGv zIc9ycp!nT*l#`M9wA*lY#unNomc7W5s-E~?iLRf5uB+)S-d~T=5&PZOQPGRJ!3*q+ z`7Ap+yTMyR^qao#^!OKyGHAHUv}8`Bk%Z7M$h3sfjC2Kg{7{f8uzcbIi5;r`m)sec zP=>0RGv4#S4|vk}A(*#*2tc4C|5u(m?+P>LIgFA#ugOd>ZhlOspJW(b;Cyqp_;SKu z%y<74hx_YeYqwa|i1}vq_vOvk7tU$to9=$t=S|0j17%Dy|8G3EWccm}Q_=7%th+p; z{ow4G8gniTToU1qMP!)eW+z32-0TF%k0w%z*BNy_-;8xy(<&YU9Qb zz2V)7+=Ngwrx+SUy18xd&H}&e{-B$?t0qhHrno5=DhbUw39EDfN9-&jg1#6~$vm(v z*!B$){KVdG%09MctFLI}2!$d;_X8viPetdtc~` z8Gv;1P<<>8otp)K+{-I59ygiKfO95X;h&u!l!VY%R`AJ0f0&7W4ljE+sw{2ud*>;yZe z1$~<@pN`FW8AE9{Vy-M<^hgCE0?D8q+YdM=f+qeL}sSo7&L|abl?L|xZvrR2*37IjehcA+5bFybA&ik;0la{AR23U(Pw=ElO zQ>SgYq58w_I=in;vR!&pRK&J7GQ-jj-|7s6{s=9sw$iBoDr`k?Es#olUr>S{WP@%^ zw>z*h=(dm1;5l9Y0&VIo(D9Q6(jodE7nXPzxzj4%QmbB3^-5l^`1-t+Cb6C!E$Ia7 z*Eo+h9Es`o9~aRY(gC1Lf*##{F+MRrfmGovoj}u8LJh!o>bt`$9?^Qsht;GDjM&pJ zI$J1pddl4d+HsG2Lfz|KPN0))=qv}u2rQC(0 z-FugqyNl+E?+x-z4s7y&=k(wZigc|!>&#D`lA?c|`P)buRQXH6-y?NqWd8INW+CT1 zO2dAH`EQJr(mK1L!-VjL0pK73 zvp1-&b)nAesg}J&e25U*X_5ihQhfzQhXas3Vg@0`0hkxL&G;v-WVF5{wY<(9v7b_R z6>aWpZp{}KVxjUc_sJ~8nlo0WH(>-?bG#IFxNS@1T?3$7gw_>#ReG(vQrkF+(< z^$)IH`W1R$fNjlJxy`P{huwIfu?n-j3-l^%1LKM%`5fo=I4g1%yIg@?Z9n2@^Fgi04 z1}?UVgja%wCiBq+e%q71l>fn7sI>4>+I%xWUR~tV4G+RD0qklawN_tm8GIha)m}ujv zjWA--S6x;cc{3H&KHrs9#yrKqT@Z!$ZYTNP- zD$a|JJWH09#GKq%z&hhGA1g}^?{X~JOsI&3I*2S&)qe#L-PGZL_?|!%wmqEDGm-fk z#*JEZt-|JczdJI4tapfjbw#`9X}b=q0Mge8)$Y3_+QVk|Tm;^1Z(8RPX|A|1UbNF4 zQHBHj-?cmA}Z1h+J?#Xv-Zj;k7ar^WX3A7`yfN^cbHpW zh3@X^+WhJiV}9WQ*4J+2+zRYhm+YYQ_z7jWz z%*5T`+SpiHDY*uja|Qd$B>San>;z5$4e_6^#~>_8Sen=eh1>=#HD!;KjC0kIzp zltpU`Ihs_gu{ma2tv=mF2WMfou?;UKL!?B+lMOM)Epkqb2?oBhCt@*ch7ea~1aW?U zW)KGgGC^sn+`5%B6Z2F#sK|RHM_J6;Z7_M=RO(oV2<=bDYQ(6==e!d^EF$ISPT)({ z1Wt;qrK7Z5tZnz+*ry;T!-f?@NbK!WYTI43=k}ie1DwK|Lpq@oZySrkS&c4OA@iTr zvQKI0pp`?Dg=CzStIVCg!~ji#ZGOm=Qk@E7|46OFs07UBW@o94Wye~xOd{s6R{EP6 zy_^cPjVK6^HrLq34dE9tkuzm=Eexin+gJ`_q|=0{dgcpSnxb0?I>mtlC*NvC=9UMY z@@i3Zr8G?}^;wv4P>ASEKS;Dr5pSyJ)#`o_qUK(k9Z(d~qdw1 zRKDKU?HlIH%KBIMYzk)27(Y|ks|{yZLu!ozoSXPCo@T63FadT8ttEW?c`TW9yTf^o zY0(4G-s5mBxAJ8zBSW@fmJW+Jv*8xfjKaf=(Ckosu~9?AuP<0cu5h;K2t=5m%OH4# z8O6Xdc4{fNmq~>zNbf(G@0&XF`QIumvd!+A6BcxwCt-b1YyRx5&)Y@ofULuvPC&OX zM7PO?j}gf!a#~#*{ia>it|ZR5{-^E@tLECs{55zW3kA2B4^f_5P1l+K^wu*&F73Nr zT3;CpkVa6k!7Xxe)ehPEv{au`AVucuzSM)R*q=#@eetblT&a&$mHIzb>PBf(%_QG6TQYUA6bOJ3?(yv@lN$=tw zm-$QD5J=`dUPspABqQn3#Q4mPXt#s;7cZcuCi4_9BLrdAEU$F_%LuVq-S}7)u=vSiD&i!ye z1-(3Nc540P3!OOJY0&7bfsHn{;$V(~Wv_JsU0z3IH`=9_S<4Bc>lK+t+S|}=FDD6d z$>kJlLpmLNSZOMU8Aq7Wt~ApEpu%hYmb{ zkR1@Z=)dGkff#ih0|r)8U&X&(SW?Bm8g+E$;h-_>B)xPiMRSqvfLN0l@*YnNd9~1S zD7`e=gUXeQec+Td0HIJupYlOoE3>{`L%uPk=#GPaVE+czl#X9m^Lr0GL#0DS$7LVw z%5m`*^Tn%n9q7xynDf=X;WMWTDBVq&nM*2Zb$Ur99;I>jY)LS*HD9|Pf#qjSo|8{f zh}W#KYd2UFbey-l*R`^(_bxh75;p zg$o*Kw})a{Z6N`{gUppdf?_?0r57p(=!M_Q<(cyrsuy5_3~s~umj+N+hh)*FUuphS zG{Pi`M#Hr$(RRC}(>ATOW`j=Qa)hvHhh0jD`>owZ(o4A6jI=s9F<)V8OG)=5T+;up zH?DYwLDlS<$9U7ws}sm=kOEVvVF9)91|4#1S#O)!db-lqZl}84p}KoDyhHAeW+|R} z#M$EzF?qD!f~;)|D}2J+UEx~n!)kq1TR15LZz_2pw3v;7&^he{HpC6(rIM{4WEJJP zr!R-QjS$4pIxa3+bOVZ90ou>@fcD?!Oa}9hBqz2iRt#Et5eH|;*bnf(0DzlmU^|)H zNw||Ety*WVpr8xQyDjA174ohoZyN{pMKzq3wVSDdUq{PwbEd6iIf9PTPIYnkUki+V z2YH+Kd&{><`A)igobr?k0?~67yPRC_;PyQj51ZK9F48PlmEEl)8?J@Y6p;I4F82nh zi2!745tD@;yY*SSxq<>j+Hp|pswo^2cvaODIi0i<33xXZli8K6!!|VAmEG;K__Brh zrzsL;ylb*c@c4`b&z3mr>IFKcQEs{MZ5bjI{1>k z0Wj1Gm-o+KUfwmTw{WA{x?bngXv+?Um-!|3+jf{g0gWh)KEf?H$cxCk^k$oFxPsB) z$I$`v#{kl5K8iTt-F41=ZA1h?wCwsGdxyttfb<$yo5yU?c2OahfKZ!rZ^g~3t(&ae zdgl>+uxM`~#39tRJ5`fz3`&Ls2dD3<7%INdS;~ZFmK4R zz^xxke#=%s6KqQMa(&on^giu(8Pd8?676$-+U36PSJ(!+yKho|cnD@!;>$@Yarb2u zc{RmV>3jhYe_B;l#&>F58$hV4vPM-tPL-VBGp%%OBM0#|9=0{pwsy+au;5uo;NCl; z=8{r=-lc3+s#Q!+=1C}JmHC>dlpmj$Qff#dN_p}WB`0oZnN3@E?~7E?(LohRM^v)p zX8751gi|)xK@~FU)DszLa!Fvqhk46v*bSa^s&=>Ji6Ud&2#)JP1h9NN$>*Bao{U8Y%G&_t+9K8wSx~+-84?J)NM*Idz&g6W!OzU6iVf!E!CrwmL5 zu#Fj@o{2%t_C)tBvwGqm9H{T|(EWYV`kAa~4y>s~0c-!9F|7$&V|N={uk!0~4Ft6H_xJj9+7c#Ie3|eUlWIT3(b9-+BrW*6!h8#O_mY_Tb8peUHlEBJ@2R~li%lvd=i59RMKS*! zSH;6_wf!BMQ)6Pc;CT2H=5K9O@NFuP3Y`kBEZS?TD%h8)K%8`A%1NPNHR=hF>j>Jp z&rQ#8ne~vZrfHbBuktEfw2WG%?Z39F_9rv7Q}+zwWDuh^N#%*%z*4gN5U%*;JR_vZ z1nC0lRU`wuH*c?_of1@*N3iZMP`*L;zQWz}p}Tz)0|9C$28(JY2t=&Q*{vrk!TfTt z_G;wNB?nYcvX@Z$)xj;pA;MoLFA=DHh~f$(B@tA&5{LUvoD%((BR;Ru2f&#{7&CvC zjG3AE*~L9kB7*Pk+{%zC^oBRiEcCo^d}&seR|dy2 zaf2rh!wr@~PH1ca6+W;G+dbB8+hIyt?=l=%QSFXdl*{xynVM&3*;bWhSDeK$?Qgp* zSQ{XnA_sFNz`p2d;X@KAaBRFoMWzyb&y0oR6>({9{dLHtX8G;bUu#)7-WUmgl(tl* zJ?`RRa#HiaLfZ#*Sq2VN&s*tjHEeq~4A?JvVaM0`2~v{@`JemV@}sg!@;d~5-R1W< z6?3hilYQDTR3K#QE@7*#H5>0#+vC=32RGMO1m5)}yIt~Fx4m0xOtQLN+YyonQD1i% zfX+6Vd3&`)GkltQrAlFGu!{CMl?DzmQUzb^*%@VF1Nkbatci9uM<@#$+oV2O!maFF z0lt|bf#v6NeYEVu_)pry2#uaL0s+Z+wY`RpQR?3a!^?&G(qeszwxEwJ9zjN=&jufa z4oto+K5&gS@}c_H=YS7f>wW8e$_{r4v20$+A z!NJIl`Yvg}w@0UkoLONBMy%9m)^!7bSzI(I@u+dZCAPqhvGxuYY~$`NM7mn(YMXQ& zVB;1ws}aP26oVNI`Rhw@E4Ttow&x4mTp>H{LhU_J9tL3`)?nqToIA-$I5`)&oMx2( zKnL1}s_sg6AD*;1?K&sV+Y}Anv_3k=bU~otsTOi{Cs1l`W0dTY> zf;n(7n%_%7D+uReI^7%N^q2*9VkJ{80DY9Plu&j-7qp50;82(f!AFYM&>-`>xuUy$ z@5Q@ND70WXw4hT!(9*(MyFi2yK;a|30`x-xd_^sc7iw6*LUD%(O|fcm|%xUEFitgi{IW4X4Sod`k( zyByoaBDdVDA{>?IF!Ei^MqL%EFcJXOyvfgyfolSLqFOE_bu(lpJ;EKleSWyOZbfii zmVpb&Ogm)&g^d&t@VmapYHE@dSFC|eMMyD7Wc+Pzi*F0bblD~s{<-FaWUpRGA+a)6r)Tl%td;bsmHAZHzBdJn>aOSCVO$SIP1qFaOVSXX9K)e|n2M+9O` z5owJZ-etB=BWlldyB3BCzxIf#P1E*7ho4qjwy;Tj=4?poM3qNKs|A!7Rp)lMs4w5ve`$$!%)4

~ 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

lj(ODvg`!8Dv`^n*EK}E<^ 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

)I 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 Xnq*7rjgr#y^{<(h+AFPsB;Wr4*MjLs diff --git a/software/FusionX/src/ttymz/Makefile b/software/FusionX/src/ttymz/Makefile index 2a45abf17..a94f9a45e 100644 --- a/software/FusionX/src/ttymz/Makefile +++ b/software/FusionX/src/ttymz/Makefile @@ -1,15 +1,17 @@ # Select the target host. -#MODEL := MZ2000 -#MODEL := MZ700 -#DEBUG := y -MODEL := MZ80A +#MODEL := MZ2000 +#MODEL := MZ700 +#MODEL := MZ80A +#MODEL := PCW8XXX +#MODEL := PCW9XXX KERNEL := $(PWD)/../../../linux/kernel FUSIONX := $(PWD)/../.. CROSS := arm-linux-gnueabihf- +CTRLINC = -IZeta/API -IZ80/API -DTARGET_HOST_$(MODEL)=1 + +ccflags-y = -O2 -I${KERNEL}/drivers/sstar/include -I${KERNEL}/drivers/sstar/include/infinity2m -I${KERNEL}/drivers/sstar/gpio/infinity2m -D__KERNEL_DRIVER__ -DTARGET_HOST_$(MODEL)=1 ifeq ($(DEBUG),y) ccflags-y += -DTTYMZ_DEBUG -else -ccflags-y += -O2 -I${KERNEL}/drivers/sstar/include -I${KERNEL}/drivers/sstar/include/infinity2m -I${KERNEL}/drivers/sstar/gpio/infinity2m -D__KERNEL_DRIVER__ endif obj-m += ttymzdrv.o ttymzdrv-objs += ttymz.o z80io.o sharpmz.o @@ -18,16 +20,42 @@ ttymzdrv-objs += ../../../linux/kernel/drivers/sstar/gpio/infinity2m/mhal_gpio. ttymzdrv-objs += ../../../linux/kernel/drivers/sstar/gpio/infinity2m/mhal_pinmux.o ttymzdrv-objs += ../../../linux/kernel/drivers/sstar/gpio/infinity2m/padmux_tables.o +all: + @echo "Specify target host, ie. make " + @echo "Supported hosts: MZ80A, MZ700, MZ2000, PCW8XXX, PCW9XXX" -all: +MZ80A: MODEL_MZ80A +MZ700: MODEL_MZ700 +MZ2000: MODEL_MZ2000 +PCW8XXX: MODEL_PCW8XXX +PCW9XXX: MODEL_PCW9XXX + +MODEL_MZ80A: + $(MAKE) MODEL=MZ80A BUILD_MZ80A +MODEL_MZ700: + $(MAKE) MODEL=MZ700 BUILD_MZ700 +MODEL_MZ2000: + $(MAKE) MODEL=MZ2000 BUILD_MZ2000 +MODEL_PCW8XXX: + $(MAKE) MODEL=PCW8XXX BUILD_PCW8XXX +MODEL_PCW9XXX: + $(MAKE) MODEL=PCW8XXX BUILD_PCW9XXX + +BUILD_MZ80A: kmod +BUILD_MZ700: kmod +BUILD_MZ2000: kmod +BUILD_PCW8XXX: kmod +BUILD_PCW9XXX: kmod + +kmod: @echo "" @echo "Build TTYMZ driver for host: $(MODEL)" make -C $(KERNEL) ARCH=arm CROSS_COMPILE=$(CROSS) M="$(PWD)" modules - @echo "" install: @echo "Copy kernel driver..." - @cp ttymz.ko $(FUSIONX)/modules/ + @cp ttymzdrv.ko $(FUSIONX)/modules/ clean: make -C $(KERNEL) M=$(PWD) clean + @rm -f ttymz diff --git a/software/FusionX/src/ttymz/sharpmz.c b/software/FusionX/src/ttymz/sharpmz.c index ebdb232a6..dbd82e2f7 100644 --- a/software/FusionX/src/ttymz/sharpmz.c +++ b/software/FusionX/src/ttymz/sharpmz.c @@ -542,21 +542,21 @@ static t_scanCodeMap scanCodeMap[] = { CTRL_G , // ^G F7 CTRL_H , // ^H F8 // S5 28 - 2F - NOKEY , - NOKEY , - NOKEY , - NOKEY , + HOTKEY_ORIGINAL, // 1 - Hotkey to invoke original mode. + HOTKEY_RFS40, // 2 - Hotkey to invoke RFS 40 mode. + HOTKEY_TZFS, // 3 - Hotkey to invoke TZFS mode. + HOTKEY_LINUX, // 4 - Hotkey to invoke Linux mode. NOKEY , NOKEY , NOKEY , NOKEY , // S6 30 - 37 (ERROR? 7 VALUES ONLY!!) - NOKEY , // ^YEN E6 - CTRL_CAPPA , // ^ EF + NOKEY , // ^YEN E6 + CTRL_CAPPA , // ^ EF NOKEY , NOKEY , NOKEY , - CTRL_UNDSCR , // ^, + CTRL_UNDSCR , // ^, NOKEY , NOKEY , // S7 - 38 - 3F @@ -567,22 +567,22 @@ static t_scanCodeMap scanCodeMap[] = { NOKEY , NOKEY , NOKEY , - CTRL_SLASH , // ^/ EE + CTRL_SLASH , // ^/ EE // S8 40 - 47 - modifier keys. - NOKEY , // BREAK - CTRL+BREAK - not yet assigned - NOKEY , // CTRL + NOKEY , // BREAK - CTRL+BREAK - not yet assigned + NOKEY , // CTRL NOKEY , NOKEY , NOKEY , NOKEY , NOKEY , - NOKEY , // SHIFT + NOKEY , // SHIFT // S9 48 - 4F - Function keys. - FUNC1 , // Function key F1 - FUNC2 , // Function key F2 - FUNC3 , // Function key F3 - FUNC4 , // Function key F4 - FUNC5 , // Function key F5 + FUNC1 , // Function key F1 + FUNC2 , // Function key F2 + FUNC3 , // Function key F3 + FUNC4 , // Function key F4 + FUNC5 , // Function key F5 NOKEY , NOKEY , NOKEY @@ -681,8 +681,8 @@ static t_scanCodeMap scanCodeMap[] = { NOKEY }} }; -#endif -#if (TARGET_HOST_MZ80A == 1) + +#elif (TARGET_HOST_MZ80A == 1) static t_scanCodeMap scanCodeMap[] = { // MZ_80A NO SHIFT {{ @@ -1151,6 +1151,477 @@ static t_scanCodeMap scanCodeMap[] = { }} }; +#elif (TARGET_HOST_MZ2000 == 1) +static t_scanCodeMap scanCodeMap[] = { + // MZ_2000 NO SHIFT + {{ + // S0 00 - 07 + NOKEY , // BREAK/CTRL + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + GRAPHKEY , // GRAPH + NOKEY , // SHIFT + // S1 08 - 0F + '2' , // 2 + '1' , // 1 + 'w' , // w + 'q' , // q + 'a' , // a + BACKS , // DELETE + NOKEY , // NULL + 'z' , // z + // S2 10 - 17 + '4' , // 4 + '3' , // 3 + 'r' , // r + 'e' , // e + 'd' , // d + 's' , // s + 'x' , // x + 'c' , // c + // S3 18 - 1F + '6' , // 6 + '5' , // 5 + 'y' , // y + 't' , // t + 'g' , // g + 'f' , // f + 'v' , // v + 'b' , // b + // S4 20 - 27 + '8' , // 8 + '7' , // 7 + 'i' , // i + 'u' , // u + 'j' , // j + 'h' , // h + 'n' , // n + ' ' , // SPACE + // S5 28 - 2F + '0' , // 0 + '9' , // 9 + 'p' , // p + 'o' , // o + 'l' , // l + 'k' , // k + ',' , // , + 'm' , // m + // S6 30 - 37 + '^' , // ^ + '-' , // - + '[' , // [ + '@' , // @ + ':' , // : + ';' , // ; + '/' , // / + '.' , // . + // S7 38 - 3F + HOMEKEY , // HOME. + '\\' , // Backslash + CURSRIGHT, // CURSOR RIGHT + CURSUP , // CURSOR UP + CR , // CR + ']' , // ] + NOKEY , // + '?' , // ? + // S8 40 - 47 - Keypad keys. + '8' , // Keypad 8 + '7' , // 7 + '5' , // 5 + '4' , // 4 + '2' , // 2 + '1' , // 1 + DBLZERO , // 00 + '0' , // 0 + // S9 48 - 4F - Keypad keys. + '+' , // + + '0' , // 9 + '-' , // - + '6' , // 6 + NOKEY , // + '3' , // 3 + NOKEY , + '.' // . + }}, + // MZ_2000 CAPS LOCK + {{ + // S0 00 - 07 + NOKEY , // BREAK/CTRL + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + ALPHAKEY , // GRAPH + NOKEY , // SHIFT + // S1 08 - 0F + '2' , // 2 + '1' , // 1 + 'W' , // W + 'Q' , // Q + 'A' , // A + BACKS , // DELETE + NOKEY , // NULL + 'Z' , // Z + // S2 10 - 17 + '4' , // 4 + '3' , // 3 + 'R' , // R + 'E' , // E + 'D' , // D + 'S' , // S + 'X' , // X + 'C' , // C + // S3 18 - 1F + '6' , // 6 + '5' , // 5 + 'Y' , // Y + 'T' , // T + 'G' , // G + 'F' , // F + 'V' , // V + 'B' , // B + // S4 20 - 27 + '8' , // 8 + '7' , // 7 + 'I' , // I + 'U' , // U + 'J' , // J + 'H' , // H + 'N' , // N + ' ' , // SPACE + // S5 28 - 2F + '0' , // 0 + '9' , // 9 + 'P' , // P + 'O' , // O + 'L' , // L + 'K' , // K + ',' , // , + 'M' , // M + // S6 30 - 37 + '^' , // ^ + '-' , // - + '[' , // [ + '@' , // @ + ':' , // : + ';' , // ; + '/' , // / + '.' , // . + // S7 38 - 3F + HOMEKEY , // HOME. + '\\' , // Backslash + CURSRIGHT, // CURSOR RIGHT + CURSUP , // CURSOR UP + CR , // CR + ']' , // ] + NOKEY , // + '?' , // ? + // S8 40 - 47 - Keypad keys. + '8' , // Keypad 8 + '7' , // 7 + '5' , // 5 + '4' , // 4 + '2' , // 2 + '1' , // 1 + DBLZERO , // 00 + '0' , // 0 + // S9 48 - 4F - Keypad keys. + '+' , // + + '0' , // 9 + '-' , // - + '6' , // 6 + NOKEY , // + '3' , // 3 + NOKEY , + '.' // . + }}, + // MZ_2000 SHIFT LOCK. + {{ + // S0 00 - 07 + NOKEY , // BREAK/CTRL + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + ALPHAKEY , // GRAPH + NOKEY , // SHIFT + // S1 08 - 0F + '"' , // " + '!' , // ! + 'W' , // W + 'Q' , // Q + 'A' , // A + INSERT , // INSERT + NOKEY , // NULL + 'Z' , // Z + // S2 10 - 17 + '$' , // $ + '#' , // # + 'R' , // R + 'E' , // E + 'D' , // D + 'S' , // S + 'X' , // X + 'C' , // C + // S3 18 - 1F + '&' , // & + '%' , // % + 'Y' , // Y + 'T' , // T + 'G' , // G + 'F' , // F + 'V' , // V + 'B' , // B + // S4 20 - 27 + '(' , // ( + '\'' , // ' + 'I' , // I + 'U' , // U + 'J' , // J + 'H' , // H + 'N' , // N + ' ' , // SPACE + // S5 28 - 2F + '_' , // _ + ')' , // ) + 'P' , // P + 'O' , // O + 'L' , // L + 'K' , // K + '<' , // < + 'M' , // M + // S6 30 - 37 + '~' , // ~ + '=' , // = + '{' , // { + '`' , // ` + '*' , // * + '+' , // + + NOKEY , // + '>' , // > + // S7 38 - 3F + CLRKEY , // CLR. + '|' , // | + CURSLEFT , // CURSOR LEFT + CURSDOWN , // CURSOR DOWN + CR , // CR + '}' , // } + NOKEY , // + NOKEY , // + // S8 40 - 47 - Keypad keys. + '8' , // Keypad 8 + '7' , // 7 + '5' , // 5 + '4' , // 4 + '2' , // 2 + '1' , // 1 + DBLZERO , // 00 + '0' , // 0 + // S9 48 - 4F - Keypad keys. + '+' , // + + '0' , // 9 + '-' , // - + '6' , // 6 + NOKEY , // + '3' , // 3 + NOKEY , + '.' // . + }}, + // MZ_2000 CONTROL CODE + {{ + // S0 00 - 07 + NOKEY , // BREAK/CTRL + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + ALPHAGRAPHKEY, // GRAPH + NOKEY , // SHIFT + // S1 08 - 0F + NOKEY , // + NOKEY , // + CTRL_W , // CTRL_W + CTRL_Q , // CTRL_Q + CTRL_A , // CTRL_A + DELETE , // DELETE + NOKEY , // NULL + CTRL_Z , // CTRL_Z + // S2 10 - 17 + NOKEY , // + NOKEY , // + CTRL_R , // CTRL_R + CTRL_E , // CTRL_E + CTRL_D , // CTRL_D + CTRL_S , // CTRL_S + CTRL_X , // CTRL_X + CTRL_C , // CTRL_C + // S3 18 - 1F + NOKEY , // + NOKEY , // + CTRL_Y , // CTRL_Y + CTRL_T , // CTRL_T + CTRL_G , // CTRL_G + CTRL_F , // CTRL_F + CTRL_V , // CTRL_V + CTRL_B , // CTRL_B + // S4 20 - 27 + NOKEY , // + NOKEY , // + CTRL_I , // CTRL_I + CTRL_U , // CTRL_U + CTRL_J , // CTRL_J + CTRL_H , // CTRL_H + CTRL_N , // CTRL_N + ' ' , // SPACE + // S5 28 - 2F + CTRL_UNDSCR, // CTRL+_ + NOKEY , // + CTRL_P , // CTRL_P + CTRL_O , // CTRL_O + CTRL_L , // CTRL_L + CTRL_K , // CTRL_K + NOKEY , // + CTRL_M , // CTRL_M + // S6 30 - 37 + CTRL_CAPPA, // CTRL+^ + NOKEY , // + CTRL_LB , // CTRL+[ + CTRL_AT , // CTRL+@ + NOKEY , // + NOKEY , // + CTRL_SLASH, // CTRL+/ + NOKEY , // + // S7 38 - 3F + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + CTRL_RB , // CTRL+] + NOKEY , // + NOKEY , // + // S8 40 - 47 - Keypad keys. + NOKEY , // Keypad 8 + NOKEY , // 7 + NOKEY , // 5 + HOTKEY_LINUX, // 4 - Hotkey to invoke Linux mode. + HOTKEY_RFS40, // 2 - Hotkey to invoke RFS 40 mode. + HOTKEY_RFS80, // 1 - Hotkey to invoke RFS 80 mode. + NOKEY , // 00 + HOTKEY_ORIGINAL, // 0 - Hotkey to invoke original mode. + // S9 48 - 4F - Keypad keys. + NOKEY , // + + NOKEY , // 9 + NOKEY , // - + NOKEY , // 6 + NOKEY , // + HOTKEY_TZFS, // 3 - Hotkey to invoke TZFS mode. + NOKEY , + NOKEY // . + }}, + // MZ_2000 KANA + {{ + // S0 00 - 07 + NOKEY , // BREAK/CTRL + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + NOKEY , // + GRAPHKEY , // DAKU TEN + NOKEY , // + // S1 08 - 0F + 0x35 , // HA + 0x77 , // TA + 0xD7 , // WA + 0xB3 , // YO + 0xB7 , // HANDAKU + NOKEY , + NOKEY , + NOKEY , + // S2 10 - 17 + 0x7C , // KA + 0x70 , // KE + 0x41 , // SHI + 0x31 , // KO + 0x39 , // HI + 0xA6 , // TE + 0x78 , // KI + 0xDD , // CHI + // S3 18 - 1F + 0x3D , // FU + 0x5D , // MI + 0x6C , // MU + 0x56 , // ME + 0x1D , // RHI + 0x33 , // RA + 0xD5 , // HE + 0xB1 , // HO + // S4 20 - 27 + 0x46 , // SA + 0x6E , // TO + 0xD9 , // THU + 0x48 , // SU + 0x74 , // KU + 0x43 , // SE + 0x4C , // SO + 0x73 , // MA + // S5 28 - 2F + 0x3F , // A + 0x36 , // I + 0x7E , // U + 0x3B , // E + 0x7A , // O + 0x1E , // NA + 0x5F , // NI + 0xA2 , // NU + // S6 30 - 37 + 0xD3 , // YO + 0x9F , // YU + 0xD1 , // YA + 0x00 , // SPACE + 0x9D , // NO + 0xA3 , // NE + 0xD0 , // RU + 0xB9 , // RE + // S7 38 - 3F + 0xC6 , // ?CLR + 0xC5 , // ?HOME + 0xC2 , // ?CURSOR UP + 0xC1 , // ?CURSOR DOWN + 0xC3 , // ?CURSOR RIGHT + 0xC4 , // ?CURSOR LEFT + 0xBB , // DASH + 0xBE , // RO + // S8 40 - 47 - Keypad keys. + '8' , // Keypad 8 + '7' , // 7 + '5' , // 5 + '4' , // 4 + '2' , // 2 + '1' , // 1 + DBLZERO , // 00 + '0' , // 0 + // S9 48 - 4F - Keypad keys. + '+' , // + + '0' , // 9 + '-' , // - + '6' , // 6 + NOKEY , // + '3' , // 3 + NOKEY , + '.' // . + }} +}; + +#else + #error "Unknown host, no keyboard map configured." #endif // Mapping table of sharp special control keys to ANSI ESCape sequences. @@ -1203,8 +1674,36 @@ static t_ansiKeyMap ansiKeySeq[] = { // Colour map for the Ansi Terminal. const unsigned char ansiColourMap[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; -#endif -#if (TARGET_HOST_MZ80A == 1) + +#elif (TARGET_HOST_MZ80A == 1) + // Static structures for controlling and managing hardware features. + // Display control structure. Used to manage the display including the Ansi Terminal. + const t_displayBuffer displayDefault = { .displayAttr = 0x71, .backingRow = 0, .displayCol = 0, .displayRow = 0, .maxBackingRow = (VC_DISPLAY_BUFFER_SIZE / VC_MAX_COLUMNS), + .maxDisplayRow = VC_MAX_ROWS, .maxBackingCol = 80, .useAnsiTerm = 1, .lineWrap = 0, .inDebug = 0 + }; + + // Keyboard control structure. Used to manage keyboard sweep, mapping and store. + const t_keyboard keyboardDefault = { .holdTimer = 0L, .autorepeat = 0, .mode = KEYB_LOWERCASE, .cursorOn = 1, .flashTimer = 0L, .keyBuf[0] = 0x00, .keyBufPtr = 0, + .mode = KEYB_LOWERCASE, .dualmode = KEYB_DUAL_GRAPH + }; + + // Audio control structure. Used to manage audio output. + const t_audio audioDefault = { .audioStopTimer = 0 + }; + + + // AnsiTerminal control structure. Used to manage the inbuilt Ansi Terminal. + const t_AnsiTerm ansitermDefault = { .state = ANSITERM_ESC, .charcnt = 0, .paramcnt = 0, .setDisplayMode = 0, .setExtendedMode = 0, .saveRow = 0, .saveCol = 0, + }; + + // Module control structure. + const t_control ctrlDefault = { .suspendIO = 0, .debug = 0 + }; + + // Colour map for the Ansi Terminal. + const unsigned char ansiColourMap[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + +#elif (TARGET_HOST_MZ2000 == 1) // Static structures for controlling and managing hardware features. // Display control structure. Used to manage the display including the Ansi Terminal. const t_displayBuffer displayDefault = { .displayAttr = 0x71, .backingRow = 0, .displayCol = 0, .displayRow = 0, .maxBackingRow = (VC_DISPLAY_BUFFER_SIZE / VC_MAX_COLUMNS), @@ -1249,6 +1748,11 @@ static t_ansiKeyMap ansiKeySeq[] = { // uint8_t mzInitMBHardware(void) { + #if (TARGET_HOST_MZ700 == 1) + // Ensure memory paging is set to default. + SPI_SEND_32(0x00e4, 0x00 << 8 | CPLD_CMD_WRITEIO_ADDR); + #endif + // From the 1Z-013A monitor code, initialise the 8255 PIO. // WRITE_HARDWARE(1, MBADDR_KEYPF, 0x8A); // 10001010 CTRL WORD MODE0 @@ -1312,10 +1816,11 @@ void mzBeep(uint32_t freq, uint32_t timeout) // Locals. #if (TARGET_HOST_MZ80A == 1) uint16_t freqDiv = TIMER_8253_MZ80A_FREQ/(freq*2); - #else + #elif (TARGET_HOST_MZ700 == 1) uint16_t freqDiv = TIMER_8253_MZ700_FREQ/freq; #endif + #if (TARGET_HOST_MZ2000 == 0) // Setup the 8253 Timer 0 to output a sound, enable output to amplifier and set timeout. WRITE_HARDWARE(0, MBADDR_CONTF, 0x34 ); // Timer 0 to square wave generator, load LSB first. WRITE_HARDWARE(0, MBADDR_CONT0, (freqDiv&0xff) ); @@ -1324,6 +1829,7 @@ void mzBeep(uint32_t freq, uint32_t timeout) // Set a 500ms timeout on the sound to create beep effect. audio.audioStopTimer = timeout == 0 ? 11 : (timeout/10)+1; // Each unit is 10ms, valid range 1..n + #endif return; } @@ -2964,8 +3470,33 @@ uint8_t mzSweepKeys(void) { keyboard.shiftKey = 0; } - #endif - #if (TARGET_HOST_MZ80A == 1) + + #elif (TARGET_HOST_MZ80A == 1) + // Check for modifiers. + // + if((keyboard.scanbuf[0][0] & 0x01) == 0) + { + keyboard.shiftKey = 1; + } else + { + keyboard.shiftKey = 0; + } + if((keyboard.scanbuf[0][0] & 0x80) == 0 && keyboard.shiftKey == 0) + { + keyboard.ctrlKey = 1; + } else + { + keyboard.ctrlKey = 0; + } + if((keyboard.scanbuf[0][0] & 0x80) == 0 && keyboard.shiftKey == 1) + { + keyboard.breakKey = 1; + } else + { + keyboard.breakKey = 0; + } + + #elif (TARGET_HOST_MZ2000 == 1) // Check for modifiers. // if((keyboard.scanbuf[0][0] & 0x01) == 0) diff --git a/software/FusionX/src/ttymz/sharpmz.h b/software/FusionX/src/ttymz/sharpmz.h index a8fe6ccd1..426791f7b 100755 --- a/software/FusionX/src/ttymz/sharpmz.h +++ b/software/FusionX/src/ttymz/sharpmz.h @@ -36,182 +36,210 @@ extern "C" { #endif -#define TARGET_HOST_MZ700 0 // Target compilation for an MZ700 -#define TARGET_HOST_MZ2000 0 // MZ2000 -#define TARGET_HOST_MZ80A 1 // MZ80A +// Build time target. Overrides if compile time definition given. +#if defined(TARGET_HOST_MZ700) + #define TARGET_HOST_MZ700 1 + #define TARGET_HOST_MZ2000 0 + #define TARGET_HOST_MZ80A 0 + #define TARGET_HOST_PCW 0 +#elif defined(TARGET_HOST_MZ2000) + #define TARGET_HOST_MZ2000 1 + #define TARGET_HOST_MZ700 0 + #define TARGET_HOST_MZ80A 0 + #define TARGET_HOST_PCW 0 +#elif defined(TARGET_HOST_MZ80A) + #define TARGET_HOST_MZ80A 1 + #define TARGET_HOST_MZ2000 0 + #define TARGET_HOST_MZ700 0 + #define TARGET_HOST_PCW 0 +#elif defined(TARGET_HOST_PCW8XXX) || defined(TARGET_HOST_PCW9XXX) + #define TARGET_HOST_PCW 1 + #define TARGET_HOST_MZ2000 0 + #define TARGET_HOST_MZ700 0 + #define TARGET_HOST_MZ80A 0 +#else + #define TARGET_HOST_MZ700 0 // Target compilation for an MZ700 + #define TARGET_HOST_MZ2000 0 // MZ2000 + #define TARGET_HOST_MZ80A 0 // MZ80A + #define TARGET_HOST_PCW 0 // Amstrad PCW8XXX/9XXX +#endif // Video display constants. -#define VC_MAX_ROWS 25 // Maximum number of rows on display. -#define VC_MAX_COLUMNS 80 // Maximum number of columns on display. -#define VC_MAX_BUFFER_ROWS 50 // Maximum number of backing store rows for scrollback feature. -#define VC_DISPLAY_BUFFER_SIZE VC_MAX_COLUMNS * VC_MAX_BUFFER_ROWS // Size of the display buffer for scrollback. +#define VC_MAX_ROWS 25 // Maximum number of rows on display. +#if defined(TARGET_HOST_MZ700) + #define VC_MAX_COLUMNS 40 // Maximum number of columns on display. +#else + #define VC_MAX_COLUMNS 80 // Maximum number of columns on display. +#endif +#define VC_MAX_BUFFER_ROWS 50 // Maximum number of backing store rows for scrollback feature. +#define VC_DISPLAY_BUFFER_SIZE VC_MAX_COLUMNS * VC_MAX_BUFFER_ROWS // Size of the display buffer for scrollback. // Keyboard constants. -#define KEYB_AUTOREPEAT_INITIAL_TIME 800 // Time in milliseconds before starting autorepeat. -#define KEYB_AUTOREPEAT_TIME 100 // Time in milliseconds between auto repeating characters. -#define KEYB_FLASH_TIME 350 // Time in milliseconds for the cursor flash change. -#define CURSOR_THICK_BLOCK 0x43 // Thick block cursor for lower case CAPS OFF -#define CURSOR_BLOCK 0xEF // Block cursor for SHIFT Lock. -#define CURSOR_UNDERLINE 0x3E // Thick underscore for CAPS Lock. -#define MAX_KEYB_BUFFER_SIZE 32 // Maximum size of the keyboard buffer. +#define KEYB_AUTOREPEAT_INITIAL_TIME 800 // Time in milliseconds before starting autorepeat. +#define KEYB_AUTOREPEAT_TIME 100 // Time in milliseconds between auto repeating characters. +#define KEYB_FLASH_TIME 350 // Time in milliseconds for the cursor flash change. +#define CURSOR_THICK_BLOCK 0x43 // Thick block cursor for lower case CAPS OFF +#define CURSOR_BLOCK 0xEF // Block cursor for SHIFT Lock. +#define CURSOR_UNDERLINE 0x3E // Thick underscore for CAPS Lock. +#define MAX_KEYB_BUFFER_SIZE 32 // Maximum size of the keyboard buffer. // Audio constants. -#define TIMER_8253_MZ80A_FREQ 2000000 // Base input frequency of Timer 0 for square wave generation. -#define TIMER_8253_MZ700 768000 // Base input frequency of Timer 0 for square wave generation. +#define TIMER_8253_MZ80A_FREQ 2000000 // Base input frequency of Timer 0 for square wave generation. +#define TIMER_8253_MZ700_FREQ 768000 // Base input frequency of Timer 0 for square wave generation. // Base addresses and sizes within the Video Controller. -#define VIDEO_BASE_ADDR 0x000000 // Base address of the Video Controller. -#define VIDEO_VRAM_BASE_ADDR VIDEO_BASE_ADDR + 0x00D000 // Base address of the character video RAM using direct addressing. -#define VIDEO_VRAM_SIZE 0x800 // Size of the video RAM. -#define VIDEO_ARAM_BASE_ADDR VIDEO_BASE_ADDR + 0x00D800 // Base address of the character attribute RAM using direct addressing. -#define VIDEO_ARAM_SIZE 0x800 // Size of the attribute RAM. +#define VIDEO_BASE_ADDR 0x000000 // Base address of the Video Controller. +#define VIDEO_VRAM_BASE_ADDR VIDEO_BASE_ADDR + 0x00D000 // Base address of the character video RAM using direct addressing. +#define VIDEO_VRAM_SIZE 0x800 // Size of the video RAM. +#define VIDEO_ARAM_BASE_ADDR VIDEO_BASE_ADDR + 0x00D800 // Base address of the character attribute RAM using direct addressing. +#define VIDEO_ARAM_SIZE 0x800 // Size of the attribute RAM. // Video Module control bits. -#define VMMODE_MASK 0xF8 // Mask to mask out video mode. -#define VMMODE_MZ80K 0x00 // Video mode = MZ80K -#define VMMODE_MZ80C 0x01 // Video mode = MZ80C -#define VMMODE_MZ1200 0x02 // Video mode = MZ1200 -#define VMMODE_MZ80A 0x03 // Video mode = MZ80A -#define VMMODE_MZ700 0x04 // Video mode = MZ700 -#define VMMODE_MZ800 0x05 // Video mode = MZ800 -#define VMMODE_MZ1500 0x06 // Video mode = MZ1500 -#define VMMODE_MZ80B 0x07 // Video mode = MZ80B -#define VMMODE_MZ2000 0x08 // Video mode = MZ2000 -#define VMMODE_MZ2200 0x09 // Video mode = MZ2200 -#define VMMODE_MZ2500 0x0A // Video mode = MZ2500 -#define VMMODE_80CHAR 0x80 // Enable 80 character display. -#define VMMODE_80CHAR_MASK 0x7F // Mask to filter out display width control bit. -#define VMMODE_COLOUR 0x20 // Enable colour display. -#define VMMODE_COLOUR_MASK 0xDF // Mask to filter out colour control bit. +#define VMMODE_MASK 0xF8 // Mask to mask out video mode. +#define VMMODE_MZ80K 0x00 // Video mode = MZ80K +#define VMMODE_MZ80C 0x01 // Video mode = MZ80C +#define VMMODE_MZ1200 0x02 // Video mode = MZ1200 +#define VMMODE_MZ80A 0x03 // Video mode = MZ80A +#define VMMODE_MZ700 0x04 // Video mode = MZ700 +#define VMMODE_MZ800 0x05 // Video mode = MZ800 +#define VMMODE_MZ1500 0x06 // Video mode = MZ1500 +#define VMMODE_MZ80B 0x07 // Video mode = MZ80B +#define VMMODE_MZ2000 0x08 // Video mode = MZ2000 +#define VMMODE_MZ2200 0x09 // Video mode = MZ2200 +#define VMMODE_MZ2500 0x0A // Video mode = MZ2500 +#define VMMODE_80CHAR 0x80 // Enable 80 character display. +#define VMMODE_80CHAR_MASK 0x7F // Mask to filter out display width control bit. +#define VMMODE_COLOUR 0x20 // Enable colour display. +#define VMMODE_COLOUR_MASK 0xDF // Mask to filter out colour control bit. // Sharp MZ colour attributes. -#define VMATTR_FG_BLACK 0x00 // Foreground black character attribute. -#define VMATTR_FG_BLUE 0x10 // Foreground blue character attribute. -#define VMATTR_FG_RED 0x20 // Foreground red character attribute. -#define VMATTR_FG_PURPLE 0x30 // Foreground purple character attribute. -#define VMATTR_FG_GREEN 0x40 // Foreground green character attribute. -#define VMATTR_FG_CYAN 0x50 // Foreground cyan character attribute. -#define VMATTR_FG_YELLOW 0x60 // Foreground yellow character attribute. -#define VMATTR_FG_WHITE 0x70 // Foreground white character attribute. -#define VMATTR_FG_MASKOUT 0x8F // Mask to filter out foreground attribute. -#define VMATTR_FG_MASKIN 0x70 // Mask to filter out foreground attribute. -#define VMATTR_BG_BLACK 0x00 // Background black character attribute. -#define VMATTR_BG_BLUE 0x01 // Background blue character attribute. -#define VMATTR_BG_RED 0x02 // Background red character attribute. -#define VMATTR_BG_PURPLE 0x03 // Background purple character attribute. -#define VMATTR_BG_GREEN 0x04 // Background green character attribute. -#define VMATTR_BG_CYAN 0x05 // Background cyan character attribute. -#define VMATTR_BG_YELLOW 0x06 // Background yellow character attribute. -#define VMATTR_BG_WHITE 0x07 // Background white character attribute. -#define VMATTR_BG_MASKOUT 0xF8 // Mask to filter out background attribute. -#define VMATTR_BG_MASKIN 0x07 // Mask to filter out background attribute. +#define VMATTR_FG_BLACK 0x00 // Foreground black character attribute. +#define VMATTR_FG_BLUE 0x10 // Foreground blue character attribute. +#define VMATTR_FG_RED 0x20 // Foreground red character attribute. +#define VMATTR_FG_PURPLE 0x30 // Foreground purple character attribute. +#define VMATTR_FG_GREEN 0x40 // Foreground green character attribute. +#define VMATTR_FG_CYAN 0x50 // Foreground cyan character attribute. +#define VMATTR_FG_YELLOW 0x60 // Foreground yellow character attribute. +#define VMATTR_FG_WHITE 0x70 // Foreground white character attribute. +#define VMATTR_FG_MASKOUT 0x8F // Mask to filter out foreground attribute. +#define VMATTR_FG_MASKIN 0x70 // Mask to filter out foreground attribute. +#define VMATTR_BG_BLACK 0x00 // Background black character attribute. +#define VMATTR_BG_BLUE 0x01 // Background blue character attribute. +#define VMATTR_BG_RED 0x02 // Background red character attribute. +#define VMATTR_BG_PURPLE 0x03 // Background purple character attribute. +#define VMATTR_BG_GREEN 0x04 // Background green character attribute. +#define VMATTR_BG_CYAN 0x05 // Background cyan character attribute. +#define VMATTR_BG_YELLOW 0x06 // Background yellow character attribute. +#define VMATTR_BG_WHITE 0x07 // Background white character attribute. +#define VMATTR_BG_MASKOUT 0xF8 // Mask to filter out background attribute. +#define VMATTR_BG_MASKIN 0x07 // Mask to filter out background attribute. // Sharp MZ constants. // -#define MBADDR_KEYPA 0xE000 // Mainboard 8255 Port A -#define MBADDR_KEYPB 0xE001 // Mainboard 8255 Port B -#define MBADDR_KEYPC 0xE002 // Mainboard 8255 Port C -#define MBADDR_KEYPF 0xE003 // Mainboard 8255 Mode Control -#define MBADDR_CSTR 0xE002 // Mainboard 8255 Port C -#define MBADDR_CSTPT 0xE003 // Mainboard 8255 Mode Control -#define MBADDR_CONT0 0xE004 // Mainboard 8253 Counter 0 -#define MBADDR_CONT1 0xE005 // Mainboard 8253 Counter 1 -#define MBADDR_CONT2 0xE006 // Mainboard 8253 Counter 1 -#define MBADDR_CONTF 0xE007 // Mainboard 8253 Mode Control -#define MBADDR_SUNDG 0xE008 // Register for reading the tempo timer status (cursor flash). horizontal blank and switching sound on/off. -#define MBADDR_TEMP 0xE008 // As above, different name used in original source when writing. -#define MBADDR_MEMSW 0xE00C // Memory swap, 0000->C000, C000->0000 -#define MBADDR_MEMSWR 0xE010 // Reset memory swap. -#define MBADDR_NRMDSP 0xE014 // Return display to normal. -#define MBADDR_INVDSP 0xE015 // Invert display. -#define MBADDR_SCLDSP 0xE200 // Hardware scroll, a read to each location adds 8 to the start of the video access address therefore creating hardware scroll. 00 - reset to power up -#define MBADDR_SCLBASE 0xE2 // High byte scroll base. -#define MBADDR_DSPCTL 0xDFFF // Display 40/80 select register (bit 7) +#define MBADDR_KEYPA 0xE000 // Mainboard 8255 Port A +#define MBADDR_KEYPB 0xE001 // Mainboard 8255 Port B +#define MBADDR_KEYPC 0xE002 // Mainboard 8255 Port C +#define MBADDR_KEYPF 0xE003 // Mainboard 8255 Mode Control +#define MBADDR_CSTR 0xE002 // Mainboard 8255 Port C +#define MBADDR_CSTPT 0xE003 // Mainboard 8255 Mode Control +#define MBADDR_CONT0 0xE004 // Mainboard 8253 Counter 0 +#define MBADDR_CONT1 0xE005 // Mainboard 8253 Counter 1 +#define MBADDR_CONT2 0xE006 // Mainboard 8253 Counter 1 +#define MBADDR_CONTF 0xE007 // Mainboard 8253 Mode Control +#define MBADDR_SUNDG 0xE008 // Register for reading the tempo timer status (cursor flash). horizontal blank and switching sound on/off. +#define MBADDR_TEMP 0xE008 // As above, different name used in original source when writing. +#define MBADDR_MEMSW 0xE00C // Memory swap, 0000->C000, C000->0000 +#define MBADDR_MEMSWR 0xE010 // Reset memory swap. +#define MBADDR_NRMDSP 0xE014 // Return display to normal. +#define MBADDR_INVDSP 0xE015 // Invert display. +#define MBADDR_SCLDSP 0xE200 // Hardware scroll, a read to each location adds 8 to the start of the video access address therefore creating hardware scroll. 00 - reset to power up +#define MBADDR_SCLBASE 0xE2 // High byte scroll base. +#define MBADDR_DSPCTL 0xDFFF // Display 40/80 select register (bit 7) //Common character definitions. -#define SCROLL 0x01 // Set scroll direction UP. -#define BELL 0x07 -#define ENQ 0x05 -#define SPACE 0x20 -#define TAB 0x09 // TAB ACROSS (8 SPACES FOR SD-BOARD) -#define CR 0x0D -#define LF 0x0A -#define FF 0x0C -#define DELETE 0x7F -#define BACKS 0x08 -#define SOH 0x01 // For XModem etc. -#define EOT 0x04 -#define ACK 0x06 -#define NAK 0x15 -#define NUL 0x00 -//#define NULL 0x00 -#define CTRL_A 0x01 -#define CTRL_B 0x02 -#define CTRL_C 0x03 -#define CTRL_D 0x04 -#define CTRL_E 0x05 -#define CTRL_F 0x06 -#define CTRL_G 0x07 -#define CTRL_H 0x08 -#define CTRL_I 0x09 -#define CTRL_J 0x0A -#define CTRL_K 0x0B -#define CTRL_L 0x0C -#define CTRL_M 0x0D -#define CTRL_N 0x0E -#define CTRL_O 0x0F -#define CTRL_P 0x10 -#define CTRL_Q 0x11 -#define CTRL_R 0x12 -#define CTRL_S 0x13 -#define CTRL_T 0x14 -#define CTRL_U 0x15 -#define CTRL_V 0x16 -#define CTRL_W 0x17 -#define CTRL_X 0x18 -#define CTRL_Y 0x19 -#define CTRL_Z 0x1A -#define ESC 0x1B -#define CTRL_SLASH 0x1C -#define CTRL_LB 0x1B -#define CTRL_RB 0x1D -#define CTRL_CAPPA 0x1E -#define CTRL_UNDSCR 0x1F -#define CTRL_AT 0x00 -#define FUNC1 0x80 -#define FUNC2 0x81 -#define FUNC3 0x82 -#define FUNC4 0x83 -#define FUNC5 0x84 -#define FUNC6 0x85 -#define FUNC7 0x86 -#define FUNC8 0x87 -#define FUNC9 0x88 -#define FUNC10 0x89 -#define PAGEUP 0xE0 -#define PAGEDOWN 0xE1 -#define CURHOMEKEY 0xE2 -#define ALPHAGRAPHKEY 0xE3 -#define HOTKEY_ORIGINAL 0xE8 -#define HOTKEY_RFS80 0xE9 -#define HOTKEY_RFS40 0xEA -#define HOTKEY_TZFS 0xEB -#define HOTKEY_LINUX 0xEC -#define NOKEY 0xF0 -#define CURSRIGHT 0xF1 -#define CURSLEFT 0xF2 -#define CURSUP 0xF3 -#define CURSDOWN 0xF4 -#define DBLZERO 0xF5 -#define INSERT 0xF6 -#define CLRKEY 0xF7 -#define HOMEKEY 0xF8 -#define ENDKEY 0xF9 -#define ANSITGLKEY 0xFA -#define BREAKKEY 0xFB -#define GRAPHKEY 0xFC -#define ALPHAKEY 0xFD -#define DEBUGKEY 0xFE // Special key to enable debug features such as the ANSI emulation. +#define SCROLL 0x01 // Set scroll direction UP. +#define BELL 0x07 +#define ENQ 0x05 +#define SPACE 0x20 +#define TAB 0x09 // TAB ACROSS (8 SPACES FOR SD-BOARD) +#define CR 0x0D +#define LF 0x0A +#define FF 0x0C +#define DELETE 0x7F +#define BACKS 0x08 +#define SOH 0x01 // For XModem etc. +#define EOT 0x04 +#define ACK 0x06 +#define NAK 0x15 +#define NUL 0x00 +//#define NULL 0x00 +#define CTRL_A 0x01 +#define CTRL_B 0x02 +#define CTRL_C 0x03 +#define CTRL_D 0x04 +#define CTRL_E 0x05 +#define CTRL_F 0x06 +#define CTRL_G 0x07 +#define CTRL_H 0x08 +#define CTRL_I 0x09 +#define CTRL_J 0x0A +#define CTRL_K 0x0B +#define CTRL_L 0x0C +#define CTRL_M 0x0D +#define CTRL_N 0x0E +#define CTRL_O 0x0F +#define CTRL_P 0x10 +#define CTRL_Q 0x11 +#define CTRL_R 0x12 +#define CTRL_S 0x13 +#define CTRL_T 0x14 +#define CTRL_U 0x15 +#define CTRL_V 0x16 +#define CTRL_W 0x17 +#define CTRL_X 0x18 +#define CTRL_Y 0x19 +#define CTRL_Z 0x1A +#define ESC 0x1B +#define CTRL_SLASH 0x1C +#define CTRL_LB 0x1B +#define CTRL_RB 0x1D +#define CTRL_CAPPA 0x1E +#define CTRL_UNDSCR 0x1F +#define CTRL_AT 0x00 +#define FUNC1 0x80 +#define FUNC2 0x81 +#define FUNC3 0x82 +#define FUNC4 0x83 +#define FUNC5 0x84 +#define FUNC6 0x85 +#define FUNC7 0x86 +#define FUNC8 0x87 +#define FUNC9 0x88 +#define FUNC10 0x89 +#define PAGEUP 0xE0 +#define PAGEDOWN 0xE1 +#define CURHOMEKEY 0xE2 +#define ALPHAGRAPHKEY 0xE3 +#define HOTKEY_ORIGINAL 0xE8 +#define HOTKEY_RFS80 0xE9 +#define HOTKEY_RFS40 0xEA +#define HOTKEY_TZFS 0xEB +#define HOTKEY_LINUX 0xEC +#define NOKEY 0xF0 +#define CURSRIGHT 0xF1 +#define CURSLEFT 0xF2 +#define CURSUP 0xF3 +#define CURSDOWN 0xF4 +#define DBLZERO 0xF5 +#define INSERT 0xF6 +#define CLRKEY 0xF7 +#define HOMEKEY 0xF8 +#define ENDKEY 0xF9 +#define ANSITGLKEY 0xFA +#define BREAKKEY 0xFB +#define GRAPHKEY 0xFC +#define ALPHAKEY 0xFD +#define DEBUGKEY 0xFE // Special key to enable debug features such as the ANSI emulation. // Macros. // @@ -285,7 +313,7 @@ typedef struct { uint8_t dispCode; } t_dispCodeMap; -// Mapping table from keyboard scan codes to Sharp MZ-700 keys. +// Mapping table from keyboard scan codes to Sharp MZ keys. // typedef struct { uint8_t scanCode[80]; diff --git a/software/FusionX/src/ttymz/z80io.c b/software/FusionX/src/ttymz/z80io.c index 084eeaf91..ca816832a 100644 --- a/software/FusionX/src/ttymz/z80io.c +++ b/software/FusionX/src/ttymz/z80io.c @@ -431,7 +431,7 @@ uint8_t z80io_SPI_Send32(uint32_t txData, uint32_t *rxData) //-------------------------------------------------------- // Test Methods. //-------------------------------------------------------- -#ifdef INCLUDE_TEST_METHODS +#if defined(INCLUDE_TEST_METHODS) && INCLUDE_TEST_METHODS == 1 #include "z80io_test.c" #else uint8_t z80io_Z80_TestMemory(void) diff --git a/software/FusionX/src/ttymz/z80io.h b/software/FusionX/src/ttymz/z80io.h index 459e941f0..74b393b33 100755 --- a/software/FusionX/src/ttymz/z80io.h +++ b/software/FusionX/src/ttymz/z80io.h @@ -39,7 +39,7 @@ #endif // Definitions to control compilation. -//#define INCLUDE_TEST_METHODS 0 +#define INCLUDE_TEST_METHODS 0 // CPLD Commands. #define CPLD_CMD_FETCH_ADDR 0x10 @@ -82,6 +82,14 @@ #define CPLD_CMD_READIO_ADDR_P5 0x35 #define CPLD_CMD_READIO_ADDR_P6 0x36 #define CPLD_CMD_READIO_ADDR_P7 0x37 +#define CPLD_CMD_READIO_WRITE_ADDR 0x38 +#define CPLD_CMD_READIO_WRITE_ADDR_P1 0x39 +#define CPLD_CMD_READIO_WRITE_ADDR_P2 0x3A +#define CPLD_CMD_READIO_WRITE_ADDR_P3 0x3B +#define CPLD_CMD_READIO_WRITE_ADDR_P4 0x3C +#define CPLD_CMD_READIO_WRITE_ADDR_P5 0x3D +#define CPLD_CMD_READIO_WRITE_ADDR_P6 0x3E +#define CPLD_CMD_READIO_WRITE_ADDR_P7 0x3F #define CPLD_CMD_HALT 0x50 #define CPLD_CMD_REFRESH 0x51 #define CPLD_CMD_SET_SIGROUP1 0xF0 @@ -386,8 +394,8 @@ #define CPLD_LAST_TSTATE() (MHal_RIU_REG(PAD_Z80IO_LTSTATE_ADDR) & 0x4) #define CPLD_Z80_INT() (MHal_RIU_REG(PAD_Z80IO_INT_ADDR) & 0x4) #define CPLD_Z80_NMI() (MHal_RIU_REG(PAD_Z80IO_NMI_ADDR) & 0x4) -#define SPI_SEND8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ +#define SPI_SEND_8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d_); \ MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); \ while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ @@ -396,9 +404,18 @@ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE);\ } +#define SPI_SEND_I_8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d_); \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE);\ + } #define SPI_SEND16(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ @@ -406,10 +423,9 @@ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ } -#define SPI_SEND32(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ +#define SPI_SEND_16(_d1_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d1_); \ while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ @@ -417,24 +433,63 @@ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ } -#define SPI_SEND32i(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ - pr_info("Stage 0");\ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - pr_info("Stage 1");\ +#define SPI_SEND_P_16(_d1_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d1_); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) != 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - pr_info("Stage 2");\ - timeout = MAX_CHECK_CNT; \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; }; \ - pr_info("Stage 3");\ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + } +#define SPI_SET_FRAME_SIZE() { MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + } +#define SPI_SEND32(_d_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; };\ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + } +#define SPI_SEND_32(_d1_, _d2_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d2_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)_d1_); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; };\ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + } +#define SPI_SEND_I_32(_d1_, _d2_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d2_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)_d1_); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + } +#define SPI_SEND_48(_d1_, _d2_, _d3_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 6); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d3_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)_d2_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+2, (uint16_t)_d1_); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; };\ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ } - // while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; }; // read 2 byte #define MSPI_READ(_reg_) READ_WORD(gMspBaseAddr + ((_reg_)<<2)) // write 2 byte diff --git a/software/FusionX/src/z80drv/MZ2000/z80ctrl.c b/software/FusionX/src/z80drv/MZ2000/z80ctrl.c deleted file mode 100644 index 6e7565c6a..000000000 --- a/software/FusionX/src/z80drv/MZ2000/z80ctrl.c +++ /dev/null @@ -1,794 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80ctrl.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 Control Interface -// This file contains a command line utility tool for controlling the z80drv device -// driver. The tool allows manipulation of the emulated Z80, inspection of its -// memory and data, transmission of adhoc commands to the underlying CPLD-Z80 -// gateway and loading/saving of programs and data to/from the Z80 virtual and -// host memory. -// -// Credits: Zilog Z80 CPU Emulator v0.2 written by Manuel Sainz de Baranda y Goñi -// The Z80 CPU Emulator is the heart of the Z80 device driver. -// Copyright: (c) 2019-2022 Philip Smart -// (c) 1999-2022 Manuel Sainz de Baranda y Goñi -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80driver.h" - -#define VERSION "1.0" -#define AUTHOR "P.D.Smart" -#define COPYRIGHT "(c) 2018-22" - -// Getopt_long is buggy so we use optparse. -#define OPTPARSE_IMPLEMENTATION -#define OPTPARSE_API static -#include "optparse.h" - -// Device driver name. -#define DEVICE_FILENAME "/dev/z80drv" - -// Constants for the Sharp MZ80A MZF file format. -#define MZF_HEADER_SIZE 128 // Size of the MZF header. -#define MZF_ATTRIBUTE 0x00 // Code Type, 01 = Machine Code. -#define MZF_FILENAME 0x01 // Title/Name (17 bytes). -#define MZF_FILENAME_LEN 17 // Length of the filename, it is not NULL terminated, generally a CR can be taken as terminator but not guaranteed. -#define MZF_FILESIZE 0x12 // Size of program. -#define MZF_LOADADDR 0x14 // Load address of program. -#define MZF_EXECADDR 0x16 // Exec address of program. -#define MZF_COMMENT 0x18 // Comment, used for details of the file or startup code. -#define MZF_COMMENT_LEN 104 // Length of the comment field. -#define CMT_TYPE_OBJCD 0x001 // MZF contains a binary object. -#define CMT_TYPE_BTX1CD 0x002 // MZF contains a BASIC program. -#define CMT_TYPE_BTX2CD 0x005 // MZF contains a BASIC program. -#define CMT_TYPE_TZOBJCD0 0x0F8 // MZF contains a TZFS binary object for page 0. -#define CMT_TYPE_TZOBJCD1 0x0F9 -#define CMT_TYPE_TZOBJCD2 0x0FA -#define CMT_TYPE_TZOBJCD3 0x0FB -#define CMT_TYPE_TZOBJCD4 0x0FC -#define CMT_TYPE_TZOBJCD5 0x0FD -#define CMT_TYPE_TZOBJCD6 0x0FE -#define CMT_TYPE_TZOBJCD7 0x0FF // MZF contains a TZFS binary object for page 7. -#define MZ_CMT_ADDR 0x10F0 - -// Structure to define a Sharp MZ80A MZF directory structure. This header appears at the beginning of every Sharp MZ80A tape (and more recently archived/emulator) images. -// -typedef struct __attribute__((__packed__)) { - uint8_t attr; // MZF attribute describing the file. - uint8_t fileName[MZF_FILENAME_LEN]; // Each directory entry is the size of an MZF filename. - uint16_t fileSize; // Size of file. - uint16_t loadAddr; // Load address for the file. - uint16_t execAddr; // Execution address where the Z80 starts processing. - uint8_t comment[MZF_COMMENT_LEN]; // Text comment field but often contains a startup machine code program. -} t_svcDirEnt; - -// Possible commands to be issued to the Z80 driver. -enum CTRL_COMMANDS { - Z80_CMD_STOP = 0, - Z80_CMD_START = 1, - Z80_CMD_PAUSE = 2, - Z80_CMD_CONTINUE = 3, - Z80_CMD_RESET = 4, - Z80_CMD_SPEED = 5, - Z80_CMD_HOST_RAM = 6, - Z80_CMD_VIRTUAL_RAM = 7, - Z80_CMD_DUMP_MEMORY = 8, - Z80_CMD_MEMORY_TEST = 9, - CPLD_CMD_SEND_CMD = 10, - CPLD_CMD_SPI_TEST = 11, - CPLD_CMD_PRL_TEST = 12 -}; - - -// Shared memory between this process and the Z80 driver. -static t_Z80Ctrl *Z80Ctrl = NULL; - -// Method to obtain and return the output screen width. -// -uint8_t getScreenWidth(void) -{ - return(MAX_SCREEN_WIDTH); -} - -struct termios orig_termios; - -void reset_terminal_mode() -{ - tcsetattr(0, TCSANOW, &orig_termios); -} - -void set_conio_terminal_mode() -{ - struct termios new_termios; - - /* take two copies - one for now, one for later */ - tcgetattr(0, &orig_termios); - memcpy(&new_termios, &orig_termios, sizeof(new_termios)); - - /* register cleanup handler, and set the new terminal mode */ - atexit(reset_terminal_mode); - cfmakeraw(&new_termios); - tcsetattr(0, TCSANOW, &new_termios); -} - -int kbhit() -{ - struct timeval tv = { 0L, 0L }; - fd_set fds; - FD_ZERO(&fds); - FD_SET(0, &fds); - return select(1, &fds, NULL, NULL, &tv) > 0; -} - -int getch(uint8_t wait) -{ - int r; - unsigned char c; - - if(wait != 0 || (wait == 0 && kbhit())) - { - if ((r = read(0, &c, sizeof(c))) < 0) { - return r; - } else { - return c; - } - } - return 0; -} - -void delay(int number_of_seconds) -{ - // Converting time into milli_seconds - int milli_seconds = 1000 * number_of_seconds; - - // Storing start time - clock_t start_time = clock(); - - // looping till required time is not achieved - while (clock() < start_time + milli_seconds); -} - -// Function to dump out a given section of memory via the UART. -// -int memoryDump(uint32_t memaddr, uint32_t memsize, uint8_t memoryFlag, uint32_t memwidth, uint32_t dispaddr, uint8_t dispwidth) -{ - uint8_t displayWidth = dispwidth;; - uint32_t pnt = memaddr; - uint32_t endAddr = memaddr + memsize; - uint32_t addr = dispaddr; - uint32_t i = 0; - //uint32_t data; - int8_t keyIn; - int result = -1; - char c = 0; - - // Sanity check. memoryFlag == 0 required kernel driver to dump so we exit as it cannot be performed here. - if(memoryFlag == 0) - return(-1); - - // Reconfigure terminal to allow non-blocking key input. - // - set_conio_terminal_mode(); - - // If not set, calculate output line width according to connected display width. - // - if(displayWidth == 0) - { - switch(getScreenWidth()) - { - case 40: - displayWidth = 8; - break; - case 80: - displayWidth = 16; - break; - default: - displayWidth = 32; - break; - } - } - - while (1) - { - printf("%08lX", addr); // print address - printf(": "); - - // print hexadecimal data - for (i=0; i < displayWidth; ) - { - switch(memwidth) - { - case 16: - if(pnt+i < endAddr) - printf("%04X", memoryFlag == 1 ? (uint16_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint16_t)Z80Ctrl->page[pnt+i] : (uint16_t)Z80Ctrl->iopage[pnt+i]); - else - printf(" "); - i++; - break; - - case 32: - if(pnt+i < endAddr) - printf("%08lX", memoryFlag == 1 ? (uint32_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint32_t)Z80Ctrl->page[pnt+i] : (uint32_t)Z80Ctrl->iopage[pnt+i]); - else - printf(" "); - i++; - break; - - case 8: - default: - if(pnt+i < endAddr) - printf("%02X", memoryFlag == 1 ? (uint8_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint8_t)Z80Ctrl->page[pnt+i] : (uint8_t)Z80Ctrl->iopage[pnt+i]); - else - printf(" "); - i++; - break; - } - fputc((char)' ', stdout); - } - - // print ascii data - printf(" |"); - - // print single ascii char - for (i=0; i < displayWidth; i++) - { - c = memoryFlag == 1 ? (char)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (char)Z80Ctrl->page[pnt+i] : (char)Z80Ctrl->iopage[pnt+i]; - if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~')) - fputc((char)c, stdout); - else - fputc((char)' ', stdout); - } - - printf("|\r\n"); - fflush(stdout); - - // Move on one row. - pnt += displayWidth; - addr += displayWidth; - - // User abort (ESC), pause (Space) or all done? - // - keyIn = getch(0); - if(keyIn == ' ') - { - do { - keyIn = getch(0); - } while(keyIn != ' ' && keyIn != 0x1b); - } - // Escape key pressed, exit with 0 to indicate this to caller. - if (keyIn == 0x1b) - { - sleep(1); - result = 0; - goto memoryDumpExit; - } - - // End of buffer, exit the loop. - if(pnt >= (memaddr + memsize)) - { - break; - } - } - - // Normal exit, return -1 to show no key pressed. -memoryDumpExit: - reset_terminal_mode(); - return(result); -} - -// Method to load a program or data file into the Z80 memory. First load into Virtual memory and then trigger a sync to bring Host RAM in line. -// -int z80load(int fdZ80, char *fileName) -{ - // Locals. - struct ioctlCmd ioctlCmd; - int ret = 0; - t_svcDirEnt mzfHeader; - - // Pause the Z80. - // - ioctlCmd.cmd = IOCTL_CMD_Z80_PAUSE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - // Open the file and read directly into the Virtual memory via the share. - FILE *ptr; - ptr = fopen(fileName, "rb"); - if(ptr) - { - // First the header. - fread((uint8_t *)&mzfHeader, MZF_HEADER_SIZE, 1, ptr); - -#if(TARGET_HOST_MZ700 == 1) - if(mzfHeader.loadAddr > 0x1000) - { -#endif - // Copy in the header. - memcpy((uint8_t *)&Z80Ctrl->memory[MZ_CMT_ADDR], (uint8_t *)&mzfHeader, MZF_HEADER_SIZE); - - // Now read in the data. - fread(&Z80Ctrl->memory[mzfHeader.loadAddr], mzfHeader.fileSize, 1, ptr); - printf("Loaded %s, Size:%04x, Addr:%04x, Exec:%04x\n", fileName, mzfHeader.fileSize, mzfHeader.loadAddr, mzfHeader.execAddr); -#if(TARGET_HOST_MZ700 == 1) - } -#endif - - // Sync the loaded image from Virtual memory to hard memory. - ioctlCmd.cmd = IOCTL_CMD_SYNC_TO_HOST_RAM; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - -#if(TARGET_HOST_MZ2000 == 1) - // Set PC to 2 (NST) which switches to RUN mode and executes at 0000H - ioctlCmd.z80.pc = 2; -#endif -#if(TARGET_HOST_MZ700 == 1) - // MZ-700 just use the MZF header exec address. - ioctlCmd.z80.pc = mzfHeader.execAddr; -#endif - - // Set PC to required setting ready for run. - ioctlCmd.cmd = IOCTL_CMD_SETPC; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - // Resume Z80 processing. - // - ioctlCmd.cmd = IOCTL_CMD_Z80_CONTINUE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - } - else - printf("Couldnt open file\n"); - - return ret; -} - -// Method to request basic Z80 operations. -// -int ctrlCmd(int fdZ80, enum CTRL_COMMANDS cmd, long param1, long param2, long param3) -{ - // Locals. - struct ioctlCmd ioctlCmd; - uint32_t idx; - int ret = 0; - - switch(cmd) - { - case Z80_CMD_STOP: - // Use IOCTL to request Z80 to Stop (power off) processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_START: - // Use IOCTL to request Z80 to Start (power on) processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_START; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_PAUSE: - // Use IOCTL to request Z80 to pause processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_PAUSE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_CONTINUE: - // Use IOCTL to request Z80 continue processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_CONTINUE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_RESET: - // Use IOCTL to request Z80 reset. - ioctlCmd.cmd = IOCTL_CMD_Z80_RESET; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_SPEED: - // Check value is in range. - for(idx=1; idx < 256; idx+=idx) - { - if((uint32_t)param1 == idx) break; - } - if(idx == 256) - { - printf("Speed factor is illegal. It must be a multiple value of the original CPU clock, ie. 1x, 2x, 4x etc\n"); - ret = -1; - } else - { - // Use IOCTL to request Z80 cpu freq change. - ioctlCmd.speed.speedMultiplier = (uint32_t)param1; - ioctlCmd.cmd = IOCTL_CMD_Z80_CPU_FREQ; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - } - break; - case CPLD_CMD_SEND_CMD: - // Build up the IOCTL command to request the given data is sent to the CPLD. - ioctlCmd.cmd = IOCTL_CMD_CPLD_CMD; - ioctlCmd.cpld.cmd = (uint32_t)param1; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_DUMP_MEMORY: - // If virtual memory, we can dump it via the shared memory segment. - if((uint8_t)param1) - { - memoryDump((uint32_t)param2, (uint32_t)param3, (uint8_t)param1, (uint8_t)param1 == 2 || (uint8_t)param1 == 3 ? 32 : 8, (uint32_t)param2, 0); - } else - { - // Build an IOCTL command to get the driver to dump the memory. - ioctlCmd.cmd = IOCTL_CMD_DUMP_MEMORY; - ioctlCmd.addr.start = (uint32_t)param2; - ioctlCmd.addr.end = (uint32_t)param2+(uint32_t)param3; - ioctlCmd.addr.size = (uint32_t)param3; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - } - break; - case Z80_CMD_HOST_RAM: - // Use IOCTL to request change to host RAM. - ioctlCmd.cmd = IOCTL_CMD_USE_HOST_RAM; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_VIRTUAL_RAM: - // Use IOCTL to request change to host RAM. - ioctlCmd.cmd = IOCTL_CMD_USE_VIRTUAL_RAM; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_MEMORY_TEST: - // Send command to test the SPI. - ioctlCmd.cmd = IOCTL_CMD_Z80_MEMTEST; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case CPLD_CMD_PRL_TEST: - // Send command to test the SPI. - ioctlCmd.cmd = IOCTL_CMD_PRL_TEST; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case CPLD_CMD_SPI_TEST: - // Send command to test the SPI. - ioctlCmd.cmd = IOCTL_CMD_SPI_TEST; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - - default: - printf("Command not supported!\n"); - ret = -1; - break; - } - - return ret; -} - -// Method to perform some simple tests on the Z80 emulator. -// -int z80test(int fdZ80) -{ - // Locals. - struct ioctlCmd ioctlCmd; - int ret = 0; - - // Stop the Z80. - // -printf("Send STOP\n"); - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - FILE *ptr; - ptr = fopen("/customer/mz700.rom", "rb"); - if(ptr) - { - fread(&Z80Ctrl->memory, 65536, 1, ptr); - } else printf("Couldnt open file\n"); - - // Configure the Z80. - // -printf("Send SETPC\n"); - ioctlCmd.z80.pc = 0; - ioctl(fdZ80, IOCTL_CMD_SETPC, &ioctlCmd); - - memoryDump(0 , 65536, 1, 8, 0, 0); - - // Start the Z80. - // -printf("Send START\n"); - ioctlCmd.cmd = IOCTL_CMD_Z80_START; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - delay(10); - -printf("Send STOP\n"); - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - memoryDump(0, 65536, 1, 8, 0, 0); -out: - return ret; -} - -// Output usage screen. So mamy commands you do need to be prompted!! -void showArgs(char *progName, struct optparse *options) -{ - printf("%s %s %s %s\n\n", progName, VERSION, COPYRIGHT, AUTHOR); - printf("Synopsis:\n"); - printf("%s --help # This help screen.\n", progName); - printf(" --cmd = RESET # Reset the Z80\n"); - printf(" = STOP # Stop and power off the Z80\n"); - printf(" = START # Power on and start the Z80\n"); - printf(" = PAUSE # Pause running Z80\n"); - printf(" = CONTINUE # Continue Z80 execution\n"); - printf(" = HOSTRAM # Use HOST DRAM\n"); - printf(" = VIRTRAM # Use Virtual RAM\n"); - printf(" = SPEED --speed <1, 2, 4, 8, 16, 32, 64, 128> # In Virtual RAM mode, set CPU speed to base clock x factor.\n"); - printf(" = LOADMZF --file # Load MZF file into memory.\n"); - printf(" = DUMP --addr <24bit addr> --end <24bit addr> [--size <24bit>]--virtual <0 - Host RAM, 1 = Virtual RAM, 2 = PageTable, 3 = IOPageTable>\n"); - printf(" = CPLDCMD --data <32bit command> # Send adhoc 32bit command to CPLD.\n"); - printf(" = Z80TEST # Perform various debugging tests\n"); - printf(" = SPITEST # Perform SPI testing\n"); - printf(" = PRLTEST # Perform Parallel Bus testing\n"); - printf(" = Z80MEMTEST # Perform HOST memory tests.\n"); - printf(" -- # Some commands can be abbreviated.\n"); - -} - -int main(int argc, char *argv[]) -{ - int fdZ80; - char buff[64]; - char cmd[64] = { 0 }; - char fileName[256] = { 0 }; - int opt; - uint32_t hexData = 0; - long speedMultiplier = 1; - long startAddr = 0x0000; - long endAddr = 0x1000; - int virtualMemory = 0; - int helpFlag = 0; - int verboseFlag = 0; - - // Define parameters to be processed. - struct optparse options; - static struct optparse_long long_options[] = - { - {"help", 'h', OPTPARSE_NONE}, - {"cmd", 'c', OPTPARSE_REQUIRED}, - {"file", 'f', OPTPARSE_REQUIRED}, - {"data", 'd', OPTPARSE_REQUIRED}, - {"speed", 'S', OPTPARSE_REQUIRED}, - {"virtual", 'V', OPTPARSE_REQUIRED}, - {"addr", 'a', OPTPARSE_REQUIRED}, - {"end", 'e', OPTPARSE_REQUIRED}, - {"size", 's', OPTPARSE_REQUIRED}, - {"verbose", 'v', OPTPARSE_NONE}, - {"dump", '1', OPTPARSE_NONE}, - {"loadmzf", '2', OPTPARSE_NONE}, - {"reset", '3', OPTPARSE_NONE}, - {"stop", '4', OPTPARSE_NONE}, - {"start", '5', OPTPARSE_NONE}, - {"pause", '6', OPTPARSE_NONE}, - {"continue", '7', OPTPARSE_NONE}, - {"speed", '8', OPTPARSE_NONE}, - {"cpldcmd", '9', OPTPARSE_NONE}, - {0} - }; - - // Parse the command line options. - // - optparse_init(&options, argv); - while((opt = optparse_long(&options, long_options, NULL)) != -1) - { - switch(opt) - { - // Hex data. - case 'd': - // hexData = (uint32_t)strtol(options.optarg, NULL, 0); - sscanf(options.optarg, "0x%08x", &hexData); - printf("Hex data:%08x\n", hexData); - break; - - // Start address for memory operations. - case 'a': - startAddr = strtol(options.optarg, NULL, 0); - //printf("Start Addr:%04x\n", startAddr); - break; - - // Speed multiplication factor for CPU governor when running in virtual memory. - case 'S': - speedMultiplier = strtol(options.optarg, NULL, 0); - //printf("Speed = base freq x %d\n", speedFactor); - break; - - // End address for memory operations. - case 'e': - endAddr = strtol(options.optarg, NULL, 0); - //printf("End Addr:%04x\n", endAddr); - break; - - // Size instead of end address for memory operations. - case 's': - endAddr = startAddr + strtol(options.optarg, NULL, 0); - //printf("End Addr:%04x\n", endAddr); - break; - - // Virtual memory flag, 0 = host, 1 = virtual memory, 2 = page table, 3 = iopage table. - case 'V': - virtualMemory = atoi(options.optarg); - break; - - // Filename. - case 'f': - strcpy(fileName, options.optarg); - break; - - // Command to execute. - case 'c': - strcpy(cmd, options.optarg); - break; - - // Quick command flags. - case '1': - strcpy(cmd, "DUMP"); - break; - case '2': - strcpy(cmd, "LOADMZF"); - break; - case '3': - strcpy(cmd, "RESET"); - break; - case '4': - strcpy(cmd, "STOP"); - break; - case '5': - strcpy(cmd, "START"); - break; - case '6': - strcpy(cmd, "PAUSE"); - break; - case '7': - strcpy(cmd, "CONTINUE"); - break; - case '8': - strcpy(cmd, "SPEED"); - break; - case '9': - strcpy(cmd, "CPLDCMD"); - break; - - // Verbose mode. - case 'v': - verboseFlag = 1; - break; - - // Command help needed. - case 'h': - helpFlag = 1; - showArgs(argv[0], &options); - break; - - // Unrecognised, show synopsis. - case '?': - showArgs(argv[0], &options); - printf("%s: %s\n", argv[0], options.errmsg); - return(1); - } - } - - // Open the z80drv driver and attach to its shared memory, basically the Z80 control structure which includes the virtual Z80 memory. - fdZ80 = open(DEVICE_FILENAME, O_RDWR|O_NDELAY); - if(fdZ80 >= 0) - { - Z80Ctrl = (t_Z80Ctrl *)mmap(0, sizeof(t_Z80Ctrl), PROT_READ | PROT_WRITE, MAP_SHARED, fdZ80, 0); - if(Z80Ctrl == (void *)-1) - { - printf("Failed to attach to the Z80 Control structure, cannot continue, exitting....\n"); - close(fdZ80); - exit(1); - } - } else - { - printf("Failed to open the Z80 Driver, exitting...\n"); - exit(1); - } - - // Basic string to method mapping. Started off with just 1 or two but has grown, may need a table! - if(strcasecmp(cmd, "LOADMZF") == 0) - { - z80load(fdZ80, fileName); - } else - if(strcasecmp(cmd, "RESET") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_RESET, 0, 0, 0); - } else - if(strcasecmp(cmd, "STOP") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_STOP, 0, 0, 0); - } else - if(strcasecmp(cmd, "START") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_START, 0, 0, 0); - } else - if(strcasecmp(cmd, "PAUSE") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_PAUSE, 0, 0, 0); - } else - if(strcasecmp(cmd, "CONTINUE") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_CONTINUE, 0, 0, 0); - } else - if(strcasecmp(cmd, "SPEED") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_SPEED, speedMultiplier, 0, 0); - } else - if(strcasecmp(cmd, "DUMP") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_DUMP_MEMORY, virtualMemory, startAddr, (endAddr - startAddr)); - } else - if(strcasecmp(cmd, "HOSTRAM") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_HOST_RAM, 0, 0, 0); - } else - if(strcasecmp(cmd, "VIRTRAM") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_VIRTUAL_RAM, 0, 0, 0); - } else - if(strcasecmp(cmd, "CPLDCMD") == 0) - { - ctrlCmd(fdZ80, CPLD_CMD_SEND_CMD, hexData, 0, 0); - } else - - // Test methods, if the code is built-in to the driver. - if(strcasecmp(cmd, "Z80TEST") == 0) - { - z80test(fdZ80); - } else - if(strcasecmp(cmd, "SPITEST") == 0) - { - ctrlCmd(fdZ80, CPLD_CMD_SPI_TEST, 0, 0, 0); - } else - if(strcasecmp(cmd, "PRLTEST") == 0) - { - ctrlCmd(fdZ80, CPLD_CMD_PRL_TEST, 0, 0, 0); - } else - if(strcasecmp(cmd, "Z80MEMTEST") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_MEMORY_TEST, 0, 0, 0); - } - else - { - showArgs(argv[0], &options); - printf("No command given, nothing done!\n"); - } - - // Unmap shared memory and close the device. - munmap(Z80Ctrl, sizeof(t_Z80Ctrl)); - close(fdZ80); - - return(0); -} diff --git a/software/FusionX/src/z80drv/MZ2000/z80driver.c b/software/FusionX/src/z80drv/MZ2000/z80driver.c deleted file mode 100644 index 019e605bc..000000000 --- a/software/FusionX/src/z80drv/MZ2000/z80driver.c +++ /dev/null @@ -1,1616 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80driver.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 Driver -// This file contains the methods used to create a linux device driver which provides -// the services of a Z80 CPU emulation and the control of an underlying Z80'less host -// system. In essence this driver is the host Z80 CPU. -// Credits: Zilog Z80 CPU Emulator v0.2 written by Manuel Sainz de Baranda y Goñi -// The Z80 CPU Emulator is the heart of this driver and in all ways, is compatible with -// the original Z80. -// Copyright: (c) 2019-2022 Philip Smart -// (c) 1999-2022 Manuel Sainz de Baranda y Goñi -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80io.h" -#include "z80menu.h" -#include "z80driver.h" - -#include -#include -#include - -/* Meta Information */ -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Philip D Smart"); -MODULE_DESCRIPTION("Z80 CPU Emulator and Hardware Interface Driver"); - -/* Global variables for the threads */ -static struct task_struct *kthread_z80; -static int threadId_z80 = 1; - -// Device class and major numbers. -static struct class *class; -static struct device *device; -static int major; - -// CPU Instance. -static Z80 Z80CPU; - -// Z80 Control data. -static t_Z80Ctrl *Z80Ctrl = NULL; - -// Runtime control of the CPU. As the CPU runs in a detached thread on core 1, the cpu needs to be suspended before any external -// operations can take place. This is achieved with the runtime mutex. -enum Z80_RUN_STATES Z80RunMode; -static struct mutex Z80RunModeMutex; -static DEFINE_MUTEX(Z80DRV_MUTEX); - - -//------------------------------------------------------------------------------------------------------------------------------- -// -// Z80 CPU Kernel Logic. -// -// THe Z80 CPU is initialised and set running, processing instructions either from the underlying host hardware or internal -// memory. The configuration and flow is controlled via the Z80Ctrl structure which is User Space accessible. -// -//------------------------------------------------------------------------------------------------------------------------------- - -// Method to read a byte from physical hardware or internal virtual memory/devices. -// The page table indicates the source and the read is processed accordingly. -static zuint8 z80_read(void *context, zuint16 address) -{ - // Locals. - // - zuint8 data; -// uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - Z_UNUSED(context) - - // Only read if the address is in physical RAM. - if(isPhysical(address)) - { - // Commence cycle to retrieve the data from Real RAM. - // Optimise SPI according to last address sent to CPLD. -// if(addrDiff >=0 && addrDiff < 8) -// { -// SPI_SEND8((CPLD_CMD_READ_ADDR + addrDiff)); -// Z80Ctrl->z80PrevAddr += addrDiff; -// } else -// { - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = address; -// } - while(CPLD_READY() == 0); - data = READ_CPLD_DATA_IN(); - - // Pause until the Last T-State is detected. - // while(CPLD_LAST_TSTATE() == 0); - } - else if(isVirtualHW(address)) - { - // Virtual Hardware - call the handler. - switch(realAddress(address)) - { - default: - break; - } - } - else if(isVirtualMemory(address)) - { - // Retrieve data from virtual memory. - data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); -// if(address >= 0xCF01 && address < 0xCF0A) -// pr_info("Read:0x%x, 0x%x, 0x%x", address, data, isVirtualROM(address) ? getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK) + Z80_VIRTUAL_RAM_SIZE : getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK)); - } - - // Keyport data? Store. - if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 8 && (data & 0x41) == 0) - { - Z80Ctrl->keyportShiftCtrl = 0x01; - } else - if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 0 && (data & 0x80) == 0) - { - Z80Ctrl->keyportHotKey = 0x01; - } - return(data); -} - -// Method to write a byte to physical hardware or internal virtual memory or devices. -// The page table indicates the target and the write is processed accordingly. -static void z80_write(void *context, zuint16 address, zuint8 data) -{ - // Locals. - // uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - Z_UNUSED(context) - - // To detect Hotkey presses, we need to store the keboard strobe data and on keydata read. - if(isHW(address) && address == 0xE000) - { - Z80Ctrl->keyportStrobe = data; - } - - // Write to physical host? - if(isPhysical(address)) - { - // Commence cycle to write the data to real RAM. - // Optimise SPI according to last address sent to CPLD. -// if(addrDiff >=0 && addrDiff < 8) -// { -//if(address >= 0xD010 && address < 0xD017) { data = 0x41; } -// SPI_SEND16((data << 8) | (CPLD_CMD_WRITE_ADDR + addrDiff)); -// Z80Ctrl->z80PrevAddr += addrDiff; -// } else -// { - SPI_SEND32((uint32_t)address << 16 | data << 8 | CPLD_CMD_WRITE_ADDR); - Z80Ctrl->z80PrevAddr = address; -// } - - // Write-thru to virtual memory if we update real memory. - // if(isPhysicalRAM(address)) - // writeVirtualRAM(address, data); - -// if(address >= 0xD000 && address < 0xD018) -// pr_info("Write:0x%x, 0x%x, 0x%x", address, data, isVirtualROM(address) ? getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK) + Z80_VIRTUAL_RAM_SIZE : getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK)); - - // Pause until the Last T-State is detected. - // while(CPLD_LAST_TSTATE() == 0); - } - else if(isVirtualHW(address)) - { - // Virtual Hardware - call the handler. - switch(realAddress(address)) - { - default: - break; - } - } - else if(isVirtualRAM(address)) - { - // Update virtual memory. - writeVirtualRAM(address, data); -//pr_info("Write:0x%x, 0x%x, 0x%x", address, data, isVirtualROM(address) ? getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK) + Z80_VIRTUAL_RAM_SIZE : getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK)); - } - // Cannot write to virtual ROM so no logic. -} - -// Primary Opcode fetch method. This method is called each time a single or multi-byte opcode is -// encountered. Opcode data is retrieved via the z80_fetch method. -// -// Depending on the address and the configured page map, the opcode is fetched from hardware -// or internal virtual memory. As this method is the primary timing method for Z80 instructions -// (read/write methods dont affect the timing so much as long as they operate in less than the read/write -// cycle of an original Z80). -// Initially the timing on the virtual memory is set by a governor delay but this will be updated to a more -// precise M/T-State cycle per instruction type delay. -static zuint8 z80_fetch_opcode(void *context, zuint16 address) -{ - // Locals. - zuint8 opcode = 0x00; -// uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - volatile uint32_t idx; // Leave as volatile otherwise optimiser will optimise out the delay code. - Z_UNUSED(context) - - // Normally only opcode fetches occur in RAM but allow any physical address as it could be a Z80 programming trick. - if(isPhysical(address)) - { - // Commence cycle to fetch the opcode from potentially Real RAM albeit it could be any physical hardware. - // Optimise SPI according to last address sent to CPLD. -// if(addrDiff >=0 && addrDiff < 8) -// { -// SPI_SEND8((CPLD_CMD_FETCH_ADDR + addrDiff)); -// Z80Ctrl->z80PrevAddr += addrDiff; -// } else -// { - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_FETCH_ADDR); - Z80Ctrl->z80PrevAddr = address; -// } - while(CPLD_READY() == 0); - opcode = READ_CPLD_DATA_IN(); - - // Pause until the Last T-State is detected. -// while(CPLD_LAST_TSTATE() == 0); - } else - // Virtual fetches only occur in memory as we are not emulating original hardware. - if(isVirtualMemory(address)) - { - // for(idx=0; idx < Z80Ctrl->cpuGovernorDelay; idx++); - // for(idx=0; idx < 238; idx++); - // 243 loaded - - // Retrieve data from virtual memory. - // opcode = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); - - if(isVirtualROM(address)) - { - opcode = readVirtualROM(address); - for(idx=0; idx < Z80Ctrl->cpuGovernorDelayROM; idx++); - } else - { - opcode = readVirtualRAM(address); - for(idx=0; idx < Z80Ctrl->cpuGovernorDelayRAM; idx++); - } - } -//if(opcode == 0xED && address > 0x270) -// pr_info("Fetch:0x%x, 0x%x, 0x%x", address, opcode, isVirtualROM(address) ? getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK) + Z80_VIRTUAL_RAM_SIZE : getPageAddr(address, MEMORY_TYPE_VIRTUAL_MASK)); - return(opcode); -} - -// Method similar to z80_read, kept seperate to avoid additional what-if logic and doesnt require virtual hardware logic. -// -static zuint8 z80_fetch(void *context, zuint16 address) -{ - // Locals. - // - zuint8 data = 0x00; -// uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - Z_UNUSED(context) - - // Normally only opcode fetches occur in RAM but allow any physical address as it could be a Z80 programming trick. - if(isPhysical(address)) - { - // Commence cycle to retrieve the data from Real RAM. - // Optimise SPI according to last address sent to CPLD. -// if(addrDiff >=0 && addrDiff < 8) -// { -// SPI_SEND8((CPLD_CMD_READ_ADDR + addrDiff)); -// Z80Ctrl->z80PrevAddr += addrDiff; -// } else -// { - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = address; -// } - while(CPLD_READY() == 0); - data = READ_CPLD_DATA_IN(); - - // Pause until the Last T-State is detected. -// while(CPLD_LAST_TSTATE() == 0); - } else - if(isVirtualMemory(address)) - { - // Retrieve data from virtual memory. - data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); - } -//pr_info("(0x%x)", data); - - return(data); -} - -// Method to perform a Z80 input operation. This normally goes to hardware and the CPLD executes the required cycle. -// Some ports are dedicated virtual ports providing virtual services to the host computer/application. These are intercepted -// and processed in this driver. -static zuint8 z80_in(void *context, zuint16 port) -{ - // Locals. - zuint8 value; -// uint16_t portDiff = (uint16_t)port - Z80Ctrl->z80PrevPort; - Z_UNUSED(context) - - // Physical port go direct to hardware to retrieve value. - if(isPhysicalIO(port)) - { - // Commence cycle to retrieve the value from the I/O port. Port contains the 16bit BC value. - // Optimise SPI according to last port sent to CPLD. -// if(portDiff >=0 && portDiff < 8) -// { -// SPI_SEND8((CPLD_CMD_READIO_ADDR + portDiff)); -// Z80Ctrl->z80PrevPort += portDiff; -// } else -// { - SPI_SEND32((uint32_t)port << 16 | CPLD_CMD_READIO_ADDR); - Z80Ctrl->z80PrevPort = port; -// } - - // Whilst waiting for the CPLD, we now determine if this is a memory management port and update the memory page if required. - switch(port & 0x00FF) - { - // Port is not a memory management port. - default: - break; - } - - // Finally ensure the data from the port is ready and retrieve it. - while(CPLD_READY() == 0); - value = READ_CPLD_DATA_IN(); - } else - // Virtual I/O Port. - { - // Virtual I/O - call the handler. - switch(realPort(port)) - { - default: - value = 0x00; - break; - } - } -// if((port&0x00ff) == 0xE8) -// pr_info("z80_in:0x%x, 0x%x\n", port, value); - return(value); -} - -// Method to perform a Z80 output operation. This normally goes to hardware and the CPLD executes the required cycle. -// Some ports are dedicated virtual ports providing virtual services to the host computer/application. These are intercepted -// and processed in this driver. -// There are also ports which are both hardware and need mirroring in software. These ports, typically memory mapping ports. -// when activated in the hardware need to be mirrored in the page table so correct virtual memory is used when addressed. -static void z80_out(void *context, zuint16 port, zuint8 value) -{ - // Locals. - uint32_t idx; -// uint16_t portDiff = (uint16_t)port - Z80Ctrl->z80PrevPort; - Z_UNUSED(context) - -//if((port&0xff) == 0xe8) -//{ -// value |= 0xC0; -//} -//udelay(10); - // Physical port go direct to hardware to retrieve value. - if(isPhysicalIO(port)) - { - // Commence cycle to write the value to the I/O port. Port contains the 16bit BC value. - // Optimise SPI according to last port sent to CPLD. -// if(portDiff >=0 && portDiff < 8) -// { -// SPI_SEND16((value << 8) | (CPLD_CMD_WRITEIO_ADDR + portDiff)); -// Z80Ctrl->z80PrevPort += portDiff; -// if((port&0x00ff) == 0xE8) -//pr_info("OUT:0x%x(%x)=0x%x\n", port, portDiff, value); -// } else -// { - SPI_SEND32((uint32_t)port << 16 | value << 8 | CPLD_CMD_WRITEIO_ADDR); - Z80Ctrl->z80PrevPort = port; -// if((port&0x00ff) == 0xE8) -// { -// SPI_SEND32((uint32_t)port << 16 | value << 8 | CPLD_CMD_WRITEIO_ADDR); -// udelay(10); -// SPI_SEND32((uint32_t)port << 16 | value << 8 | CPLD_CMD_WRITEIO_ADDR); -// udelay(10); -// SPI_SEND32((uint32_t)port << 16 | value << 8 | CPLD_CMD_WRITEIO_ADDR); -// udelay(10); -// SPI_SEND32((uint32_t)port << 16 | value << 8 | CPLD_CMD_WRITEIO_ADDR); -// udelay(10); -//pr_info("OUT:0x%x=0x%x\n", port, value); -// } -// } - -//udelay(10); - // Determine if this is a memory management port and update the memory page if required. - switch(port & 0x00FF) - { -#if(TARGET_HOST_MZ2000 == 1) - case IO_ADDR_E0: - break; - - case IO_ADDR_E1: - break; - - case IO_ADDR_E2: - break; - - case IO_ADDR_E3: - // Program control register. - if(value & 0x80) - { - } else - { - switch((value >> 1) & 0x07) - { - // NST toggle. - case 1: - // NST pages in all RAM and resets cpu. - if(value & 0x01) - { - Z80Ctrl->lowMemorySwap = 0; - for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(Z80Ctrl->defaultPageMode == USE_PHYSICAL_RAM) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - } - z80_instant_reset(&Z80CPU); - } - break; - - default: - break; - } - } - break; - - case IO_ADDR_E8: - // NEED FLAG TO SET THIS WHEN CALLED WITH NON MEMORY SWITCH BYTE - if(isPhysical(0xD000) && (value & 0x80) == 0) - { - for(idx=0xC000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(Z80Ctrl->defaultPageMode == USE_PHYSICAL_RAM) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (Z80Ctrl->lowMemorySwap ? idx - 0x8000 : idx)); - } - } - } else - if(value & 0x80) - { - if(value & 0x40) - { - setMemoryType(0xD000/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, 0xD000); - } else - { - for(idx=0xC000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - } - } - break; -#endif - -#if(TARGET_HOST_MZ700 == 1) - // Enable lower 4K block as DRAM - case IO_ADDR_E0: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - break; - - // Enable upper 12K block, including Video/Memory Mapped peripherals area, as DRAM. - case IO_ADDR_E1: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - // MZ-700 mode we only work in first 64K block. - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - } - break; - - // Enable Monitor ROM in lower 4K block - case IO_ADDR_E2: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - break; - - // Enable Video RAM and Memory mapped peripherals in upper 12K block. - case IO_ADDR_E3: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Reset to power on condition memory map. - case IO_ADDR_E4: - // Lower 4K set to Monitor ROM. - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - if(!Z80Ctrl->inhibitMode) - { - // Upper 12K to hardware. - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Inhibit. Backup current page data in region 0xD000-0xFFFF and inhibit it. - case IO_ADDR_E5: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - backupMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_INHIBIT, idx); - } - Z80Ctrl->inhibitMode = 1; - break; - - // Restore D000-FFFF to its original state. - case IO_ADDR_E6: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - restoreMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - } - Z80Ctrl->inhibitMode = 0; - break; -#endif - - // Port is not a memory management port. - default: - break; - } - } else - if(isVirtualIO(port)) - { - // MZ700 memory mode switch. - // - // MZ-700 - // |0000:0FFF|1000:CFFF|D000:FFFF - // ------------------------------ - // OUT 0xE0 = |DRAM | | - // OUT 0xE1 = | | |DRAM - // OUT 0xE2 = |MONITOR | | - // OUT 0xE3 = | | |Memory Mapped I/O - // OUT 0xE4 = |MONITOR |DRAM |Memory Mapped I/O - // OUT 0xE5 = | | |Inhibit - // OUT 0xE6 = | | | - // - // = Return to the state prior to the complimentary command being invoked. - - // Determine if this is a memory management port and update the memory page if required. - switch(port & 0x00FF) - { -#if(TARGET_HOST_MZ700 == 1) - // Enable lower 4K block as DRAM - case IO_ADDR_E0: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - break; - - // Enable upper 12K block, including Video/Memory Mapped peripherals area, as DRAM. - case IO_ADDR_E1: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - // MZ-700 mode we only work in first 64K block. - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - } - break; - - // Enable MOnitor ROM in lower 4K block - case IO_ADDR_E2: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - break; - - // Enable Video RAM and Memory mapped peripherals in upper 12K block. - case IO_ADDR_E3: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Reset to power on condition memory map. - case IO_ADDR_E4: - // Lower 4K set to Monitor ROM. - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - if(!Z80Ctrl->inhibitMode) - { - // Upper 12K to hardware. - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Inhibit. Backup current page data in region 0xD000-0xFFFF and inhibit it. - case IO_ADDR_E5: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - backupMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_INHIBIT, idx); - } - Z80Ctrl->inhibitMode = 1; - break; - - // Restore D000-FFFF to its original state. - case IO_ADDR_E6: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - restoreMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - } - Z80Ctrl->inhibitMode = 0; - break; -#endif - - // Port is not a memory management port. - default: - break; - } - } else - { - // Virtual I/O - call the handler. - switch(realPort(port)) - { - default: - break; - } - } -//pr_info("z80_out:0x%x, 0x%x\n", port, value); -} - -// NOP - No Operation method. This instruction is used for timing, padding out an application or during -// HALT cycles to ensure Refresh occurs. -// If the address is configured as hardware (via the page table) then a refresh cycle is requested otherwise -// nothing to be done. -static zuint8 z80_nop(void *context, zuint16 address) -{ - // Locals. - Z_UNUSED(context) - - if(isPhysical(address)) - { - // If autorefresh is not enabled, send a single refresh request. - if(Z80Ctrl->refreshDRAM == 0) - SPI_SEND8(CPLD_CMD_REFRESH); -pr_info("NOP"); - } - return 0x00; -} - -// HALT - CPU executes a HALT instruction which results in the HALT line going active low and then it enters -// a state executing NOP instructions to ensure DRAM refresh until a reset or INT event. -static void z80_halt(void *context, zboolean state) -{ - // Locals. - Z_UNUSED(context) Z_UNUSED(state) - - // Inform CPLD of halt state. - printk("z80_halt\n"); - SPI_SEND8(CPLD_CMD_HALT); - Z80CPU.cycles = Z80_MAXIMUM_CYCLES; -} - -// Methods below are not yet implemented, Work In Progress! -static zuint8 z80_context(void *context, zuint16 address) -{ - Z_UNUSED(context) - printk("z80_context\n"); - return 0x00; -} -static zuint8 z80_nmia(void *context, zuint16 address) -{ - Z_UNUSED(context) - printk("z80_nmia\n"); - return 0x00; -} -static zuint8 z80_inta(void *context, zuint16 address) -{ - Z_UNUSED(context) - printk("z80_inta\n"); - return 0x00; -} -static zuint8 z80_intFetch(void *context, zuint16 address) -{ - Z_UNUSED(context) - printk("z80_int_fetch\n"); - return 0x00; -} -static void z80_ldia(void *context) -{ - Z_UNUSED(context) - printk("z80_ldia\n"); -} -static void z80_ldra(void *context) -{ - Z_UNUSED(context) - printk("z80_ldra\n"); -} -static void z80_reti(void *context) -{ - Z_UNUSED(context) - printk("z80_reti\n"); -} -static void z80_retn(void *context) -{ - Z_UNUSED(context) - printk("z80_retn\n"); -} -static zuint8 z80_illegal(void *context, zuint8 opcode) -{ - Z_UNUSED(context) - printk("z80_illegal\n"); - return 0x00; -} - -// Z80 CPU Emulation Thread -// ------------------------ -// This is a kernel thread, bound to CPU 1 with IRQ's disabled. -// The Z80 is controlled by a mutex protected variable to define run, stop, pause and terminate modes. -int thread_z80(void * thread_nr) -{ - // Locals. - uint8_t canRun = 0; - int t_nr = *(int *) thread_nr; - //struct sched_param param = {.sched_priority = 99}; - spinlock_t spinLock; - unsigned long flags; - - // Initialise spinlock and disable IRQ's. We should be the only process running on core 1. - spin_lock_init(&spinLock); - spin_lock_irqsave(&spinLock, flags); - - // Assign this emulation to high priority realtime scheduling. Also the task will be assigned to an isolated CPU. - //sched_setscheduler(current, SCHED_RR, ¶m); - - // Run the CPU forever or until a stop occurs. - while(!kthread_should_stop()) - { - // Run the Z80 emulation if enabled. - if(canRun) z80_run(&Z80CPU, 100); - - // Reset pressed? - if(CPLD_RESET()) - { - z80_instant_reset(&Z80CPU); - setupMemory(Z80Ctrl->defaultPageMode); - - // Wait for release before restarting CPU. - while(CPLD_RESET()); - } else - { - // Update state to indicate request has been actioned. - mutex_lock(&Z80RunModeMutex); - if(Z80RunMode == Z80_STOP) Z80RunMode = Z80_STOPPED; - if(Z80RunMode == Z80_PAUSE) Z80RunMode = Z80_PAUSED; - if(Z80RunMode == Z80_CONTINUE) Z80RunMode = Z80_RUNNING; - if(Z80RunMode == Z80_RUNNING) canRun=1; else canRun=0; - mutex_unlock(&Z80RunModeMutex); - - // Hotkey pressed? Bring up user menu. - if(Z80Ctrl->keyportShiftCtrl && Z80Ctrl->keyportHotKey) - { - z80menu(); - Z80Ctrl->keyportShiftCtrl = 0; - Z80Ctrl->keyportHotKey = 0; - } - } - } - - // Release spinlock as we are unloading driver. - spin_unlock_irqrestore(&spinLock, flags); - printk("kthread - Z80 Thread %d finished execution!\n", t_nr); - return 0; -} - - -//------------------------------------------------------------------------------------------------------------------------------- -// -// User space driver access. -// -//------------------------------------------------------------------------------------------------------------------------------- - - -// Device close. -// When a user space application terminates or closes the z80drv device driver, this function is called -// to close any open connections, memory and variables required to handle the user space application -// requests. -static int z80drv_release(struct inode *inodep, struct file *filep) -{ - // Locals. - - mutex_unlock(&Z80DRV_MUTEX); - //pr_info("z80drv: Device successfully closed\n"); - - return(0); -} - -// Device open. -// When a user space application open's the z80drv device driver, this function is called -// to initialise and allocate any required memory or devices prior to servicing requests from the -// user space application. -static int z80drv_open(struct inode *inodep, struct file *filep) -{ - // Locals. - int ret = 0; - - if(!mutex_trylock(&Z80DRV_MUTEX)) - { - pr_alert("z80drv: device busy!\n"); - ret = -EBUSY; - goto out; - } - - //pr_info("z80drv: Device opened\n"); - -out: - return(ret); -} - -// Map shared memory. -// The z80drv allocates on the stack a chunk of memory and control variables which is used to control the Z80 Emulation state -// and provide it with internal 'virtual memory'. This virtual memory is either used as the core Z80 memory or as banked extensions -// to the host DRAM. -// The user space application is able to bind with the shared memory to perform tasks such as load/save of applications. -static int z80drv_mmap(struct file *filp, struct vm_area_struct *vma) -{ - // Locals. - int ret = 0; - struct page *page = NULL; - unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start); - - // Make sure requested size is within range of the Z80 control structure. The kernel may page align it so the size may be bigger than - // the structure. - if(size < sizeof(t_Z80Ctrl) || size > sizeof(t_Z80Ctrl)*2) - { - ret = -EINVAL; - goto out; - } - - // Map the memory and exit. - page = virt_to_page((unsigned long)Z80Ctrl + (vma->vm_pgoff << PAGE_SHIFT)); - ret = remap_pfn_range(vma, vma->vm_start, page_to_pfn(page), size, vma->vm_page_prot); - if (ret != 0) - { - goto out; - } - -out: - return ret; -} - -// Device read. -// This method allows an application which opens the z80drv driver to read data in a stream. It is here for -// possible future use. -static ssize_t z80drv_read(struct file *filep, char *buffer, size_t len, loff_t *offset) -{ - // Locals. - int ret; - - if (len > Z80_VIRTUAL_RAM_SIZE) - { - pr_info("read overflow!\n"); - ret = -EFAULT; - goto out; - } - - if (copy_to_user(buffer, Z80Ctrl, len) == 0) - { - pr_info("z80drv: copy %u char to the user\n", len); - ret = len; - } else - { - ret = -EFAULT; - } - -out: - return ret; -} - -// Device write. -// This method allows an application which opens the z80drv driver to write stream data. It is here for -// possible future use. -static ssize_t z80drv_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) -{ - // Locals. - int ret; - - if (copy_from_user(Z80Ctrl, buffer, len)) - { - pr_err("z80drv: write fault!\n"); - ret = -EFAULT; - goto out; - } - pr_info("z80drv: copy %d char from the user\n", len); - ret = len; - -out: - return ret; -} - -// Function to dump out a given section of the physical host memory. -// -int memoryDump(uint32_t memaddr, uint32_t memsize, uint32_t dispaddr, uint8_t dispwidth) -{ - uint8_t displayWidth = dispwidth; - uint32_t pnt = memaddr; - uint32_t endAddr = memaddr + memsize; - uint32_t addr = dispaddr; - uint8_t data; - uint32_t i = 0; - int result = -1; - char c = 0; - - // If not set, calculate output line width according to connected display width. - // - if(displayWidth == 0) - { - switch(MAX_SCREEN_WIDTH) - { - case 40: - displayWidth = 8; - break; - case 80: - displayWidth = 16; - break; - default: - displayWidth = 32; - break; - } - } - - while (1) - { - printk(KERN_INFO "%08X", addr); // print address - printk(KERN_CONT ": "); - - // print hexadecimal data - for (i=0; i < displayWidth; ) - { - if(pnt+i < endAddr) - { - SPI_SEND32((uint16_t)(pnt+i) << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = pnt+i; - while(CPLD_READY() == 0); - data = READ_CPLD_DATA_IN(); - printk(KERN_CONT "%02X", data); - } - else - printk(KERN_CONT " "); - i++; - - printk(KERN_CONT " "); - } - - // print ascii data - printk(KERN_CONT " |"); - - // print single ascii char - for (i=0; i < displayWidth; i++) - { - SPI_SEND32((uint16_t)(pnt+i) << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = pnt+i; - while(CPLD_READY() == 0); - c = (char)READ_CPLD_DATA_IN(); - if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~')) - printk(KERN_CONT "%c", (char)c); - else - printk(KERN_CONT " "); - } - - printk(KERN_CONT "|\n"); - - // Move on one row. - pnt += displayWidth; - addr += displayWidth; - - // End of buffer, exit the loop. - if(pnt >= (memaddr + memsize)) - { - break; - } - } - - return(result); -} - -// Method to setup a default memory/IO profile. This profile will be changed by the host processing and also can be tweaked -// by the z80ctrl application. -// -void setupMemory(enum Z80_MEMORY_PROFILE mode) -{ - // Locals. - uint32_t idx; - - if(mode == USE_PHYSICAL_RAM) - { - // Initialise the page pointers and memory to use physical RAM. - for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x8000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - else //if(idx >= 0x8000 && idx < 0xD000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - - // Video RAM labelled as HW as we dont want to cache it. - //else if(idx >= 0xD000 && idx < 0xE000) - // { - // setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - //} else - // { - // setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - // } - } - for(idx=0x0000; idx < 0x10000; idx++) - { - Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; - } - // Cancel refresh as using physical RAM for program automatically refreshes DRAM. - Z80Ctrl->refreshDRAM = 0; - } - else if(mode == USE_VIRTUAL_RAM) - { -#if(TARGET_HOST_MZ2000 == 1) - // Initialise the page pointers and memory to use virtual RAM. - // MZ-2000 comes up in IPL mode where lower 32K is ROM and upper 32K is RAM remapped from 0x0000. - for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x8000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (Z80Ctrl->lowMemorySwap ? idx - 0x8000 : idx)); - } - } - for(idx=0x0000; idx < 0x10000; idx++) - { - Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; - } - // Enable refresh as using virtual RAM stops refresh of host DRAM. - Z80Ctrl->refreshDRAM = 1; - pr_info("Im here\n"); - } -#endif - - // Enable autorefresh if refreshDRAM is set. - SPI_SEND8(Z80Ctrl->refreshDRAM == 1 ? CPLD_CMD_SET_AUTO_REFRESH : CPLD_CMD_CLEAR_AUTO_REFRESH); - - // Inhibit mode disabled. - Z80Ctrl->inhibitMode = 0; - return; -} - -// IOCTL Method -// This method allows User Space application to control the Z80 CPU and internal functionality of the -// device driver. This is the preferred control method along with the shared memory segment for the driver. -static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) -{ - // Locals. - struct ioctlCmd ioctlCmd; - uint16_t idx; - uint32_t tmp[2]; - enum Z80_RUN_STATES currentRunMode; - enum Z80_RUN_STATES nextRunMode; - - // Get current running mode so any operations on the Z80 return it to original mode unless action overrides it. - mutex_lock(&Z80RunModeMutex); currentRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - - switch(cmd) - { - // Basic commands. - case IOCTL_CMD_SEND: - if(copy_from_user(&ioctlCmd, (int32_t *)arg, sizeof(ioctlCmd))) - printk("IOCTL - Couldnt retrieve command!\n"); - else - { - //printk("IOCTL - Command (%08x)\n", ioctlCmd.cmd); - switch(ioctlCmd.cmd) - { - // Command to stop the Z80 CPU and power off. - case IOCTL_CMD_Z80_STOP: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - z80_power(&Z80CPU, FALSE); - Z80_PC(Z80CPU) = 0; - printk("Z80 stopped.\n"); - break; - - // Command to power on and start the Z80 CPU. - case IOCTL_CMD_Z80_START: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_RUNNING; mutex_unlock(&Z80RunModeMutex); - - z80_power(&Z80CPU, TRUE); - printk("Z80 started.\n"); - break; - - // Command to pause the Z80. - case IOCTL_CMD_Z80_PAUSE: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_PAUSE; mutex_unlock(&Z80RunModeMutex); - printk("Z80 paused.\n"); - break; - - // Command to release a paused Z80. - case IOCTL_CMD_Z80_CONTINUE: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_CONTINUE; mutex_unlock(&Z80RunModeMutex); - printk("Z80 running.\n"); - break; - - // Command to perform a CPU reset. - case IOCTL_CMD_Z80_RESET: - // Stop the CPU prior to reset. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - z80_instant_reset(&Z80CPU); - setupMemory(Z80Ctrl->defaultPageMode); - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Reset.\n"); - break; - - // Command to setup the page table to use host memory and physical hardware. - case IOCTL_CMD_USE_HOST_RAM: - // Stop the CPU prior to memory reconfiguration. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - Z80Ctrl->defaultPageMode = USE_PHYSICAL_RAM; - setupMemory(Z80Ctrl->defaultPageMode); - z80_instant_reset(&Z80CPU); - - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Set to use Host Memory.\n"); - break; - - // Command to setup the page table to use virtual memory, only physical hardware is accessed on the host. - case IOCTL_CMD_USE_VIRTUAL_RAM: - // Stop the CPU prior to memory reconfiguration. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - Z80Ctrl->defaultPageMode = USE_VIRTUAL_RAM; - setupMemory(Z80Ctrl->defaultPageMode); - z80_instant_reset(&Z80CPU); - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Set to use Virtual Memory.\n"); - break; - - // Command to synchronise virtual memory to host DRAM. - case IOCTL_CMD_SYNC_TO_HOST_RAM: - // Stop the CPU prior to memory sync. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Copy virtual memory to host DRAM. - for(idx=0x1000; idx < 0xD000; idx++) - { - SPI_SEND32((uint32_t)idx << 16 | Z80Ctrl->memory[idx] << 8 | CPLD_CMD_WRITE_ADDR); - } - - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Host DRAM syncd with Virtual Memory.\n"); - break; - - // Command to dump out host memory. - case IOCTL_CMD_DUMP_MEMORY: - // Need to suspend the Z80 otherwise we will get memory clashes. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_PAUSE; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_PAUSE); - - // Dump out the physical memory address. - memoryDump(ioctlCmd.addr.start, ioctlCmd.addr.end - ioctlCmd.addr.start, ioctlCmd.addr.start, 0); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to set the governor delay to approximate real Z80 cpu frequencies when running in virtual memory. - case IOCTL_CMD_Z80_CPU_FREQ: - switch(ioctlCmd.speed.speedMultiplier) - { - case 2: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_7MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_7MHZ; - break; - - case 4: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_14MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_14MHZ; - break; - - case 8: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_28MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_28MHZ; - break; - - case 16: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_56MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_56MHZ; - break; - - case 32: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_112MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_112MHZ; - break; - - case 64: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_224MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_224MHZ; - break; - - case 128: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_448MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_448MHZ; - break; - - case 1: - default: - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_3_54MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_3_54MHZ; - break; - } - break; - - // Command to set the Z80 CPU Program Counter value. - case IOCTL_CMD_SETPC: - // Stop the CPU prior to PC change. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - Z80_PC(Z80CPU) = ioctlCmd.z80.pc; - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Set PC to %04x\n", ioctlCmd.z80.pc); - break; - - // Method to send adhoc commands to the CPLD, ie for switching active display etc. - case IOCTL_CMD_CPLD_CMD: - // Stop the CPU prior to sending a direct command to the CPLD. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Send the command, small delay then send NOP to retrieve the response. - z80io_SPI_Send32(ioctlCmd.cpld.cmd, &tmp[0]); - udelay(50); - z80io_SPI_Send32(0x00000000, &tmp[0]); - z80io_SPI_Send32(0x00000000, &tmp[1]); - pr_info("CPLD TX:%08x, RX:%08x,%08x\n", ioctlCmd.cpld.cmd, tmp[0], tmp[1]); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to run a series of SOM to CPLD SPI tests. - case IOCTL_CMD_SPI_TEST: - // Stop the CPU prior to SPI testing. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Perform SPI Tests. - z80io_SPI_Test(); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to run a series of SOM to CPLD Parallel Bus tests. - case IOCTL_CMD_PRL_TEST: - // Stop the CPU prior to SPI testing. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Perform Parallel Bus tests. - z80io_PRL_Test(); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to run a series of Z80 host memory tests to assess the performance of the SOM->CPLD interface. - case IOCTL_CMD_Z80_MEMTEST: - // Stop the CPU prior to Host memory testing. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Perform host memory tests. - z80io_Z80_TestMemory(); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - default: - break; - } - } - break; - - default: - printk("IOCTL - Unhandled Command (%08x)\n", ioctlCmd.cmd); - break; - - } - return 0; -} - -// Structure to declare public API methods. -// Standard Linux device driver structure to declare accessible methods within the driver. -static const struct file_operations z80drv_fops = { - .open = z80drv_open, - .read = z80drv_read, - .write = z80drv_write, - .release = z80drv_release, - .mmap = z80drv_mmap, - .unlocked_ioctl = z80drv_ioctl, - .owner = THIS_MODULE, -}; - -// Initialisation. -// This is the entry point into the device driver when loaded into the kernel. -// The method intialises any required hardware (ie. GPIO's, SPI etc), memory and the Z80 -// Emulation. It also allocates the Major and Minor device numbers and sets up the -// device in /dev. -static int __init ModuleInit(void) -{ - // Locals. - int idx; - int ret = 0; - - // Setup the Z80 handlers. - Z80CPU.context = z80_context; - Z80CPU.fetch = z80_fetch; - Z80CPU.fetch_opcode = z80_fetch_opcode; - Z80CPU.read = z80_read; - Z80CPU.write = z80_write; - Z80CPU.nop = z80_nop; - Z80CPU.in = z80_in; - Z80CPU.out = z80_out; - Z80CPU.halt = z80_halt; - Z80CPU.nmia = z80_nmia; - Z80CPU.inta = z80_inta; - Z80CPU.int_fetch = z80_intFetch; - Z80CPU.ld_i_a = z80_ldia; - Z80CPU.ld_r_a = z80_ldra; - Z80CPU.reti = z80_reti; - Z80CPU.retn = z80_retn; - Z80CPU.illegal = z80_illegal; - - mutex_init(&Z80DRV_MUTEX); - - // Get device Major number. - major = register_chrdev(0, DEVICE_NAME, &z80drv_fops); - if (major < 0) { - pr_info("z80drv: fail to register major number!"); - ret = major; - goto initExit; - } - - class = class_create(THIS_MODULE, CLASS_NAME); - if (IS_ERR(class)){ - unregister_chrdev(major, DEVICE_NAME); - pr_info("z80drv: failed to register device class"); - ret = PTR_ERR(class); - goto initExit; - } - - device = device_create(class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); - if (IS_ERR(device)) { - class_destroy(class); - unregister_chrdev(major, DEVICE_NAME); - ret = PTR_ERR(device); - goto initExit; - } - - // Allocate the Z80 memory to be shared between this kernel space driver and the controlling user space application. - Z80Ctrl = (t_Z80Ctrl *)kmalloc(sizeof(t_Z80Ctrl), GFP_KERNEL); - if (Z80Ctrl == NULL) - { - pr_info("z80drv: failed to allocate memory!"); - ret = -ENOMEM; - goto initExit; - } - - // Initialise the hardware to host interface. - z80io_init(); - - // Initialise the virtual RAM from the HOST DRAM. This is to maintain compatibility as some applications (in my experience) have - // bugs, which Im putting down to not initialising variables. The host DRAM is in a pattern of 0x00..0x00, 0xFF..0xFF repeating - // when first powered on. - for(idx=0; idx < Z80_VIRTUAL_RAM_SIZE; idx++) - { -#if(TARGET_HOST_MZ700 == 1) - if(idx >= 0x1000 && idx < 0xD000) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->memory[idx] = z80io_PRL_Read8(1); - } else - { - Z80Ctrl->memory[idx] = 0x00; - } -#endif -#if(TARGET_HOST_MZ2000 == 1) - if(idx >= 0x8000 && idx < 0x10000) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->memory[idx-0x8000] = z80io_PRL_Read8(1); - } else - { - if(idx >= 0x0000 && idx < 0x8000) - Z80Ctrl->memory[idx+0x8000] = 0x00; - else - Z80Ctrl->memory[idx] = 0x00; - } -#endif - } - -#if(TARGET_HOST_MZ700 == 1) - Z80Ctrl->memory[0x1200] = 0x01; - Z80Ctrl->memory[0x1201] = 0x86; - Z80Ctrl->memory[0x1202] = 0xf2; - Z80Ctrl->memory[0x1203] = 0x3e; - Z80Ctrl->memory[0x1204] = 0x15; - Z80Ctrl->memory[0x1205] = 0x3d; - Z80Ctrl->memory[0x1206] = 0x20; - Z80Ctrl->memory[0x1207] = 0xfd; - Z80Ctrl->memory[0x1208] = 0x0b; - Z80Ctrl->memory[0x1209] = 0x78; - Z80Ctrl->memory[0x120a] = 0xb1; - Z80Ctrl->memory[0x120b] = 0x20; - Z80Ctrl->memory[0x120c] = 0xf6; - Z80Ctrl->memory[0x120d] = 0xc3; - Z80Ctrl->memory[0x120e] = 0x00; - Z80Ctrl->memory[0x120f] = 0x00; -#endif - - // Copy the host BIOS into the Virtual ROM. - for(idx=0; idx < Z80_VIRTUAL_ROM_SIZE; idx++) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->memory[Z80_VIRTUAL_RAM_SIZE+idx] = z80io_PRL_Read8(1); - } - -#if(TARGET_HOST_MZ2000 == 1) - Z80Ctrl->lowMemorySwap = 1; -#endif - - // Setup Refresh so that an automatic refresh mode is performed by the CPLD whilst running in Virtual memory. This is necessary as opcode fetches from - // host memory, by the CPLD, normally performs the refresh and when running in virtual memory, these refresh cycles arent performed. - Z80Ctrl->refreshDRAM = 0; - - // Setup the governor delay, it is the delay per opcode fetch to restrict the Z80 CPU to a given speed. - Z80Ctrl->cpuGovernorDelayROM = INSTRUCTION_DELAY_ROM_3_54MHZ; - Z80Ctrl->cpuGovernorDelayRAM = INSTRUCTION_DELAY_RAM_3_54MHZ; - - // Setup the default Page Mode. This is needed if an event such as a reset occurs which needs to return the page and iotable back to default. - Z80Ctrl->defaultPageMode = USE_VIRTUAL_RAM; - - // Setup memory profile to use internal virtual RAM (SOM kernel RAM rather than HOST DRAM). - setupMemory(Z80Ctrl->defaultPageMode); - - // Initialse run control. - mutex_init(&Z80RunModeMutex); - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - - // Setup the address and port history for communicating addresses to the CPLD. Used to shorten the instruction length to increase latency. - Z80Ctrl->z80PrevAddr = 0xFFFF; - Z80Ctrl->z80PrevPort = 0xFFFF; - - // Initialse hotkey detection variables. - Z80Ctrl->keyportStrobe = 0x00; - Z80Ctrl->keyportShiftCtrl = 0x00; - Z80Ctrl->keyportHotKey = 0x00; - - // PC to start and power on the CPU - Z80_PC(Z80CPU) = 0; - z80_power(&Z80CPU, TRUE); - - // Create thread to run the Z80 cpu. - kthread_z80 = kthread_create(thread_z80, &threadId_z80, "kthread_z80"); - if(kthread_z80 != NULL) - { - printk("kthread - Thread Z80 was created, waking...!\n"); - kthread_bind(kthread_z80, 1); - wake_up_process(kthread_z80); - } - else { - printk("kthread - Thread Z80 could not be created!\n"); - ret = -1; - goto initExit; - } - -initExit: - return ret; -} - -// Exit -// This method is called when the device driver is removed from the kernel with the rmmod command. -// It is responsible for closing and freeing all allocated memory, terminating all threads and removing -// the device from the /dev directory. -static void __exit ModuleExit(void) -{ - // Stop the internal threads. - //printk("kthread - Stop Z80 thread\n"); - kthread_stop(kthread_z80); - - // Return the memory used for the Z80 'virtual memory' and control variables. - kfree(Z80Ctrl); - - // Nothing to be done for the hardware. - - // Cleanup and remove the device. - mutex_destroy(&Z80DRV_MUTEX); - device_destroy(class, MKDEV(major, 0)); - class_unregister(class); - class_destroy(class); - unregister_chrdev(major, DEVICE_NAME); - - //pr_info("z80drv: unregistered!\n"); -} - -module_init(ModuleInit); -module_exit(ModuleExit); diff --git a/software/FusionX/src/z80drv/MZ2000/z80driver.h b/software/FusionX/src/z80drv/MZ2000/z80driver.h deleted file mode 100644 index bf622a6da..000000000 --- a/software/FusionX/src/z80drv/MZ2000/z80driver.h +++ /dev/null @@ -1,326 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80driver.h -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 Driver -// This file contains the declarations used in the z80drv device driver. -// -// Credits: Zilog Z80 CPU Emulator v0.2 written by Manuel Sainz de Baranda y Goñi -// The Z80 CPU Emulator is the heart of this driver and in all ways, is compatible with -// the original Z80. -// Copyright: (c) 2019-2022 Philip Smart -// (c) 1999-2022 Manuel Sainz de Baranda y Goñi -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef Z80DRIVER_H -#define Z80DRIVER_H - -// Constants. -#define TARGET_HOST_MZ700 0 -#define TARGET_HOST_MZ2000 1 -#define Z80_VIRTUAL_ROM_SIZE 16384 // Sized to maximum ROM which is the MZ-800 ROM. -#define Z80_VIRTUAL_RAM_SIZE (65536 * 8) // (PAGE_SIZE * 2) // max size mmaped to userspace -#define Z80_VIRTUAL_MEMORY_SIZE Z80_VIRTUAL_RAM_SIZE + Z80_VIRTUAL_ROM_SIZE -#define Z80_MEMORY_PAGE_SIZE 16 -#define MAX_SCREEN_WIDTH 132 -#define DEVICE_NAME "z80drv" -#define CLASS_NAME "mogu" - -// Memory and IO page types. Used to create a memory page which maps type of address space to real address space on host or virtual memory. -#define MEMORY_TYPE_VIRTUAL_MASK 0x00FFFFFF -#define MEMORY_TYPE_REAL_MASK 0x0000FFFF -#define IO_TYPE_MASK 0x0000FFFF -#define MEMORY_TYPE_INHIBIT 0x00000000 -#define MEMORY_TYPE_PHYSICAL_RAM 0x80000000 -#define MEMORY_TYPE_PHYSICAL_ROM 0x40000000 -#define MEMORY_TYPE_PHYSICAL_VRAM 0x20000000 -#define MEMORY_TYPE_PHYSICAL_HW 0x10000000 -#define MEMORY_TYPE_VIRTUAL_RAM 0x08000000 -#define MEMORY_TYPE_VIRTUAL_ROM 0x04000000 -#define MEMORY_TYPE_VIRTUAL_HW 0x02000000 -#define IO_TYPE_PHYSICAL_HW 0x80000000 -#define IO_TYPE_VIRTUAL_HW 0x40000000 - - -// Approximate governor delays to regulate emulated CPU speed. -// MZ-700 -#if(TARGET_HOST_MZ700 == 1) -#define INSTRUCTION_DELAY_ROM_3_54MHZ 253 -#define INSTRUCTION_DELAY_ROM_7MHZ 126 -#define INSTRUCTION_DELAY_ROM_14MHZ 63 -#define INSTRUCTION_DELAY_ROM_28MHZ 32 -#define INSTRUCTION_DELAY_ROM_56MHZ 16 -#define INSTRUCTION_DELAY_ROM_112MHZ 8 -#define INSTRUCTION_DELAY_ROM_224MHZ 4 -#define INSTRUCTION_DELAY_ROM_448MHZ 1 -#define INSTRUCTION_DELAY_RAM_3_54MHZ 253 -#define INSTRUCTION_DELAY_RAM_7MHZ 126 -#define INSTRUCTION_DELAY_RAM_14MHZ 63 -#define INSTRUCTION_DELAY_RAM_28MHZ 32 -#define INSTRUCTION_DELAY_RAM_56MHZ 16 -#define INSTRUCTION_DELAY_RAM_112MHZ 8 -#define INSTRUCTION_DELAY_RAM_224MHZ 4 -#define INSTRUCTION_DELAY_RAM_448MHZ 1 -#endif -// MZ-2000 -#if(TARGET_HOST_MZ2000 == 1) -#define INSTRUCTION_DELAY_ROM_3_54MHZ 243 -#define INSTRUCTION_DELAY_ROM_7MHZ 122 -#define INSTRUCTION_DELAY_ROM_14MHZ 61 -#define INSTRUCTION_DELAY_ROM_28MHZ 30 -#define INSTRUCTION_DELAY_ROM_56MHZ 15 -#define INSTRUCTION_DELAY_ROM_112MHZ 7 -#define INSTRUCTION_DELAY_ROM_224MHZ 3 -#define INSTRUCTION_DELAY_ROM_448MHZ 1 -#define INSTRUCTION_DELAY_RAM_3_54MHZ 218 -#define INSTRUCTION_DELAY_RAM_7MHZ 112 -#define INSTRUCTION_DELAY_RAM_14MHZ 56 -#define INSTRUCTION_DELAY_RAM_28MHZ 28 -#define INSTRUCTION_DELAY_RAM_56MHZ 14 -#define INSTRUCTION_DELAY_RAM_112MHZ 7 -#define INSTRUCTION_DELAY_RAM_224MHZ 3 -#define INSTRUCTION_DELAY_RAM_448MHZ 1 -#endif - -// IOCTL commands. Passed from user space using the IOCTL method to command the driver to perform an action. -#define IOCTL_CMD_Z80_STOP 's' -#define IOCTL_CMD_Z80_START 'S' -#define IOCTL_CMD_Z80_PAUSE 'P' -#define IOCTL_CMD_Z80_RESET 'R' -#define IOCTL_CMD_Z80_CONTINUE 'C' -#define IOCTL_CMD_USE_HOST_RAM 'x' -#define IOCTL_CMD_USE_VIRTUAL_RAM 'X' -#define IOCTL_CMD_DUMP_MEMORY 'M' -#define IOCTL_CMD_Z80_CPU_FREQ 'F' -#define IOCTL_CMD_CPLD_CMD 'z' -#define IOCTL_CMD_SEND _IOW('c', 'c', int32_t *) -#define IOCTL_CMD_SETPC _IOW('p', 'p', int32_t *) -#define IOCTL_CMD_SYNC_TO_HOST_RAM 'V' -#define IOCTL_CMD_SPI_TEST '1' -#define IOCTL_CMD_PRL_TEST '2' -#define IOCTL_CMD_Z80_MEMTEST '3' - - - -// Chip Select map MZ80K-MZ700. -// -// 0000 - 0FFF = CS_ROMni : R/W : MZ80K/A/700 = Monitor ROM or RAM (MZ80A rom swap) -// 1000 - CFFF = CS_RAMni : R/W : MZ80K/A/700 = RAM -// C000 - CFFF = CS_ROMni : R/W : MZ80A = Monitor ROM (MZ80A rom swap) -// D000 - D7FF = CS_VRAMni : R/W : MZ80K/A/700 = VRAM -// D800 - DFFF = CS_VRAMni : R/W : MZ700 = Colour VRAM (MZ700) -// E000 - E003 = CS_8255n : R/W : MZ80K/A/700 = 8255 -// E004 - E007 = CS_8254n : R/W : MZ80K/A/700 = 8254 -// E008 - E00B = CS_LS367n : R/W : MZ80K/A/700 = LS367 -// E00C - E00F = CS_ESWPn : R : MZ80A = Memory Swap (MZ80A) -// E010 - E013 = CS_ESWPn : R : MZ80A = Reset Memory Swap (MZ80A) -// E014 = CS_E5n : R/W : MZ80A/700 = Normal CRT display (in Video Controller) -// E015 = CS_E6n : R/W : MZ80A/700 = Reverse CRT display (in Video Controller) -// E200 - E2FF = : R/W : MZ80A/700 = VRAM roll up/roll down. -// E800 - EFFF = : R/W : MZ80K/A/700 = User ROM socket or DD Eprom (MZ700) -// F000 - F7FF = : R/W : MZ80K/A/700 = Floppy Disk interface. -// F800 - FFFF = : R/W : MZ80K/A/700 = Floppy Disk interface. -// -// Chip Select map MZ800 -// -// FC - FF = CS_PIOn : R/W : MZ800/MZ1500 = Z80 PIO Printer Interface -// F2 = CS_PSG0n : W : MZ800/MZ1500 = Programable Sound Generator, MZ-800 = Mono, MZ-1500 = Left Channel -// F3 = CS_PSG1n : W : MZ1500 = Programable Sound Generator, MZ-1500 = Right Channel -// E9 = CS_PSG(X)n: W : MZ1500 = Simultaneous write to both PSG's. -// F0 - F1 = CS_JOYSTK : R : MZ800 = Joystick 1 and 2 -// CC = CS_GWF : W : MZ800 = CRTC GWF Write format Register -// CD = CS_GRF : W : MZ800 = CRTC GRF Read format Register -// CE = CS_GDMD : W : MZ800 = CRTC GDMD Mode Register -// CF = CS_GCRTC : W : MZ800 = CRTC GCRTC Control Register -// D4 - D7 = CS -// D000 - DFFF - -// MZ700/MZ800 memory mode switch? -// -// MZ-700 MZ-800 -// |0000:0FFF|1000:1FFF|1000:CFFF|C000:CFFF|D000:FFFF |0000:7FFF|1000:1FFF|2000:7FFF|8000:BFFF|C000:CFFF|C000:DFFF|E000:FFFF -// -------------------------------------------------- ---------------------------------------------------------------------- -// OUT 0xE0 = |DRAM | | | | |DRAM | | | | | | -// OUT 0xE1 = | | | | |DRAM | | | | | | |DRAM -// OUT 0xE2 = |MONITOR | | | | |MONITOR | | | | | | -// OUT 0xE3 = | | | | |Memory Mapped I/O | | | | | | |Upper MONITOR ROM -// OUT 0xE4 = |MONITOR | |DRAM | |Memory Mapped I/O |MONITOR |CGROM |DRAM |VRAM | |DRAM |Upper MONITOR ROM -// OUT 0xE5 = | | | | |Inhibit | | | | | | |Inhibit -// OUT 0xE6 = | | | | | | | | | | | | -// IN 0xE0 = | |CGROM* | |VRAM* | | |CGROM | |VRAM | | | -// IN 0xE1 = | |DRAM | |DRAM | | | | |DRAM | | | -// -// = Return to the state prior to the complimentary command being invoked. -// * = MZ-800 host only. - -// Macros to lookup and test to see if a given memory block or IO byte is of a given type. Also macros to read/write to the memory block and IO byte. -#define MEMORY_BLOCK_GRANULARITY 0x800 -#define MEMORY_BLOCK_SLOTS (0x10000 / MEMORY_BLOCK_GRANULARITY) -#define MEMORY_BLOCK_MASK (0x10000 - MEMORY_BLOCK_GRANULARITY) -#define MEMORY_BLOCK_SHIFT 11 -#define getPageData(a) (Z80Ctrl->page[(a & 0xF800) >> MEMORY_BLOCK_SHIFT]) -#define getIOPageData(a) (Z80Ctrl->iopage[(a & 0xFFFF]) -#define getPageType(a, mask) (getPageData(a) & mask) -#define getPageAddr(a, mask) ((getPageData(a) & mask) + (a & (MEMORY_BLOCK_GRANULARITY-1))) -#define getIOPageType(a, mask) (getIOPageData(a) & mask) -#define getIOPageAddr(a, mask) (getIOPageData(a) & mask) -#define realAddress(a) (Z80Ctrl->page[getPageAddr(a, MEMORY_TYPE_REAL_MASK)]) -#define realPort(a) (Z80Ctrl->iopage[a & 0xFFFF] & IO_TYPE_MASK) -#define isPhysicalRAM(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_RAM)) -#define isPhysicalVRAM(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_VRAM)) -#define isPhysicalROM(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_ROM)) -#define isPhysicalMemory(a) (getPageType(a, (MEMORY_TYPE_PHYSICAL_ROM | MEMORY_TYPE_PHYSICAL_RAM | MEMORY_TYPE_PHYSICAL_VRAM))]) -#define isPhysicalHW(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_HW)) -#define isPhysical(a) (getPageType(a, (MEMORY_TYPE_PHYSICAL_HW | MEMORY_TYPE_PHYSICAL_ROM | MEMORY_TYPE_PHYSICAL_RAM | MEMORY_TYPE_PHYSICAL_VRAM))) -#define isPhysicalIO(a) (Z80Ctrl->iopage[a & 0xFFFF] & IO_TYPE_PHYSICAL_HW) -#define isVirtualRAM(a) (getPageType(a, MEMORY_TYPE_VIRTUAL_RAM)) -#define isVirtualROM(a) (getPageType(a, MEMORY_TYPE_VIRTUAL_ROM)) -#define isVirtualMemory(a) (getPageType(a, (MEMORY_TYPE_VIRTUAL_ROM | MEMORY_TYPE_VIRTUAL_RAM))) -#define isVirtualHW(a) (getPageType(a, MEMORY_TYPE_VIRTUAL_HW)) -#define isVirtualIO(a) (Z80Ctrl->iopage[a & 0xFFFF] & IO_TYPE_VIRTUAL_HW) -#define isHW(a) (getPageType(a, (MEMORY_TYPE_PHYSICAL_HW | MEMORY_TYPE_VIRTUAL_HW))) -#define readVirtualRAM(a) (Z80Ctrl->memory[ getPageAddr(a, MEMORY_TYPE_VIRTUAL_MASK) ]) -#define readVirtualROM(a) (Z80Ctrl->memory[ getPageAddr(a, MEMORY_TYPE_VIRTUAL_MASK) + Z80_VIRTUAL_RAM_SIZE ]) -#define writeVirtualRAM(a, d) { Z80Ctrl->memory[ getPageAddr(a, MEMORY_TYPE_VIRTUAL_MASK) ] = d; } -#define setMemoryType(_block_,_type_,_addr_) { Z80Ctrl->page[_block_] = _type_ | _addr_; } -#define backupMemoryType(_block_) { Z80Ctrl->shadowPage[_block_] = Z80Ctrl->page[_block_]; } -#define restoreMemoryType(_block_) { Z80Ctrl->page[_block_] = Z80Ctrl->shadowPage[_block_]; } - -#define IO_ADDR_E0 0xE0 -#define IO_ADDR_E1 0xE1 -#define IO_ADDR_E2 0xE2 -#define IO_ADDR_E3 0xE3 -#define IO_ADDR_E4 0xE4 -#define IO_ADDR_E5 0xE5 -#define IO_ADDR_E6 0xE6 -#define IO_ADDR_E7 0xE7 -#define IO_ADDR_E8 0xE8 -#define IO_ADDR_E9 0xE9 -#define IO_ADDR_EA 0xEA -#define IO_ADDR_EB 0xEB - - -enum Z80_RUN_STATES { - Z80_STOP = 0x00, - Z80_STOPPED = 0x01, - Z80_PAUSE = 0x02, - Z80_PAUSED = 0x03, - Z80_CONTINUE = 0x04, - Z80_RUNNING = 0x05, -}; -enum Z80_MEMORY_PROFILE { - USE_PHYSICAL_RAM = 0x00, - USE_VIRTUAL_RAM = 0x01 -}; - -typedef struct { - // Main memory, linear but indexed as though it were banks in 1K pages. - uint8_t memory[Z80_VIRTUAL_MEMORY_SIZE]; - - // Page pointer map. - // - // Each pointer points to a byte or block of bytes in the Z80 Memory frame, 64K Real + Banked. - // This is currently set at a block of size 0x800 per memory pointer for the MZ-700. - // The LSB of the pointer is a direct memory index to a byte or block of bytes, the upper byte of the pointer indicates type of memory space. - // 0x80 - physical host RAM - // 0x40 - physical host ROM - // 0x20 - physical host VRAM - // 0x10 - physical host hardware - // 0x08 - virtual host RAM - // 0x04 - virtual host ROM - // 0x02 - virtual host hardware - // 16bit Input Address -> map -> Pointer to 24bit memory address + type flag. - // -> Pointer+ to 24bit memory address + type flag. - uint32_t page[MEMORY_BLOCK_SLOTS]; - uint32_t shadowPage[MEMORY_BLOCK_SLOTS]; - - // I/O Page map. - // - // This is a map to indicate the use of the I/O page and allow any required remapping. - // <0x80>FF - physical host hardware - // <0x40>FF - virtual host hardware - // 16bit Input Address -> map -> Actual 16bit address to use + type flag. - uint32_t iopage[65536]; - - // Default page mode configured. This value reflects the default page and iotable map. - uint8_t defaultPageMode; - - // Refresh DRAM mode. 1 = Refresh, 0 = No refresh. Only applicable when running code in virtual Kernel RAM. - uint8_t refreshDRAM; - - // Inhibit mode is where certain memory ranges are inhibitted. The memory page is set to inhibit and this flag - // blocks actions which arent allowed during inhibit. - uint8_t inhibitMode; - - // Address caching. Used to minimise instruction length sent to CPLD. - uint16_t z80PrevAddr; - uint16_t z80PrevPort; - -#if(TARGET_HOST_MZ2000 == 1) - uint8_t lowMemorySwap; -#endif - - // Keyboard strobe and data. Required to detect hotkey press. - uint8_t keyportStrobe; - uint8_t keyportShiftCtrl; - uint8_t keyportHotKey; - - // Governor is the delay in a 32bit loop per Z80 opcode, used to govern execution speed when using virtual memory. - // This mechanism will eventually be tied into the M/T-state calculation for a more precise delay, but at the moment, - // with the Z80 assigned to an isolated CPU, it allows time sensitive tasks such as the tape recorder to work. - // The lower the value the faster the CPU speed. Two values are present as the optimiser, seeing ROM code not changing - // is quicker than RAM (both are in the same kernel memory) as a pointer calculation needs to be made. - uint32_t cpuGovernorDelayROM; - uint32_t cpuGovernorDelayRAM; -} t_Z80Ctrl; - -// IOCTL structure for passing data from user space to driver to perform commands. -// -struct z80_addr { - uint32_t start; - uint32_t end; - uint32_t size; -}; -struct z80_ctrl { - uint16_t pc; -}; -struct speed { - uint32_t speedMultiplier; -}; -struct cpld_ctrl { - uint32_t cmd; -}; -struct ioctlCmd { - int32_t cmd; - union { - struct z80_addr addr; - struct z80_ctrl z80; - struct speed speed; - struct cpld_ctrl cpld; - }; -}; - - -// Prototypes. -void setupMemory(enum Z80_MEMORY_PROFILE mode); - - -#endif diff --git a/software/FusionX/src/z80drv/MZ2000/z80io.c b/software/FusionX/src/z80drv/MZ2000/z80io.c deleted file mode 100644 index 08286199d..000000000 --- a/software/FusionX/src/z80drv/MZ2000/z80io.c +++ /dev/null @@ -1,428 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80io.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 IO Interface -// This file contains the methods used in interfacing the SOM to the Z80 socket -// and host hardware via a CPLD. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - - -//#include -//#include -//#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80io.h" - -#include -#include -#include -#include - -//------------------------------------------------------------------------------------------------------------------------------- -// -// User space driver access. -// -//------------------------------------------------------------------------------------------------------------------------------- - - - -// Initialise the SOM hardware used to communicate with the z80 socket and host hardware. -// The SOM interfaces to a CPLD which provides voltage level translation and also encapsulates the Z80 timing cycles as recreating -// them within the SOM is much more tricky. -// -// As this is an embedded device and performance/latency are priorities, minimal structured code is used to keep call stack and -// generated code to a mimimum without relying on the optimiser. -int z80io_init(void) -{ - // Locals. - int ret = 0; - - // Initialise GPIO. We call the HAL api to minimise time but for actual bit set/reset and read we go directly to registers to save time, increase throughput and minimise latency. - // Initialise the HAL. - MHal_GPIO_Init(); - - // Set the pads as GPIO devices. The HAL takes care of allocating and deallocating the padmux resources. - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_0); // Word (16bit) bidirectional bus. Default is read with data set. - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_1); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_2); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_3); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_4); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_5); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_6); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_7); - MHal_GPIO_Pad_Set(PAD_Z80IO_HIGH_BYTE); - //MHal_GPIO_Pad_Set(PAD_GPIO8); // SPIO 4wire control lines setup by the spidev driver but controlled directly in this driver. - //MHal_GPIO_Pad_Set(PAD_GPIO9); - //MHal_GPIO_Pad_Set(PAD_GPIO10); - //MHal_GPIO_Pad_Set(PAD_GPIO11); - MHal_GPIO_Pad_Set(PAD_Z80IO_READY); - MHal_GPIO_Pad_Set(PAD_Z80IO_LTSTATE); - MHal_GPIO_Pad_Set(PAD_Z80IO_BUSRQ); - MHal_GPIO_Pad_Set(PAD_Z80IO_BUSACK); - MHal_GPIO_Pad_Set(PAD_Z80IO_INT); - MHal_GPIO_Pad_Set(PAD_Z80IO_NMI); - MHal_GPIO_Pad_Set(PAD_Z80IO_WAIT); - MHal_GPIO_Pad_Set(PAD_Z80IO_RESET); - MHal_GPIO_Pad_Set(PAD_Z80IO_RSV1); -#ifdef NOTNEEDED - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_0); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_1); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_2); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_3); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_4); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_5); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_6); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_7); - MHal_GPIO_Pad_Set(PAD_Z80IO_WRITE); -#endif - - // Set required input pads. - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_0); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_1); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_2); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_3); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_4); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_5); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_6); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_7); - MHal_GPIO_Pad_Odn(PAD_Z80IO_READY); - MHal_GPIO_Pad_Odn(PAD_Z80IO_LTSTATE); - MHal_GPIO_Pad_Odn(PAD_Z80IO_BUSRQ); - MHal_GPIO_Pad_Odn(PAD_Z80IO_BUSACK); - MHal_GPIO_Pad_Odn(PAD_Z80IO_INT); - MHal_GPIO_Pad_Odn(PAD_Z80IO_NMI); - MHal_GPIO_Pad_Odn(PAD_Z80IO_WAIT); - MHal_GPIO_Pad_Odn(PAD_Z80IO_RESET); - MHal_GPIO_Pad_Odn(PAD_Z80IO_RSV1); - - // Set required output pads. -#ifdef NOTNEEDED - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_0); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_1); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_2); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_3); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_4); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_5); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_6); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_7); - MHal_GPIO_Pad_Oen(PAD_Z80IO_WRITE); - MHal_GPIO_Pull_High(PAD_Z80IO_WRITE); -#endif - - // Control signals. - MHal_GPIO_Pad_Oen(PAD_Z80IO_HIGH_BYTE); - MHal_GPIO_Pull_High(PAD_Z80IO_HIGH_BYTE); - - // Setup the MSPI0 device. - // - // Setup control, interrupts are not used. - MSPI_WRITE(MSPI_CTRL_OFFSET, MSPI_CPU_CLOCK_1_2 | MSPI_CTRL_CPOL_LOW | MSPI_CTRL_CPHA_HIGH | MSPI_CTRL_RESET | MSPI_CTRL_ENABLE_SPI); - - // Setup LSB First mode. - MSPI_WRITE(MSPI_LSB_FIRST_OFFSET, 0x0); - - // Setup clock. - CLK_WRITE(MSPI0_CLK_CFG, 0x1100) - - // Setup the frame size (all buffers to 8bits). - MSPI_WRITE(MSPI_FRAME_WBIT_OFFSET, 0xfff); - MSPI_WRITE(MSPI_FRAME_WBIT_OFFSET+1, 0xfff); - MSPI_WRITE(MSPI_FRAME_RBIT_OFFSET, 0xfff); - MSPI_WRITE(MSPI_FRAME_RBIT_OFFSET+1, 0xfff); - - // Setup Chip Selects to inactive. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Switch Video and Audio to host. - z80io_SPI_Send16(0x00f0, NULL); - - return ret; -} - - -//-------------------------------------------------------- -// Parallel bus Methods. -//-------------------------------------------------------- - -// Methods to read data from the parallel bus. -// The CPLD returns status and Z80 data on the 8bit bus as it is marginally quicker than retrieving it over the SPI bus. -// -inline uint8_t z80io_PRL_Read8(uint8_t dataFlag) -{ - // Locals. - uint8_t result = 0; - - // Byte according to flag. - if(dataFlag) - SET_CPLD_READ_DATA() - else - SET_CPLD_READ_STATUS() - - // Read the input registers and set value accordingly. - result = READ_CPLD_DATA_IN(); - - // Return 16bit value read from CPLD. - return(result); -} - -inline uint16_t z80io_PRL_Read16(void) -{ - // Locals. - uint16_t result = 0; - - // Low byte first. - CLEAR_CPLD_HIGH_BYTE(); - - // Read the input registers and set value accordingly. - result = (uint16_t)READ_CPLD_DATA_IN(); - - // High byte next. - SET_CPLD_HIGH_BYTE(); - - // Read the input registers and set value accordingly. - result |= (uint16_t)(READ_CPLD_DATA_IN() << 8); - - // Return 16bit value read from CPLD. - return(result); -} - - -// Parallel Bus methods were tried and tested but due to the GPIO bits being controlled by individual registers per bit, the setup time was longer -// than the transmission time of SPI. These methods are thus deprecated and a fusion of SPI and 8bit parallel is now used. -#ifdef NOTNEEDED -inline uint8_t z80io_PRL_Send8(uint8_t txData) -{ - // Locals. - // - - // Low byte only. - MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE].r_out) &= (~gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out); - - // Setup data. - if(txData & 0x0080) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_7].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_7].m_out); } - if(txData & 0x0040) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_6].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_6].m_out); } - if(txData & 0x0020) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_5].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_5].m_out); } - if(txData & 0x0010) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_4].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_4].m_out); } - if(txData & 0x0008) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_3].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_3].m_out); } - if(txData & 0x0004) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_2].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_2].m_out); } - if(txData & 0x0002) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_1].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_1].m_out); } - if(txData & 0x0001) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_0].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_0].m_out); } - - // Clock data. - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) &= (~gpio_table[PAD_Z80IO_WRITE ].m_out); - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) |= gpio_table[PAD_Z80IO_WRITE ].m_out; - - return(0); -} - -inline uint8_t z80io_PRL_Send16(uint16_t txData) -{ - // Locals. - // - - // Low byte first. - MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE].r_out) &= (~gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out); - - // Setup data. - if(txData & 0x0080) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_7].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_7].m_out); } - if(txData & 0x0040) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_6].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_6].m_out); } - if(txData & 0x0020) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_5].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_5].m_out); } - if(txData & 0x0010) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_4].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_4].m_out); } - if(txData & 0x0008) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_3].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_3].m_out); } - if(txData & 0x0004) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_2].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_2].m_out); } - if(txData & 0x0002) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_1].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_1].m_out); } - if(txData & 0x0001) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_0].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_0].m_out); } - - // Clock data. - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) &= (~gpio_table[PAD_Z80IO_WRITE ].m_out); - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) |= gpio_table[PAD_Z80IO_WRITE ].m_out; - - // High byte next. - MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE ].r_out) |= gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out; - - // Setup high byte. - if(txData & 0x8000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_7].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_7].m_out); } - if(txData & 0x4000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_6].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_6].m_out); } - if(txData & 0x2000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_5].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_5].m_out); } - if(txData & 0x1000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_4].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_4].m_out); } - if(txData & 0x0800) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_3].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_3].m_out); } - if(txData & 0x0400) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_2].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_2].m_out); } - if(txData & 0x0200) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_1].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_1].m_out); } - if(txData & 0x0100) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_0].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_0].m_out); } - - // Clock data. - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) &= (~gpio_table[PAD_Z80IO_WRITE ].m_out); - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) |= gpio_table[PAD_Z80IO_WRITE ].m_out; - - return(0); -} -#endif - - -//-------------------------------------------------------- -// SPI Methods. -//-------------------------------------------------------- - -// Methods to send 8,16 or 32 bits. Each method is seperate to minimise logic and execution time, 8bit being most sensitive. -// Macros have also been defined for inline inclusion which dont read back the response data. -// -uint8_t z80io_SPI_Send8(uint8_t txData, uint8_t *rxData) -{ - // Locals. - uint32_t timeout = MAX_CHECK_CNT; - - // Insert data into write buffers. - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)txData); - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); - - // Enable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); - - // Send. - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); - - // Wait for completion. - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) - { - if(--timeout == 0) - break; - } - - // Disable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Clear flag. - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); - - // Fetch data. - if(rxData != NULL) *rxData = (uint8_t)MSPI_READ(MSPI_FULL_DEPLUX_RD00); - - // Done. - return(timeout == 0); -} -uint8_t z80io_SPI_Send16(uint16_t txData, uint16_t *rxData) -{ - // Locals. - uint32_t timeout = MAX_CHECK_CNT; - - // Insert data into write buffers. - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, txData); - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); - - // Enable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); - - // Send. - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); - - // Wait for completion. - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) - { - if(--timeout == 0) - break; - } - - // Disable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Clear flag. - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); - - // Fetch data. - if(rxData != NULL) *rxData = MSPI_READ(MSPI_FULL_DEPLUX_RD00); - - // Done. - return(timeout == 0); -} -uint8_t z80io_SPI_Send32(uint32_t txData, uint32_t *rxData) -{ - // Locals. - uint32_t timeout = MAX_CHECK_CNT; - - // Insert data into write buffers. - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)txData); - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)(txData >> 16)); - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); - - // Enable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); - - // Send. - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); - - // Wait for completion. - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) - { - if(--timeout == 0) - break; - } - - // Disable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Clear flag. - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); - - // Fetch data. - if(rxData != NULL) *rxData = (uint32_t)(MSPI_READ(MSPI_FULL_DEPLUX_RD00) | (MSPI_READ(MSPI_FULL_DEPLUX_RD02) << 16)); - - // Done. - return(timeout == 0); -} - -//-------------------------------------------------------- -// Test Methods. -//-------------------------------------------------------- -#ifdef INCLUDE_TEST_METHODS -#include "z80io_test.c" -#else -uint8_t z80io_Z80_TestMemory(void) -{ - pr_info("Z80 Test Memory functionality not built-in.\n"); - return(0); -} -uint8_t z80io_SPI_Test(void) -{ - pr_info("SPI Test functionality not built-in.\n"); - return(0); -} -uint8_t z80io_PRL_Test(void) -{ - pr_info("Parallel Bus Test functionality not built-in.\n"); - return(0); -} -#endif diff --git a/software/FusionX/src/z80drv/MZ2000/z80io.h b/software/FusionX/src/z80drv/MZ2000/z80io.h deleted file mode 100755 index 9e5c1b0f2..000000000 --- a/software/FusionX/src/z80drv/MZ2000/z80io.h +++ /dev/null @@ -1,483 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80io.h -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 IO Interface -// This file contains the declarations used in interfacing the SOM to the Z80 socket -// and host hardware via a CPLD. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef Z80IO_H -#define Z80IO_H - -#ifdef __cplusplus - extern "C" { -#endif - -// Definitions to control compilation. -#define INCLUDE_TEST_METHODS 1 - -// CPLD Commands. -#define CPLD_CMD_FETCH_ADDR 0x10 -#define CPLD_CMD_FETCH_ADDR_P1 0x11 -#define CPLD_CMD_FETCH_ADDR_P2 0x12 -#define CPLD_CMD_FETCH_ADDR_P3 0x13 -#define CPLD_CMD_FETCH_ADDR_P4 0x14 -#define CPLD_CMD_FETCH_ADDR_P5 0x15 -#define CPLD_CMD_FETCH_ADDR_P6 0x16 -#define CPLD_CMD_FETCH_ADDR_P7 0x17 -#define CPLD_CMD_WRITE_ADDR 0x18 -#define CPLD_CMD_WRITE_ADDR_P1 0x19 -#define CPLD_CMD_WRITE_ADDR_P2 0x1A -#define CPLD_CMD_WRITE_ADDR_P3 0x1B -#define CPLD_CMD_WRITE_ADDR_P4 0x1C -#define CPLD_CMD_WRITE_ADDR_P5 0x1D -#define CPLD_CMD_WRITE_ADDR_P6 0x1E -#define CPLD_CMD_WRITE_ADDR_P7 0x1F -#define CPLD_CMD_READ_ADDR 0x20 -#define CPLD_CMD_READ_ADDR_P1 0x21 -#define CPLD_CMD_READ_ADDR_P2 0x22 -#define CPLD_CMD_READ_ADDR_P3 0x23 -#define CPLD_CMD_READ_ADDR_P4 0x24 -#define CPLD_CMD_READ_ADDR_P5 0x25 -#define CPLD_CMD_READ_ADDR_P6 0x26 -#define CPLD_CMD_READ_ADDR_P7 0x27 -#define CPLD_CMD_WRITEIO_ADDR 0x28 -#define CPLD_CMD_WRITEIO_ADDR_P1 0x29 -#define CPLD_CMD_WRITEIO_ADDR_P2 0x2A -#define CPLD_CMD_WRITEIO_ADDR_P3 0x2B -#define CPLD_CMD_WRITEIO_ADDR_P4 0x2C -#define CPLD_CMD_WRITEIO_ADDR_P5 0x2D -#define CPLD_CMD_WRITEIO_ADDR_P6 0x2E -#define CPLD_CMD_WRITEIO_ADDR_P7 0x2F -#define CPLD_CMD_READIO_ADDR 0x30 -#define CPLD_CMD_READIO_ADDR_P1 0x31 -#define CPLD_CMD_READIO_ADDR_P2 0x32 -#define CPLD_CMD_READIO_ADDR_P3 0x33 -#define CPLD_CMD_READIO_ADDR_P4 0x34 -#define CPLD_CMD_READIO_ADDR_P5 0x35 -#define CPLD_CMD_READIO_ADDR_P6 0x36 -#define CPLD_CMD_READIO_ADDR_P7 0x37 -#define CPLD_CMD_HALT 0x50 -#define CPLD_CMD_REFRESH 0x51 -#define CPLD_CMD_SET_SIGROUP1 0xF0 -#define CPLD_CMD_SET_AUTO_REFRESH 0xF1 -#define CPLD_CMD_CLEAR_AUTO_REFRESH 0xF2 -#define CPLD_CMD_SET_SPI_LOOPBACK 0xFE -#define CPLD_CMD_NOP1 0x00 -#define CPLD_CMD_NOP2 0xFF - - -// Pad numbers for using the MHal GPIO library. -#define PAD_Z80IO_IN_DATA_0 PAD_GPIO0 -#define PAD_Z80IO_IN_DATA_1 PAD_GPIO1 -#define PAD_Z80IO_IN_DATA_2 PAD_GPIO2 -#define PAD_Z80IO_IN_DATA_3 PAD_GPIO3 -#define PAD_Z80IO_IN_DATA_4 PAD_GPIO4 -#define PAD_Z80IO_IN_DATA_5 PAD_GPIO5 -#define PAD_Z80IO_IN_DATA_6 PAD_GPIO6 -#define PAD_Z80IO_IN_DATA_7 PAD_GPIO7 -#define PAD_SPIO_0 PAD_GPIO8 -#define PAD_SPIO_1 PAD_GPIO9 -#define PAD_SPIO_2 PAD_GPIO10 -#define PAD_SPIO_3 PAD_GPIO11 -#define PAD_Z80IO_HIGH_BYTE PAD_SAR_GPIO2 // Byte requiured, 0 = Low Byte, 1 = High Byte. -#define PAD_Z80IO_READY PAD_GPIO12 -#define PAD_Z80IO_LTSTATE PAD_PM_IRIN // IRIN -#define PAD_Z80IO_BUSRQ PAD_GPIO13 -#define PAD_Z80IO_BUSACK PAD_GPIO14 -#define PAD_Z80IO_INT PAD_UART0_RX // GPIO47 -#define PAD_Z80IO_NMI PAD_UART0_TX // GPIO48 -#define PAD_Z80IO_WAIT PAD_HSYNC_OUT // GPIO85 -#define PAD_Z80IO_RESET PAD_VSYNC_OUT // GPIO86 -#define PAD_Z80IO_RSV1 PAD_SATA_GPIO // GPIO90 - -// Physical register addresses. -#define PAD_Z80IO_IN_DATA_0_ADDR 0x103C00 -#define PAD_Z80IO_IN_DATA_1_ADDR 0x103C02 -#define PAD_Z80IO_IN_DATA_2_ADDR 0x103C04 -#define PAD_Z80IO_IN_DATA_3_ADDR 0x103C06 -#define PAD_Z80IO_IN_DATA_4_ADDR 0x103C08 -#define PAD_Z80IO_IN_DATA_5_ADDR 0x103C0A -#define PAD_Z80IO_IN_DATA_6_ADDR 0x103C0C -#define PAD_Z80IO_IN_DATA_7_ADDR 0x103C0E -#define PAD_SPIO_0_ADDR 0x103C10 -#define PAD_SPIO_1_ADDR 0x103C12 -#define PAD_SPIO_2_ADDR 0x103C14 -#define PAD_SPIO_3_ADDR 0x103C16 -#define PAD_Z80IO_HIGH_BYTE_ADDR 0x1425 -#define PAD_Z80IO_READY_ADDR 0x103C18 -#define PAD_Z80IO_LTSTATE_ADDR 0xF28 // IRIN -#define PAD_Z80IO_BUSRQ_ADDR 0x103C1A -#define PAD_Z80IO_BUSACK_ADDR 0x103C1C -#define PAD_Z80IO_INT_ADDR 0x103C30 // GPIO47 -#define PAD_Z80IO_NMI_ADDR 0x103C32 // GPIO48 -#define PAD_Z80IO_WAIT_ADDR 0x103C80 // GPIO85 -#define PAD_Z80IO_RESET_ADDR 0x103C82 // GPIO86 -#define PAD_Z80IO_RSV1_ADDR 0x103C8A // GPIO90 - -#ifdef NOTNEEDED -#define PAD_Z80IO_OUT_DATA_0 PAD_GPIO12 -#define PAD_Z80IO_OUT_DATA_1 PAD_GPIO13 -#define PAD_Z80IO_OUT_DATA_2 PAD_GPIO14 -#define PAD_Z80IO_OUT_DATA_3 PAD_UART0_RX // GPIO47 -#define PAD_Z80IO_OUT_DATA_4 PAD_UART0_TX // GPIO48 -#define PAD_Z80IO_OUT_DATA_5 PAD_HSYNC_OUT // GPIO85 -#define PAD_Z80IO_OUT_DATA_6 PAD_VSYNC_OUT // GPIO86 -#define PAD_Z80IO_OUT_DATA_7 PAD_SATA_GPIO // GPIO90 -#define PAD_Z80IO_WRITE PAD_PM_IRIN // Write data clock. -#endif - -//------------------------------------------------------------------------------------------------- -// The definitions below come from SigmaStar kernel drivers. No header file exists hence the -// duplication. -//------------------------------------------------------------------------------------------------- - -#define SUPPORT_SPI_1 0 -#define MAX_SUPPORT_BITS 16 - -#define BANK_TO_ADDR32(b) (b<<9) -#define BANK_SIZE 0x200 - -#define MS_BASE_REG_RIU_PA 0x1F000000 -#define gChipBaseAddr 0xFD203C00 -#define gPmSleepBaseAddr 0xFD001C00 -#define gSarBaseAddr 0xFD002800 -#define gRIUBaseAddr 0xFD000000 -#define gMOVDMAAddr 0xFD201600 -#define gClkBaseAddr 0xFD207000 -#define gMspBaseAddr 0xfd222000 - -#define MHal_CHIPTOP_REG(addr) (*(volatile U8*)((gChipBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_PM_SLEEP_REG(addr) (*(volatile U8*)((gPmSleepBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_SAR_GPIO_REG(addr) (*(volatile U8*)((gSarBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_RIU_REG(addr) (*(volatile U8*)((gRIUBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) - - -#define MSPI0_BANK_ADDR 0x1110 -#define MSPI1_BANK_ADDR 0x1111 -#define CLK__BANK_ADDR 0x1038 -#define CHIPTOP_BANK_ADDR 0x101E -#define MOVDMA_BANK_ADDR 0x100B - -#define BASE_REG_MSPI0_ADDR MSPI0_BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x111000) -#define BASE_REG_MSPI1_ADDR MSPI1_BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x111100) -#define BASE_REG_CLK_ADDR CLK__BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x103800) -#define BASE_REG_CHIPTOP_ADDR CHIPTOP_BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x101E00) - -//------------------------------------------------------------------------------------------------- -// Hardware Register Capability -//------------------------------------------------------------------------------------------------- -#define MSPI_WRITE_BUF_OFFSET 0x40 -#define MSPI_READ_BUF_OFFSET 0x44 -#define MSPI_WBF_SIZE_OFFSET 0x48 -#define MSPI_RBF_SIZE_OFFSET 0x48 - // read/ write buffer size -#define MSPI_RWSIZE_MASK 0xFF -#define MSPI_RSIZE_BIT_OFFSET 0x8 -#define MAX_READ_BUF_SIZE 0x8 -#define MAX_WRITE_BUF_SIZE 0x8 -// CLK config -#define MSPI_CTRL_OFFSET 0x49 -#define MSPI_CLK_CLOCK_OFFSET 0x49 -#define MSPI_CLK_CLOCK_BIT_OFFSET 0x08 -#define MSPI_CLK_CLOCK_MASK 0xFF -#define MSPI_CLK_PHASE_MASK 0x40 -#define MSPI_CLK_PHASE_BIT_OFFSET 0x06 -#define MSPI_CLK_POLARITY_MASK 0x80 -#define MSPI_CLK_POLARITY_BIT_OFFSET 0x07 -#define MSPI_CLK_PHASE_MAX 0x1 -#define MSPI_CLK_POLARITY_MAX 0x1 -#define MSPI_CLK_CLOCK_MAX 0x7 -#define MSPI_CTRL_CPOL_LOW 0x00 -#define MSPI_CTRL_CPOL_HIGH 0x80 -#define MSPI_CTRL_CPHA_LOW 0x00 -#define MSPI_CTRL_CPHA_HIGH 0x40 -#define MSPI_CTRL_3WIRE 0x10 -#define MSPI_CTRL_INTEN 0x04 -#define MSPI_CTRL_RESET 0x02 -#define MSPI_CTRL_ENABLE_SPI 0x01 -// DC config -#define MSPI_DC_MASK 0xFF -#define MSPI_DC_BIT_OFFSET 0x08 -#define MSPI_DC_TR_START_OFFSET 0x4A -#define MSPI_DC_TRSTART_MAX 0xFF -#define MSPI_DC_TR_END_OFFSET 0x4A -#define MSPI_DC_TREND_MAX 0xFF -#define MSPI_DC_TB_OFFSET 0x4B -#define MSPI_DC_TB_MAX 0xFF -#define MSPI_DC_TRW_OFFSET 0x4B -#define MSPI_DC_TRW_MAX 0xFF -// Frame Config -#define MSPI_FRAME_WBIT_OFFSET 0x4C -#define MSPI_FRAME_RBIT_OFFSET 0x4E -#define MSPI_FRAME_BIT_MAX 0x07 -#define MSPI_FRAME_BIT_MASK 0x07 -#define MSPI_FRAME_BIT_FIELD 0x03 -#define MSPI_LSB_FIRST_OFFSET 0x50 -#define MSPI_TRIGGER_OFFSET 0x5A -#define MSPI_DONE_OFFSET 0x5B -#define MSPI_DONE_CLEAR_OFFSET 0x5C -#define MSPI_CHIP_SELECT_OFFSET 0x5F -#define MSPI_CS1_DISABLE 0x01 -#define MSPI_CS1_ENABLE 0x00 -#define MSPI_CS2_DISABLE 0x02 -#define MSPI_CS2_ENABLE 0x00 -#define MSPI_CS3_DISABLE 0x04 -#define MSPI_CS3_ENABLE 0x00 -#define MSPI_CS4_DISABLE 0x08 -#define MSPI_CS4_ENABLE 0x00 -#define MSPI_CS5_DISABLE 0x10 -#define MSPI_CS5_ENABLE 0x00 -#define MSPI_CS6_DISABLE 0x20 -#define MSPI_CS6_ENABLE 0x00 -#define MSPI_CS7_DISABLE 0x40 -#define MSPI_CS7_ENABLE 0x00 -#define MSPI_CS8_DISABLE 0x80 -#define MSPI_CS8_ENABLE 0x00 - -#define MSPI_FULL_DEPLUX_RD_CNT (0x77) -#define MSPI_FULL_DEPLUX_RD00 (0x78) -#define MSPI_FULL_DEPLUX_RD01 (0x78) -#define MSPI_FULL_DEPLUX_RD02 (0x79) -#define MSPI_FULL_DEPLUX_RD03 (0x79) -#define MSPI_FULL_DEPLUX_RD04 (0x7a) -#define MSPI_FULL_DEPLUX_RD05 (0x7a) -#define MSPI_FULL_DEPLUX_RD06 (0x7b) -#define MSPI_FULL_DEPLUX_RD07 (0x7b) - -#define MSPI_FULL_DEPLUX_RD08 (0x7c) -#define MSPI_FULL_DEPLUX_RD09 (0x7c) -#define MSPI_FULL_DEPLUX_RD10 (0x7d) -#define MSPI_FULL_DEPLUX_RD11 (0x7d) -#define MSPI_FULL_DEPLUX_RD12 (0x7e) -#define MSPI_FULL_DEPLUX_RD13 (0x7e) -#define MSPI_FULL_DEPLUX_RD14 (0x7f) -#define MSPI_FULL_DEPLUX_RD15 (0x7f) - -//chip select bit map -#define MSPI_CHIP_SELECT_MAX 0x07 - -// control bit -#define MSPI_DONE_FLAG 0x01 -#define MSPI_TRIGGER 0x01 -#define MSPI_CLEAR_DONE 0x01 -#define MSPI_INT_ENABLE 0x04 -#define MSPI_RESET 0x02 -#define MSPI_ENABLE 0x01 - -// clk_mspi0 -#define MSPI0_CLK_CFG 0x33 //bit 2 ~bit 3 -#define MSPI0_CLK_108M 0x00 -#define MSPI0_CLK_54M 0x04 -#define MSPI0_CLK_12M 0x08 -#define MSPI0_CLK_MASK 0x0F - -// clk_mspi1 -#define MSPI1_CLK_CFG 0x33 //bit 10 ~bit 11 -#define MSPI1_CLK_108M 0x0000 -#define MSPI1_CLK_54M 0x0400 -#define MSPI1_CLK_12M 0x0800 -#define MSPI1_CLK_MASK 0x0F00 - -// clk_mspi -#define MSPI_CLK_CFG 0x33 -#define MSPI_SELECT_0 0x0000 -#define MSPI_SELECT_1 0x4000 -#define MSPI_CLK_MASK 0xF000 - -// Clock settings -#define MSPI_CPU_CLOCK_1_2 0x0000 -#define MSPI_CPU_CLOCK_1_4 0x0100 -#define MSPI_CPU_CLOCK_1_8 0x0200 -#define MSPI_CPU_CLOCK_1_16 0x0300 -#define MSPI_CPU_CLOCK_1_32 0x0400 -#define MSPI_CPU_CLOCK_1_64 0x0500 -#define MSPI_CPU_CLOCK_1_128 0x0600 -#define MSPI_CPU_CLOCK_1_256 0x0700 - -//CHITOP 101E mspi mode select -#define MSPI0_MODE 0x0C //bit0~bit1 -#define MSPI0_MODE_MASK 0x07 -#define MSPI1_MODE 0x0C //bit4~bit5 -#define MSPI1_MODE_MASK 0x70 -#define EJTAG_MODE 0xF -#define EJTAG_MODE_1 0x01 -#define EJTAG_MODE_2 0x02 -#define EJTAG_MODE_3 0x03 -#define EJTAG_MODE_MASK 0x03 - -//MOVDMA 100B -#define MOV_DMA_SRC_ADDR_L 0x03 -#define MOV_DMA_SRC_ADDR_H 0x04 -#define MOV_DMA_DST_ADDR_L 0x05 -#define MOV_DMA_DST_ADDR_H 0x06 -#define MOV_DMA_BYTE_CNT_L 0x07 -#define MOV_DMA_BYTE_CNT_H 0x08 -#define DMA_MOVE0_IRQ_CLR 0x28 -#define MOV_DMA_IRQ_FINAL_STATUS 0x2A -#define DMA_MOVE0_ENABLE 0x00 -#define DMA_RW 0x50 //0 for dma write to device, 1 for dma read from device -#define DMA_READ 0x01 -#define DMA_WRITE 0x00 -#define DMA_DEVICE_MODE 0x51 -#define DMA_DEVICE_SEL 0x52 - -//spi dma -#define MSPI_DMA_DATA_LENGTH_L 0x30 -#define MSPI_DMA_DATA_LENGTH_H 0x31 -#define MSPI_DMA_ENABLE 0x32 -#define MSPI_DMA_RW_MODE 0x33 -#define MSPI_DMA_WRITE 0x00 -#define MSPI_DMA_READ 0x01 - -#define MSTAR_SPI_TIMEOUT_MS 30000 -#define MSTAR_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA /*| SPI_CS_HIGH | SPI_NO_CS | SPI_LSB_FIRST*/) - - -//------------------------------------------------------------------------------------------------- -// Macros -//------------------------------------------------------------------------------------------------- - - -#define MHal_CHIPTOP_REG(addr) (*(volatile U8*)((gChipBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_PM_SLEEP_REG(addr) (*(volatile U8*)((gPmSleepBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_SAR_GPIO_REG(addr) (*(volatile U8*)((gSarBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_RIU_REG(addr) (*(volatile U8*)((gRIUBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define READ_BYTE(_reg) (*(volatile u8*)(_reg)) -#define READ_WORD(_reg) (*(volatile u16*)(_reg)) -#define READ_LONG(_reg) (*(volatile u32*)(_reg)) -#define WRITE_BYTE(_reg, _val) {(*((volatile u8*)(_reg))) = (u8)(_val); } -#define WRITE_WORD(_reg, _val) {(*((volatile u16*)(_reg))) = (u16)(_val); } -#define WRITE_LONG(_reg, _val) {(*((volatile u32*)(_reg))) = (u32)(_val); } -#define WRITE_WORD_MASK(_reg, _val, _mask) {(*((volatile u16*)(_reg))) = ((*((volatile u16*)(_reg))) & ~(_mask)) | ((u16)(_val) & (_mask)); } -#define READ_CPLD_DATA_IN() ((MHal_RIU_REG(PAD_Z80IO_IN_DATA_7_ADDR) & 0x1) << 7 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_6_ADDR) & 0x1) << 6 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_5_ADDR) & 0x1) << 5 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_4_ADDR) & 0x1) << 4 |\ - (MHal_RIU_REG(PAD_Z80IO_IN_DATA_3_ADDR) & 0x1) << 3 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_2_ADDR) & 0x1) << 2 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_1_ADDR) & 0x1) << 1 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_0_ADDR) & 0x1)) -#define SET_CPLD_READ_DATA() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) |= 0x4;} -#define SET_CPLD_READ_STATUS() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) &= ~0x4;} -#define SET_CPLD_HIGH_BYTE() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) |= 0x4;} -#define CLEAR_CPLD_HIGH_BYTE() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) &= ~0x4;} -#define CPLD_READY() (MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) -#define CPLD_RESET() (MHal_RIU_REG(PAD_Z80IO_RESET_ADDR) & 0x1) -#define CPLD_LAST_TSTATE() (MHal_RIU_REG(PAD_Z80IO_LTSTATE_ADDR) & 0x4) -#define SPI_SEND8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); \ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE);\ - } -#define SPI_SEND16(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ - } -#define SPI_SEND32(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ - } - -// read 2 byte -#define MSPI_READ(_reg_) READ_WORD(gMspBaseAddr + ((_reg_)<<2)) -// write 2 byte -//#define MSPI_WRITE(_reg_, _val_) {pr_info("PDS: MSPI_WRITE(0x%x, 0x%x, 0x%x)\n", _reg_, _val_, gMspBaseAddr + ((_reg_)<<2)); WRITE_WORD(gMspBaseAddr + ((_reg_)<<2), (_val_)); } -#define MSPI_WRITE(_reg_, _val_) WRITE_WORD(gMspBaseAddr + ((_reg_)<<2), (_val_)); -//write 2 byte mask -//#define MSPI_WRITE_MASK(_reg_, _val_, mask) {pr_info("PDS: WRITE_LONG(0x%x, 0x%x, mask=0x%x)\n", _reg_, _val_, mask); WRITE_WORD_MASK(gMspBaseAddr + ((_reg_)<<2), (_val_), (mask)); } -#define MSPI_WRITE_MASK(_reg_, _val_, mask) WRITE_WORD_MASK(gMspBaseAddr + ((_reg_)<<2), (_val_), (mask)); - -#define CLK_READ(_reg_) READ_WORD(gClkBaseAddr + ((_reg_)<<2)) -//#define CLK_WRITE(_reg_, _val_) {pr_info("PDS: CLK_WRITE(0x%x, 0x%x)\n", _reg_, _val_); WRITE_WORD(gClkBaseAddr + ((_reg_)<<2), (_val_)); } -#define CLK_WRITE(_reg_, _val_) WRITE_WORD(gClkBaseAddr + ((_reg_)<<2), (_val_)); - -#define CHIPTOP_READ(_reg_) READ_WORD(gChipBaseAddr + ((_reg_)<<2)) -//#define CHIPTOP_WRITE(_reg_, _val_) {pr_info("PDS: CHIPTOP_WRITE(0x%x, 0x%x)\n", _reg_, _val_); WRITE_WORD(gChipBaseAddr + ((_reg_)<<2), (_val_)); } -#define CHIPTOP_WRITE(_reg_, _val_) WRITE_WORD(gChipBaseAddr + ((_reg_)<<2), (_val_)); - -#define MOVDMA_READ(_reg_) READ_WORD(gMOVDMAAddr + ((_reg_)<<2)) -//#define MOVDMA_WRITE(_reg_, _val_) {pr_info("PDS: MOVDMA_WRITE(0x%x, 0x%x)\n", _reg_, _val_); WRITE_WORD(gMOVDMAAddr + ((_reg_)<<2), (_val_)); } -#define MOVDMA_WRITE(_reg_, _val_) WRITE_WORD(gMOVDMAAddr + ((_reg_)<<2), (_val_)); - -#define _HAL_MSPI_ClearDone() MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET,MSPI_CLEAR_DONE) -#define MAX_CHECK_CNT 2000 - -#define MSPI_READ_INDEX 0x0 -#define MSPI_WRITE_INDEX 0x1 - -#define SPI_MIU0_BUS_BASE 0x20000000 -#define SPI_MIU1_BUS_BASE 0xFFFFFFFF - - -// Function definitions. -// -int z80io_init(void); -uint8_t z80io_SPI_Send8(uint8_t txData, uint8_t *rxData); -uint8_t z80io_SPI_Send16(uint16_t txData, uint16_t *rxData); -uint8_t z80io_SPI_Send32(uint32_t txData, uint32_t *rxData); -#ifdef NOTNEEDED -uint8_t z80io_PRL_Send8(uint8_t txData); -uint8_t z680io_PRL_Send16(uint16_t txData); -#endif -uint8_t z80io_PRL_Read8(uint8_t dataFlag); -uint16_t z80io_PRL_Read16(void); -uint8_t z80io_SPI_Test(void); -uint8_t z80io_PRL_Test(void); -uint8_t z80io_Z80_TestMemory(void); - -extern void MHal_GPIO_Init(void); -extern void MHal_GPIO_Pad_Set(uint8_t u8IndexGPIO); -extern int MHal_GPIO_PadGroupMode_Set(uint32_t u32PadMode); -extern int MHal_GPIO_PadVal_Set(uint8_t u8IndexGPIO, uint32_t u32PadMode); -extern void MHal_GPIO_Pad_Oen(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Pad_Odn(uint8_t u8IndexGPIO); -extern uint8_t MHal_GPIO_Pad_Level(uint8_t u8IndexGPIO); -extern uint8_t MHal_GPIO_Pad_InOut(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Pull_High(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Pull_Low(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Set_High(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Set_Low(uint8_t u8IndexGPIO); -extern void MHal_Enable_GPIO_INT(uint8_t u8IndexGPIO); -extern int MHal_GPIO_To_Irq(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Set_POLARITY(uint8_t u8IndexGPIO, uint8_t reverse); -extern void MHal_GPIO_Set_Driving(uint8_t u8IndexGPIO, uint8_t setHigh); -extern void MHal_GPIO_PAD_32K_OUT(uint8_t u8Enable); - -#ifdef __cplusplus -} -#endif -#endif // Z80IO_H diff --git a/software/FusionX/src/z80drv/MZ2000/z80io_test.c b/software/FusionX/src/z80drv/MZ2000/z80io_test.c deleted file mode 100644 index ac7346b57..000000000 --- a/software/FusionX/src/z80drv/MZ2000/z80io_test.c +++ /dev/null @@ -1,541 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80io_test.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 IO Interface Test Methods -// This file contains the methods used to test the SOM to CPLD interface and evaluate -// it's performance. Production builds wont include these methods. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -//-------------------------------------------------------- -// Test Methods. -//-------------------------------------------------------- -uint8_t z80io_Z80_TestMemory(void) -{ - // Locals. - // - uint32_t addr; - uint32_t fullCmd; - uint8_t cmd; - struct timeval start, stop; - uint32_t iterations = 100; - uint32_t errorCount; - uint32_t idx; - long totalTime; - long bytesMSec; - uint8_t result; - spinlock_t spinLock; - unsigned long flags; - - SPI_SEND8(CPLD_CMD_CLEAR_AUTO_REFRESH); - - SPI_SEND32(0x00E30000 | (0x07 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E80000 | (0x82 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E20000 | (0x58 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E00000 | (0xF7 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E90000 | (0x0F << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00EB0000 | (0xCF << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00EB0000 | (0xFF << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - pr_info("Z80 Host Test - IO.\n"); -// for(idx=0; idx < 1000000; idx++) -// { -// SPI_SEND32(0x00E80000 | (0xD3 << 8) | CPLD_CMD_WRITEIO_ADDR); -// SPI_SEND32(0xD0000000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); -// SPI_SEND32(0xD0100000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); -// SPI_SEND32(0xD0200000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); -// SPI_SEND32(0xD0300000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); -// SPI_SEND32(0xD0400000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); -// SPI_SEND32(0xD0500000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); -// } - - spin_lock_init(&spinLock); - pr_info("Z80 Host Test - Testing IO Write performance.\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - for(addr=0x0000; addr < 0x10000; addr++) - { - fullCmd = 0x00000000| ((uint8_t)addr) << 8 | CPLD_CMD_WRITEIO_ADDR; - SPI_SEND32(fullCmd); - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - spin_lock_init(&spinLock); - pr_info("Z80 Host Test - Testing IO Read performance.\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible IO ports and write to it. - for(addr=0x0000; addr < 0x10000; addr++) - { - fullCmd = 0x00000000 | ((uint8_t)addr) << 8 | CPLD_CMD_READIO_ADDR; - SPI_SEND32(fullCmd); - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - spin_lock_init(&spinLock); - pr_info("Z80 Host Test - Testing RAM Write performance.\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x1000; addr < 0xD000; addr++) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Write performance (opt).\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x1000; addr < 0xD000; addr++) - { - if(addr == 0x1000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND16(((uint8_t)addr) << 8 | cmd); - } - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Write/Fetch performance (opt).\n"); - errorCount = 0; - SET_CPLD_READ_DATA(); - //MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE ].r_out) |= gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x8000; addr < 0xD000; addr++) - { - if(addr == 0x8000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND16(((uint8_t)addr) << 8 | cmd); - } - - // Read back the same byte. - cmd = 0x10; - SPI_SEND8(cmd); - while(CPLD_READY() == 0); - - result = READ_CPLD_DATA_IN(); - if(result != (uint8_t)addr) - { - if(errorCount < 50) pr_info("Read byte:0x%x, Written:0x%x\n", result, (uint8_t)addr); - errorCount++; - } - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, errorCount=%d, %ldBytes/sec\n", totalTime/1000, errorCount, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Write/Read performance (opt).\n"); - errorCount = 0; - SET_CPLD_READ_DATA(); - //MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE ].r_out) |= gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x8000; addr < 0xD000; addr++) - { - if(addr == 0x8000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND16(((uint8_t)addr) << 8 | cmd); - } - - // Read back the same byte. - cmd = 0x20; - SPI_SEND8(cmd); - while(CPLD_READY() == 0); - - result = READ_CPLD_DATA_IN(); - if(result != (uint8_t)addr) - { - if(errorCount < 50) pr_info("Read byte:0x%x, Written:0x%x\n", result, (uint8_t)addr); - errorCount++; - } - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, errorCount=%d, %ldBytes/sec\n", totalTime/1000, errorCount, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Fetch performance.\n"); - SET_CPLD_READ_DATA(); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and read from it. - for(addr=0x1000; addr < 0xD000; addr++) - { - if(addr == 0x1000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x10; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x11; - SPI_SEND8(cmd); - } - while(CPLD_READY() == 0); - result = READ_CPLD_DATA_IN(); - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Read performance (opt).\n"); - SET_CPLD_READ_DATA(); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and read from it. - for(addr=0x1000; addr < 0xD000; addr++) - { - if(addr == 0x1000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x20; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x21; - SPI_SEND8(cmd); - } - while(CPLD_READY() == 0); - result = READ_CPLD_DATA_IN(); - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - // Go through all the accessible attribute VRAM and initialise it. - pr_info("Z80 Host Test - Testing VRAM Write performance.\n"); - SPI_SEND32(0x00E80000 | (0xD3 << 8) | CPLD_CMD_WRITEIO_ADDR); - iterations = 256*10; - do_gettimeofday(&start); - for(addr=0xD800; addr < 0xE000; addr++) - { - //while(CPLD_READY() == 0); - if(addr == 0xD800) - { - fullCmd = (addr << 16) |(0x71 << 8) | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND8(cmd); - } - } - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible VRAM and write to it. - for(addr=0xD000; addr < 0xD800; addr++) - { - //while(CPLD_READY() == 0); - if(addr == 0xD000) - { - fullCmd = (addr << 16) | ((uint8_t)idx << 8) | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND8(cmd); - } - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)((1*iterations*0x800)+0x800)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - return(0); -} - - -// A simple test to verify the SOM to CPLD SPI connectivity and give an estimate of its performance. -// The performance is based on the SPI setup and transmit time along with the close and received data processing. -// In real use, the driver will just send a command and generally ignore received data so increased throughput can be achieved. -// -uint8_t z80io_SPI_Test(void) -{ - // Locals. - // - struct timeval start, stop; - uint32_t iterations = 10000000; - uint32_t idx; - uint8_t rxData8; - uint16_t rxData16; - uint16_t rxData16Last; - uint32_t rxData32; - uint32_t rxData32Last; - uint32_t errorCount; - long totalTime; - long bytesMSec; - - // Place the CPLD into echo test mode. - z80io_SPI_Send8(0xfe, &rxData8); - - // 1st. test, 8bit. - pr_info("SPI Test - Testing 8 bit performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - z80io_SPI_Send8((uint8_t)idx, &rxData8); - if(idx > 1 && (uint8_t)(idx-1) != rxData8) - { - if(errorCount < 20) - pr_info("0x%x: Last(0x%x) /= New(0x%x)\n",idx, (uint8_t)(idx-1), rxData8 ); - errorCount++; - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 2nd. test, 16bit. - pr_info("SPI Test - Testing 16 bit performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Byte re-ordering required as the CPLD echo's back the last 8bits received, it doesnt know if a transmission is 8/16/32bits. - z80io_SPI_Send16((uint16_t)idx, &rxData16); - if(idx > 0 && (uint16_t)(idx-1) != (uint16_t)(((rxData16&0x00ff) << 8) | ((rxData16Last & 0xff00) >> 8))) - { - if(errorCount < 20) - pr_info("0x%x: Last(0x%x) /= New(0x%x)\n",idx, (uint16_t)(idx-1), (uint16_t)(((rxData16&0x00ff) << 8) | ((rxData16Last & 0xff00) >> 8))); - errorCount++; - } - rxData16Last = rxData16; - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 3rd. test, 32bit. - pr_info("SPI Test - Testing 32 bit performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - z80io_SPI_Send32((uint32_t)idx, &rxData32); - if(idx > 0 && (uint32_t)(idx-1) != (uint32_t)(((rxData32&0x00ff) << 8) | ((rxData32Last & 0xff000000) >> 8) | ((rxData32Last & 0xff0000) >> 8) | ((rxData32Last & 0xff00) >> 8))) - { - if(errorCount < 20) - pr_info("0x%x: Last(0x%x) /= New(0x%x)\n",idx, (uint32_t)(idx-1), (uint32_t)(((rxData32&0x00ff) << 8) | ((rxData32Last & 0xff000000) >> 8) | ((rxData32Last & 0xff0000) >> 8) | ((rxData32Last & 0xff00) >> 8))); - errorCount++; - } - rxData32Last = rxData32; - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(4*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - pr_info("Press host RESET button Once to reset the CPLD.\n"); - return(0); -} - -// Method to test the parallel bus, verifying integrity and assessing performance. -uint8_t z80io_PRL_Test(void) -{ - // Locals. - // - struct timeval start, stop; - uint32_t iterations = 10000000; - uint32_t idx; - uint8_t rxData8; - uint16_t rxData16; - long totalTime; - long bytesMSec; -#ifdef NOTNEEDED - uint32_t errorCount; -#endif - - // Place the CPLD into echo test mode. - - // 1st. test, 8bit RW. -#ifdef NOTNEEDED - pr_info("Parallel Test - Testing 8 bit r/w performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Write byte and readback to compare. - z80io_PRL_Send8((uint8_t)idx); - rxData8 = z80io_PRL_Read8(); - if((uint8_t)idx != rxData8) - { - pr_info("0x%x: Written(0x%x) /= Read(0x%x)\n", idx, (uint8_t)(idx), rxData8); - errorCount++; - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 2nd. test, 8bit Write. - pr_info("Parallel Test - Testing 8 bit write performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Write byte. - z80io_PRL_Send8((uint8_t)idx); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); -#endif - - // 3rd. test, 8bit Read. - pr_info("Parallel Test - Testing 8 bit read performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Read byte. - rxData8 = z80io_PRL_Read8(0); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - -#ifdef NOTNEEDED - // 4th test, 16bit. - pr_info("Parallel Test - Testing 16 bit r/w performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Byte re-ordering required as the CPLD echo's back the last 8bits received, it doesnt know if a transmission is 8/16/32bits. - z80io_PRL_Send16((uint16_t)idx); - rxData16 = z80io_PRL_Read16(); - if((uint16_t)idx != rxData16) - { - pr_info("0x%x: Written(0x%x) /= Read(0x%x)\n", idx, (uint16_t)(idx), rxData16); - errorCount++; - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 5th test, 16bit Write. - pr_info("Parallel Test - Testing 16 bit write performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Write word. - z80io_PRL_Send16((uint16_t)idx); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); -#endif - - // 6th test, 16bit Read. - pr_info("Parallel Test - Testing 16 bit read performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Read word. - rxData16 = z80io_PRL_Read16(); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Press host RESET button Once to reset the CPLD.\n"); - return(0); -} diff --git a/software/FusionX/src/z80drv/MZ700/optparse.h b/software/FusionX/src/z80drv/MZ700/optparse.h deleted file mode 100644 index f96184add..000000000 --- a/software/FusionX/src/z80drv/MZ700/optparse.h +++ /dev/null @@ -1,403 +0,0 @@ -/* Optparse --- portable, reentrant, embeddable, getopt-like option parser - * - * This is free and unencumbered software released into the public domain. - * - * To get the implementation, define OPTPARSE_IMPLEMENTATION. - * Optionally define OPTPARSE_API to control the API's visibility - * and/or linkage (static, __attribute__, __declspec). - * - * The POSIX getopt() option parser has three fatal flaws. These flaws - * are solved by Optparse. - * - * 1) Parser state is stored entirely in global variables, some of - * which are static and inaccessible. This means only one thread can - * use getopt(). It also means it's not possible to recursively parse - * nested sub-arguments while in the middle of argument parsing. - * Optparse fixes this by storing all state on a local struct. - * - * 2) The POSIX standard provides no way to properly reset the parser. - * This means for portable code that getopt() is only good for one - * run, over one argv with one option string. It also means subcommand - * options cannot be processed with getopt(). Most implementations - * provide a method to reset the parser, but it's not portable. - * Optparse provides an optparse_arg() function for stepping over - * subcommands and continuing parsing of options with another option - * string. The Optparse struct itself can be passed around to - * subcommand handlers for additional subcommand option parsing. A - * full reset can be achieved by with an additional optparse_init(). - * - * 3) Error messages are printed to stderr. This can be disabled with - * opterr, but the messages themselves are still inaccessible. - * Optparse solves this by writing an error message in its errmsg - * field. The downside to Optparse is that this error message will - * always be in English rather than the current locale. - * - * Optparse should be familiar with anyone accustomed to getopt(), and - * it could be a nearly drop-in replacement. The option string is the - * same and the fields have the same names as the getopt() global - * variables (optarg, optind, optopt). - * - * Optparse also supports GNU-style long options with optparse_long(). - * The interface is slightly different and simpler than getopt_long(). - * - * By default, argv is permuted as it is parsed, moving non-option - * arguments to the end. This can be disabled by setting the `permute` - * field to 0 after initialization. - */ -#ifndef OPTPARSE_H -#define OPTPARSE_H - -#ifndef OPTPARSE_API -# define OPTPARSE_API -#endif - -struct optparse { - char **argv; - int permute; - int optind; - int optopt; - char *optarg; - char errmsg[64]; - int subopt; -}; - -enum optparse_argtype { - OPTPARSE_NONE, - OPTPARSE_REQUIRED, - OPTPARSE_OPTIONAL -}; - -struct optparse_long { - const char *longname; - int shortname; - enum optparse_argtype argtype; -}; - -/** - * Initializes the parser state. - */ -OPTPARSE_API -void optparse_init(struct optparse *options, char **argv); - -/** - * Read the next option in the argv array. - * @param optstring a getopt()-formatted option string. - * @return the next option character, -1 for done, or '?' for error - * - * Just like getopt(), a character followed by no colons means no - * argument. One colon means the option has a required argument. Two - * colons means the option takes an optional argument. - */ -OPTPARSE_API -int optparse(struct optparse *options, const char *optstring); - -/** - * Handles GNU-style long options in addition to getopt() options. - * This works a lot like GNU's getopt_long(). The last option in - * longopts must be all zeros, marking the end of the array. The - * longindex argument may be NULL. - */ -OPTPARSE_API -int optparse_long(struct optparse *options, - const struct optparse_long *longopts, - int *longindex); - -/** - * Used for stepping over non-option arguments. - * @return the next non-option argument, or NULL for no more arguments - * - * Argument parsing can continue with optparse() after using this - * function. That would be used to parse the options for the - * subcommand returned by optparse_arg(). This function allows you to - * ignore the value of optind. - */ -OPTPARSE_API -char *optparse_arg(struct optparse *options); - -/* Implementation */ -#ifdef OPTPARSE_IMPLEMENTATION - -#define OPTPARSE_MSG_INVALID "invalid option" -#define OPTPARSE_MSG_MISSING "option requires an argument" -#define OPTPARSE_MSG_TOOMANY "option takes no arguments" - -static int -optparse_error(struct optparse *options, const char *msg, const char *data) -{ - unsigned p = 0; - const char *sep = " -- '"; - while (*msg) - options->errmsg[p++] = *msg++; - while (*sep) - options->errmsg[p++] = *sep++; - while (p < sizeof(options->errmsg) - 2 && *data) - options->errmsg[p++] = *data++; - options->errmsg[p++] = '\''; - options->errmsg[p++] = '\0'; - return '?'; -} - -OPTPARSE_API -void -optparse_init(struct optparse *options, char **argv) -{ - options->argv = argv; - options->permute = 1; - options->optind = 1; - options->subopt = 0; - options->optarg = 0; - options->errmsg[0] = '\0'; -} - -static int -optparse_is_dashdash(const char *arg) -{ - return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; -} - -static int -optparse_is_shortopt(const char *arg) -{ - return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; -} - -static int -optparse_is_longopt(const char *arg) -{ - return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; -} - -static void -optparse_permute(struct optparse *options, int index) -{ - char *nonoption = options->argv[index]; - int i; - for (i = index; i < options->optind - 1; i++) - options->argv[i] = options->argv[i + 1]; - options->argv[options->optind - 1] = nonoption; -} - -static int -optparse_argtype(const char *optstring, char c) -{ - int count = OPTPARSE_NONE; - if (c == ':') - return -1; - for (; *optstring && c != *optstring; optstring++); - if (!*optstring) - return -1; - if (optstring[1] == ':') - count += optstring[2] == ':' ? 2 : 1; - return count; -} - -OPTPARSE_API -int -optparse(struct optparse *options, const char *optstring) -{ - int type; - char *next; - char *option = options->argv[options->optind]; - options->errmsg[0] = '\0'; - options->optopt = 0; - options->optarg = 0; - if (option == 0) { - return -1; - } else if (optparse_is_dashdash(option)) { - options->optind++; /* consume "--" */ - return -1; - } else if (!optparse_is_shortopt(option)) { - if (options->permute) { - int index = options->optind++; - int r = optparse(options, optstring); - optparse_permute(options, index); - options->optind--; - return r; - } else { - return -1; - } - } - option += options->subopt + 1; - options->optopt = option[0]; - type = optparse_argtype(optstring, option[0]); - next = options->argv[options->optind + 1]; - switch (type) { - case -1: { - char str[2] = {0, 0}; - str[0] = option[0]; - options->optind++; - return optparse_error(options, OPTPARSE_MSG_INVALID, str); - } - case OPTPARSE_NONE: - if (option[1]) { - options->subopt++; - } else { - options->subopt = 0; - options->optind++; - } - return option[0]; - case OPTPARSE_REQUIRED: - options->subopt = 0; - options->optind++; - if (option[1]) { - options->optarg = option + 1; - } else if (next != 0) { - options->optarg = next; - options->optind++; - } else { - char str[2] = {0, 0}; - str[0] = option[0]; - options->optarg = 0; - return optparse_error(options, OPTPARSE_MSG_MISSING, str); - } - return option[0]; - case OPTPARSE_OPTIONAL: - options->subopt = 0; - options->optind++; - if (option[1]) - options->optarg = option + 1; - else - options->optarg = 0; - return option[0]; - } - return 0; -} - -OPTPARSE_API -char * -optparse_arg(struct optparse *options) -{ - char *option = options->argv[options->optind]; - options->subopt = 0; - if (option != 0) - options->optind++; - return option; -} - -static int -optparse_longopts_end(const struct optparse_long *longopts, int i) -{ - return !longopts[i].longname && !longopts[i].shortname; -} - -static void -optparse_from_long(const struct optparse_long *longopts, char *optstring) -{ - char *p = optstring; - int i; - for (i = 0; !optparse_longopts_end(longopts, i); i++) { - if (longopts[i].shortname && longopts[i].shortname < 127) { - int a; - *p++ = longopts[i].shortname; - for (a = 0; a < (int)longopts[i].argtype; a++) - *p++ = ':'; - } - } - *p = '\0'; -} - -/* Unlike strcmp(), handles options containing "=". */ -static int -optparse_longopts_match(const char *longname, const char *option) -{ - const char *a = option, *n = longname; - if (longname == 0) - return 0; - for (; *a && *n && *a != '='; a++, n++) - if (*a != *n) - return 0; - return *n == '\0' && (*a == '\0' || *a == '='); -} - -/* Return the part after "=", or NULL. */ -static char * -optparse_longopts_arg(char *option) -{ - for (; *option && *option != '='; option++); - if (*option == '=') - return option + 1; - else - return 0; -} - -static int -optparse_long_fallback(struct optparse *options, - const struct optparse_long *longopts, - int *longindex) -{ - int result; - char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ - optparse_from_long(longopts, optstring); - result = optparse(options, optstring); - if (longindex != 0) { - *longindex = -1; - if (result != -1) { - int i; - for (i = 0; !optparse_longopts_end(longopts, i); i++) - if (longopts[i].shortname == options->optopt) - *longindex = i; - } - } - return result; -} - -OPTPARSE_API -int -optparse_long(struct optparse *options, - const struct optparse_long *longopts, - int *longindex) -{ - int i; - char *option = options->argv[options->optind]; - if (option == 0) { - return -1; - } else if (optparse_is_dashdash(option)) { - options->optind++; /* consume "--" */ - return -1; - } else if (optparse_is_shortopt(option)) { - return optparse_long_fallback(options, longopts, longindex); - } else if (!optparse_is_longopt(option)) { - if (options->permute) { - int index = options->optind++; - int r = optparse_long(options, longopts, longindex); - optparse_permute(options, index); - options->optind--; - return r; - } else { - return -1; - } - } - - /* Parse as long option. */ - options->errmsg[0] = '\0'; - options->optopt = 0; - options->optarg = 0; - option += 2; /* skip "--" */ - options->optind++; - for (i = 0; !optparse_longopts_end(longopts, i); i++) { - const char *name = longopts[i].longname; - if (optparse_longopts_match(name, option)) { - char *arg; - if (longindex) - *longindex = i; - options->optopt = longopts[i].shortname; - arg = optparse_longopts_arg(option); - if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { - return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); - } if (arg != 0) { - options->optarg = arg; - } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { - options->optarg = options->argv[options->optind]; - if (options->optarg == 0) - return optparse_error(options, OPTPARSE_MSG_MISSING, name); - else - options->optind++; - } - return options->optopt; - } - } - return optparse_error(options, OPTPARSE_MSG_INVALID, option); -} - -#endif /* OPTPARSE_IMPLEMENTATION */ -#endif /* OPTPARSE_H */ diff --git a/software/FusionX/src/z80drv/MZ700/z80ctrl.c b/software/FusionX/src/z80drv/MZ700/z80ctrl.c deleted file mode 100644 index 70c69f2b5..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80ctrl.c +++ /dev/null @@ -1,734 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80ctrl.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 Control Interface -// This file contains a command line utility tool for controlling the z80drv device -// driver. The tool allows manipulation of the emulated Z80, inspection of its -// memory and data, transmission of adhoc commands to the underlying CPLD-Z80 -// gateway and loading/saving of programs and data to/from the Z80 virtual and -// host memory. -// -// Credits: Zilog Z80 CPU Emulator v0.2 written by Manuel Sainz de Baranda y Goñi -// The Z80 CPU Emulator is the heart of the Z80 device driver. -// Copyright: (c) 2019-2022 Philip Smart -// (c) 1999-2022 Manuel Sainz de Baranda y Goñi -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80driver.h" - -#define VERSION "1.0" -#define AUTHOR "P.D.Smart" -#define COPYRIGHT "(c) 2018-22" - -// Getopt_long is buggy so we use optparse. -#define OPTPARSE_IMPLEMENTATION -#define OPTPARSE_API static -#include "optparse.h" - -// Device driver name. -#define DEVICE_FILENAME "/dev/z80drv" - -// Constants for the Sharp MZ80A MZF file format. -#define MZF_HEADER_SIZE 128 // Size of the MZF header. -#define MZF_ATTRIBUTE 0x00 // Code Type, 01 = Machine Code. -#define MZF_FILENAME 0x01 // Title/Name (17 bytes). -#define MZF_FILENAME_LEN 17 // Length of the filename, it is not NULL terminated, generally a CR can be taken as terminator but not guaranteed. -#define MZF_FILESIZE 0x12 // Size of program. -#define MZF_LOADADDR 0x14 // Load address of program. -#define MZF_EXECADDR 0x16 // Exec address of program. -#define MZF_COMMENT 0x18 // Comment, used for details of the file or startup code. -#define MZF_COMMENT_LEN 104 // Length of the comment field. -#define CMT_TYPE_OBJCD 0x001 // MZF contains a binary object. -#define CMT_TYPE_BTX1CD 0x002 // MZF contains a BASIC program. -#define CMT_TYPE_BTX2CD 0x005 // MZF contains a BASIC program. -#define CMT_TYPE_TZOBJCD0 0x0F8 // MZF contains a TZFS binary object for page 0. -#define CMT_TYPE_TZOBJCD1 0x0F9 -#define CMT_TYPE_TZOBJCD2 0x0FA -#define CMT_TYPE_TZOBJCD3 0x0FB -#define CMT_TYPE_TZOBJCD4 0x0FC -#define CMT_TYPE_TZOBJCD5 0x0FD -#define CMT_TYPE_TZOBJCD6 0x0FE -#define CMT_TYPE_TZOBJCD7 0x0FF // MZF contains a TZFS binary object for page 7. -#define MZ_CMT_ADDR 0x10F0 - -// Structure to define a Sharp MZ80A MZF directory structure. This header appears at the beginning of every Sharp MZ80A tape (and more recently archived/emulator) images. -// -typedef struct __attribute__((__packed__)) { - uint8_t attr; // MZF attribute describing the file. - uint8_t fileName[MZF_FILENAME_LEN]; // Each directory entry is the size of an MZF filename. - uint16_t fileSize; // Size of file. - uint16_t loadAddr; // Load address for the file. - uint16_t execAddr; // Execution address where the Z80 starts processing. - uint8_t comment[MZF_COMMENT_LEN]; // Text comment field but often contains a startup machine code program. -} t_svcDirEnt; - -// Possible commands to be issued to the Z80 driver. -enum CTRL_COMMANDS { - Z80_CMD_STOP = 0, - Z80_CMD_START = 1, - Z80_CMD_PAUSE = 2, - Z80_CMD_CONTINUE = 3, - Z80_CMD_RESET = 4, - Z80_CMD_SPEED = 5, - Z80_CMD_HOST_RAM = 6, - Z80_CMD_VIRTUAL_RAM = 7, - Z80_CMD_DUMP_MEMORY = 8, - Z80_CMD_MEMORY_TEST = 9, - CPLD_CMD_SEND_CMD = 10, - CPLD_CMD_SPI_TEST = 11, - CPLD_CMD_PRL_TEST = 12 -}; - - -// Shared memory between this process and the Z80 driver. -static t_Z80Ctrl *Z80Ctrl = NULL; - -// Method to obtain and return the output screen width. -// -uint8_t getScreenWidth(void) -{ - return(MAX_SCREEN_WIDTH); -} - -struct termios orig_termios; - -void reset_terminal_mode() -{ - tcsetattr(0, TCSANOW, &orig_termios); -} - -void set_conio_terminal_mode() -{ - struct termios new_termios; - - /* take two copies - one for now, one for later */ - tcgetattr(0, &orig_termios); - memcpy(&new_termios, &orig_termios, sizeof(new_termios)); - - /* register cleanup handler, and set the new terminal mode */ - atexit(reset_terminal_mode); - cfmakeraw(&new_termios); - tcsetattr(0, TCSANOW, &new_termios); -} - -int kbhit() -{ - struct timeval tv = { 0L, 0L }; - fd_set fds; - FD_ZERO(&fds); - FD_SET(0, &fds); - return select(1, &fds, NULL, NULL, &tv) > 0; -} - -int getch(uint8_t wait) -{ - int r; - unsigned char c; - - if(wait != 0 || (wait == 0 && kbhit())) - { - if ((r = read(0, &c, sizeof(c))) < 0) { - return r; - } else { - return c; - } - } - return 0; -} - -void delay(int number_of_seconds) -{ - // Converting time into milli_seconds - int milli_seconds = 1000 * number_of_seconds; - - // Storing start time - clock_t start_time = clock(); - - // looping till required time is not achieved - while (clock() < start_time + milli_seconds); -} - -// Function to dump out a given section of memory via the UART. -// -int memoryDump(uint32_t memaddr, uint32_t memsize, uint8_t memoryFlag, uint32_t memwidth, uint32_t dispaddr, uint8_t dispwidth) -{ - uint8_t displayWidth = dispwidth;; - uint32_t pnt = memaddr; - uint32_t endAddr = memaddr + memsize; - uint32_t addr = dispaddr; - uint32_t i = 0; - //uint32_t data; - int8_t keyIn; - int result = -1; - char c = 0; - - // Sanity check. memoryFlag == 0 required kernel driver to dump so we exit as it cannot be performed here. - if(memoryFlag == 0) - return(-1); - - // Reconfigure terminal to allow non-blocking key input. - // - set_conio_terminal_mode(); - - // If not set, calculate output line width according to connected display width. - // - if(displayWidth == 0) - { - switch(getScreenWidth()) - { - case 40: - displayWidth = 8; - break; - case 80: - displayWidth = 16; - break; - default: - displayWidth = 32; - break; - } - } - - while (1) - { - printf("%08lX", addr); // print address - printf(": "); - - // print hexadecimal data - for (i=0; i < displayWidth; ) - { - switch(memwidth) - { - case 16: - if(pnt+i < endAddr) - printf("%04X", memoryFlag == 1 ? (uint16_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint16_t)Z80Ctrl->page[pnt+i] : (uint16_t)Z80Ctrl->iopage[pnt+i]); - else - printf(" "); - i++; - break; - - case 32: - if(pnt+i < endAddr) - printf("%08lX", memoryFlag == 1 ? (uint32_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint32_t)Z80Ctrl->page[pnt+i] : (uint32_t)Z80Ctrl->iopage[pnt+i]); - else - printf(" "); - i++; - break; - - case 8: - default: - if(pnt+i < endAddr) - printf("%02X", memoryFlag == 1 ? (uint8_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint8_t)Z80Ctrl->page[pnt+i] : (uint8_t)Z80Ctrl->iopage[pnt+i]); - else - printf(" "); - i++; - break; - } - fputc((char)' ', stdout); - } - - // print ascii data - printf(" |"); - - // print single ascii char - for (i=0; i < displayWidth; i++) - { - c = memoryFlag == 1 ? (char)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (char)Z80Ctrl->page[pnt+i] : (char)Z80Ctrl->iopage[pnt+i]; - if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~')) - fputc((char)c, stdout); - else - fputc((char)' ', stdout); - } - - printf("|\r\n"); - fflush(stdout); - - // Move on one row. - pnt += displayWidth; - addr += displayWidth; - - // User abort (ESC), pause (Space) or all done? - // - keyIn = getch(0); - if(keyIn == ' ') - { - do { - keyIn = getch(0); - } while(keyIn != ' ' && keyIn != 0x1b); - } - // Escape key pressed, exit with 0 to indicate this to caller. - if (keyIn == 0x1b) - { - sleep(1); - result = 0; - goto memoryDumpExit; - } - - // End of buffer, exit the loop. - if(pnt >= (memaddr + memsize)) - { - break; - } - } - - // Normal exit, return -1 to show no key pressed. -memoryDumpExit: - reset_terminal_mode(); - return(result); -} - -// Method to load a program or data file into the Z80 memory. First load into Virtual memory and then trigger a sync to bring Host RAM in line. -// -int z80load(int fdZ80, char *fileName) -{ - // Locals. - struct ioctlCmd ioctlCmd; - int ret = 0; - t_svcDirEnt mzfHeader; - - // Pause the Z80. - // - ioctlCmd.cmd = IOCTL_CMD_Z80_PAUSE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - // Open the file and read directly into the Virtual memory via the share. - FILE *ptr; - ptr = fopen(fileName, "rb"); - if(ptr) - { -printf("File:%s\n", fileName); - // First the header. - fread((uint8_t *)&mzfHeader, MZF_HEADER_SIZE, 1, ptr); -printf("Load:%x\n", mzfHeader.loadAddr); - if(mzfHeader.loadAddr > 0x1000) - { -printf("Memcpy:%x,%x\n", mzfHeader.loadAddr, mzfHeader.fileSize); - // Copy in the header. - memcpy((uint8_t *)&Z80Ctrl->memory[MZ_CMT_ADDR], (uint8_t *)&mzfHeader, MZF_HEADER_SIZE); - -printf("Memcpy:%x,%x\n", mzfHeader.loadAddr, mzfHeader.fileSize); - // Now read in the data. - fread(&Z80Ctrl->memory[mzfHeader.loadAddr], mzfHeader.fileSize, 1, ptr); -printf("Memcpy:%x,%x\n", mzfHeader.loadAddr, mzfHeader.fileSize); - printf("Loaded %s, Size:%04x, Addr:%04x, Exec:%04x\n", fileName, mzfHeader.fileSize, mzfHeader.loadAddr, mzfHeader.execAddr); - } - - // Sync the loaded image from Virtual memory to hard memory. - ioctlCmd.cmd = IOCTL_CMD_SYNC_TO_HOST_RAM; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - // Resume Z80 processing. - // - ioctlCmd.cmd = IOCTL_CMD_Z80_CONTINUE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - } - else - printf("Couldnt open file\n"); - - return ret; -} - -// Method to request basic Z80 operations. -// -int ctrlCmd(int fdZ80, enum CTRL_COMMANDS cmd, long param1, long param2, long param3) -{ - // Locals. - struct ioctlCmd ioctlCmd; - uint32_t idx; - int ret = 0; - - switch(cmd) - { - case Z80_CMD_STOP: - // Use IOCTL to request Z80 to Stop (power off) processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_START: - // Use IOCTL to request Z80 to Start (power on) processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_START; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_PAUSE: - // Use IOCTL to request Z80 to pause processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_PAUSE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_CONTINUE: - // Use IOCTL to request Z80 continue processing. - ioctlCmd.cmd = IOCTL_CMD_Z80_CONTINUE; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_RESET: - // Use IOCTL to request Z80 reset. - ioctlCmd.cmd = IOCTL_CMD_Z80_RESET; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_SPEED: - // Check value is in range. - for(idx=1; idx < 256; idx+=idx) - { - if((uint32_t)param1 == idx) break; - } - if(idx == 256) - { - printf("Speed factor is illegal. It must be a multiple value of the original CPU clock, ie. 1x, 2x, 4x etc\n"); - ret = -1; - } else - { - // Use IOCTL to request Z80 cpu freq change. - ioctlCmd.speed.speedMultiplier = (uint32_t)param1; - ioctlCmd.cmd = IOCTL_CMD_Z80_CPU_FREQ; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - } - break; - case CPLD_CMD_SEND_CMD: - // Build up the IOCTL command to request the given data is sent to the CPLD. - ioctlCmd.cmd = IOCTL_CMD_CPLD_CMD; - ioctlCmd.cpld.cmd = (uint32_t)param1; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_DUMP_MEMORY: - // If virtual memory, we can dump it via the shared memory segment. - if((uint8_t)param1) - { - memoryDump((uint32_t)param2, (uint32_t)param3, (uint8_t)param1, (uint8_t)param1 == 2 || (uint8_t)param1 == 3 ? 32 : 8, (uint32_t)param2, 0); - } else - { - // Build an IOCTL command to get the driver to dump the memory. - ioctlCmd.cmd = IOCTL_CMD_DUMP_MEMORY; - ioctlCmd.addr.start = (uint32_t)param2; - ioctlCmd.addr.end = (uint32_t)param2+(uint32_t)param3; - ioctlCmd.addr.size = (uint32_t)param3; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - } - break; - case Z80_CMD_HOST_RAM: - // Use IOCTL to request change to host RAM. - ioctlCmd.cmd = IOCTL_CMD_USE_HOST_RAM; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_VIRTUAL_RAM: - // Use IOCTL to request change to host RAM. - ioctlCmd.cmd = IOCTL_CMD_USE_VIRTUAL_RAM; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case Z80_CMD_MEMORY_TEST: - // Send command to test the SPI. - ioctlCmd.cmd = IOCTL_CMD_Z80_MEMTEST; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case CPLD_CMD_PRL_TEST: - // Send command to test the SPI. - ioctlCmd.cmd = IOCTL_CMD_PRL_TEST; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - case CPLD_CMD_SPI_TEST: - // Send command to test the SPI. - ioctlCmd.cmd = IOCTL_CMD_SPI_TEST; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - break; - - default: - printf("Command not supported!\n"); - ret = -1; - break; - } - - return ret; -} - -// Method to perform some simple tests on the Z80 emulator. -// -int z80test(int fdZ80) -{ - // Locals. - struct ioctlCmd ioctlCmd; - int ret = 0; - - // Stop the Z80. - // -printf("Send STOP\n"); - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - FILE *ptr; - ptr = fopen("/customer/mz700.rom", "rb"); - if(ptr) - { - fread(&Z80Ctrl->memory, 65536, 1, ptr); - } else printf("Couldnt open file\n"); - - // Configure the Z80. - // -printf("Send SETPC\n"); - ioctlCmd.z80.pc = 0; - ioctl(fdZ80, IOCTL_CMD_SETPC, &ioctlCmd); - - memoryDump(0 , 65536, 1, 8, 0, 0); - - // Start the Z80. - // -printf("Send START\n"); - ioctlCmd.cmd = IOCTL_CMD_Z80_START; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - delay(10); - -printf("Send STOP\n"); - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - memoryDump(0, 65536, 1, 8, 0, 0); -out: - return ret; -} - -// Output usage screen. So mamy commands you do need to be prompted!! -void showArgs(char *progName, struct optparse *options) -{ - printf("%s %s %s %s\n\n", progName, VERSION, COPYRIGHT, AUTHOR); - printf("Synopsis:\n"); - printf("%s --help # This help screen.\n", progName); - printf(" --cmd = RESET # Reset the Z80\n"); - printf(" = STOP # Stop and power off the Z80\n"); - printf(" = START # Power on and start the Z80\n"); - printf(" = PAUSE # Pause running Z80\n"); - printf(" = CONTINUE # Continue Z80 execution\n"); - printf(" = HOSTRAM # Use HOST DRAM\n"); - printf(" = VIRTRAM # Use Virtual RAM\n"); - printf(" = SPEED --speed <1, 2, 4, 8, 16, 32, 64, 128> # In Virtual RAM mode, set CPU speed to base clock x factor.\n"); - printf(" = LOADMZF --file # Load MZF file into memory.\n"); - printf(" = DUMP --start <24bit addr> --end <24bit addr> --virtual <0 - Host RAM, 1 = Virtual RAM, 2 = PageTable, 3 = IOPageTable>\n"); - printf(" = CPLDCMD --data <32bit command> # Send adhoc 32bit command to CPLD.\n"); - printf(" = Z80TEST # Perform various debugging tests\n"); - printf(" = SPITEST # Perform SPI testing\n"); - printf(" = PRLTEST # Perform Parallel Bus testing\n"); - printf(" = Z80MEMTEST # Perform HOST memory tests.\n"); - -} - -int main(int argc, char *argv[]) -{ - int fdZ80; - char buff[64]; - char cmd[64] = { 0 }; - char fileName[256] = { 0 }; - int opt; - long hexData = 0; - long speedMultiplier = 1; - long startAddr = 0x0000; - long endAddr = 0x1000; - int virtualMemory = 0; - int helpFlag = 0; - int verboseFlag = 0; - - // Define parameters to be processed. - struct optparse options; - static struct optparse_long long_options[] = - { - {"help", 'h', OPTPARSE_NONE}, - {"cmd", 'c', OPTPARSE_REQUIRED}, - {"file", 'f', OPTPARSE_REQUIRED}, - {"data", 'd', OPTPARSE_REQUIRED}, - {"speed", 'S', OPTPARSE_REQUIRED}, - {"virtual", 'V', OPTPARSE_REQUIRED}, - {"start", 's', OPTPARSE_REQUIRED}, - {"end", 'e', OPTPARSE_REQUIRED}, - {"verbose", 'v', OPTPARSE_NONE}, - {0} - }; - - // Parse the command line options. - // - optparse_init(&options, argv); - while((opt = optparse_long(&options, long_options, NULL)) != -1) - { - switch(opt) - { - // Hex data. - case 'd': - hexData = strtol(options.optarg, NULL, 0); - //printf("Hex data:%08x\n", hexData); - break; - - // Start address for memory operations. - case 's': - startAddr = strtol(options.optarg, NULL, 0); - //printf("Start Addr:%04x\n", startAddr); - break; - - // Speed multiplication factor for CPU governor when running in virtual memory. - case 'S': - speedMultiplier = strtol(options.optarg, NULL, 0); - //printf("Speed = base freq x %d\n", speedFactor); - break; - - // End address for memory operations. - case 'e': - endAddr = strtol(options.optarg, NULL, 0); - //printf("End Addr:%04x\n", endAddr); - break; - - // Virtual memory flag, 0 = host, 1 = virtual memory, 2 = page table, 3 = iopage table. - case 'V': - virtualMemory = atoi(options.optarg); - break; - - // Filename. - case 'f': - strcpy(fileName, options.optarg); - break; - - // Command to execute. - case 'c': - strcpy(cmd, options.optarg); - break; - - // Verbose mode. - case 'v': - verboseFlag = 1; - break; - - // Command help needed. - case 'h': - helpFlag = 1; - showArgs(argv[0], &options); - break; - - // Unrecognised, show synopsis. - case '?': - showArgs(argv[0], &options); - printf("%s: %s\n", argv[0], options.errmsg); - return(1); - } - } - - // Open the z80drv driver and attach to its shared memory, basically the Z80 control structure which includes the virtual Z80 memory. - fdZ80 = open(DEVICE_FILENAME, O_RDWR|O_NDELAY); - if(fdZ80 >= 0) - { - Z80Ctrl = (t_Z80Ctrl *)mmap(0, sizeof(t_Z80Ctrl), PROT_READ | PROT_WRITE, MAP_SHARED, fdZ80, 0); - if(Z80Ctrl == (void *)-1) - { - printf("Failed to attach to the Z80 Control structure, cannot continue, exitting....\n"); - close(fdZ80); - exit(1); - } - } else - { - printf("Failed to open the Z80 Driver, exitting...\n"); - exit(1); - } - - // Basic string to method mapping. Started off with just 1 or two but has grown, may need a table! - if(strcasecmp(cmd, "LOADMZF") == 0) - { - z80load(fdZ80, fileName); - } else - if(strcasecmp(cmd, "RESET") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_RESET, 0, 0, 0); - } else - if(strcasecmp(cmd, "STOP") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_STOP, 0, 0, 0); - } else - if(strcasecmp(cmd, "START") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_START, 0, 0, 0); - } else - if(strcasecmp(cmd, "PAUSE") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_PAUSE, 0, 0, 0); - } else - if(strcasecmp(cmd, "CONTINUE") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_CONTINUE, 0, 0, 0); - } else - if(strcasecmp(cmd, "SPEED") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_SPEED, speedMultiplier, 0, 0); - } else - if(strcasecmp(cmd, "DUMP") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_DUMP_MEMORY, virtualMemory, startAddr, (endAddr - startAddr)); - } else - if(strcasecmp(cmd, "HOSTRAM") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_HOST_RAM, 0, 0, 0); - } else - if(strcasecmp(cmd, "VIRTRAM") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_VIRTUAL_RAM, 0, 0, 0); - } else - if(strcasecmp(cmd, "CPLDCMD") == 0) - { - ctrlCmd(fdZ80, CPLD_CMD_SEND_CMD, hexData, 0, 0); - } else - - // Test methods, if the code is built-in to the driver. - if(strcasecmp(cmd, "Z80TEST") == 0) - { - z80test(fdZ80); - } else - if(strcasecmp(cmd, "SPITEST") == 0) - { - ctrlCmd(fdZ80, CPLD_CMD_SPI_TEST, 0, 0, 0); - } else - if(strcasecmp(cmd, "PRLTEST") == 0) - { - ctrlCmd(fdZ80, CPLD_CMD_PRL_TEST, 0, 0, 0); - } else - if(strcasecmp(cmd, "Z80MEMTEST") == 0) - { - ctrlCmd(fdZ80, Z80_CMD_MEMORY_TEST, 0, 0, 0); - } - else - { - showArgs(argv[0], &options); - printf("No command given, nothing done!\n"); - } - - // Unmap shared memory and close the device. - munmap(Z80Ctrl, sizeof(t_Z80Ctrl)); - close(fdZ80); - - return(0); -} diff --git a/software/FusionX/src/z80drv/MZ700/z80driver.c b/software/FusionX/src/z80drv/MZ700/z80driver.c deleted file mode 100644 index f1159cf18..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80driver.c +++ /dev/null @@ -1,1447 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80driver.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 Driver -// This file contains the methods used to create a linux device driver which provides -// the services of a Z80 CPU emulation and the control of an underlying Z80'less host -// system. In essence this driver is the host Z80 CPU. -// Credits: Zilog Z80 CPU Emulator v0.2 written by Manuel Sainz de Baranda y Goñi -// The Z80 CPU Emulator is the heart of this driver and in all ways, is compatible with -// the original Z80. -// Copyright: (c) 2019-2022 Philip Smart -// (c) 1999-2022 Manuel Sainz de Baranda y Goñi -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80io.h" -#include "z80menu.h" -#include "z80driver.h" - -#include -#include -#include - -/* Meta Information */ -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Philip D Smart"); -MODULE_DESCRIPTION("Z80 CPU Emulator and Hardware Interface Driver"); - -/* Global variables for the threads */ -static struct task_struct *kthread_z80; -static int threadId_z80 = 1; - -// Device class and major numbers. -static struct class *class; -static struct device *device; -static int major; - -// CPU Instance. -static Z80 Z80CPU; - -// Z80 Control data. -static t_Z80Ctrl *Z80Ctrl = NULL; - -// Runtime control of the CPU. As the CPU runs in a detached thread on core 1, the cpu needs to be suspended before any external -// operations can take place. This is achieved with the runtime mutex. -enum Z80_RUN_STATES Z80RunMode; -static struct mutex Z80RunModeMutex; -static DEFINE_MUTEX(Z80DRV_MUTEX); - - -//------------------------------------------------------------------------------------------------------------------------------- -// -// Z80 CPU Kernel Logic. -// -// THe Z80 CPU is initialised and set running, processing instructions either from the underlying host hardware or internal -// memory. The configuration and flow is controlled via the Z80Ctrl structure which is User Space accessible. -// -//------------------------------------------------------------------------------------------------------------------------------- - -// Method to read a byte from physical hardware or internal virtual memory/devices. -// The page table indicates the source and the read is processed accordingly. -static zuint8 z80_read(void *context, zuint16 address) -{ - // Locals. - // - zuint8 data; - uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - Z_UNUSED(context) - - // Only read if the address is in physical RAM. - if(isPhysical(address)) - { - // Commence cycle to retrieve the data from Real RAM. - // Optimise SPI according to last address sent to CPLD. - if(addrDiff >=0 && addrDiff < 8) - { - SPI_SEND8((CPLD_CMD_READ_ADDR + addrDiff)); - Z80Ctrl->z80PrevAddr += addrDiff; - } else - { - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = address; - } - while(CPLD_READY() == 0); - data = READ_CPLD_DATA_IN(); - - // Pause until the Last T-State is detected. - while(CPLD_LAST_TSTATE() == 0); - } - else if(isVirtualHW(address)) - { - // Virtual Hardware - call the handler. - switch(realAddress(address)) - { - default: - break; - } - } - else if(isVirtualMemory(address)) - { - // Retrieve data from virtual memory. - data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); - } - - // Keyport data? Store. - if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 8 && (data & 0x41) == 0) - { - Z80Ctrl->keyportShiftCtrl = 0x01; - } else - if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 0 && (data & 0x80) == 0) - { - Z80Ctrl->keyportHotKey = 0x01; - } - return(data); -} - -// Method to write a byte to physical hardware or internal virtual memory or devices. -// The page table indicates the target and the write is processed accordingly. -static void z80_write(void *context, zuint16 address, zuint8 data) -{ - // Locals. - uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - Z_UNUSED(context) - - // To detect Hotkey presses, we need to store the keboard strobe data and on keydata read. - if(isHW(address) && address == 0xE000) - { - Z80Ctrl->keyportStrobe = data; - } - - // Write to physical host? - if(isPhysical(address)) - { - // Commence cycle to write the data to real RAM. - // Optimise SPI according to last address sent to CPLD. - if(addrDiff >=0 && addrDiff < 8) - { - SPI_SEND16((data << 8) | (CPLD_CMD_WRITE_ADDR + addrDiff)); - Z80Ctrl->z80PrevAddr += addrDiff; - } else - { - SPI_SEND32((uint32_t)address << 16 | data << 8 | CPLD_CMD_WRITE_ADDR); - Z80Ctrl->z80PrevAddr = address; - } - - // Write-thru to virtual memory if we update real memory. - // if(isPhysicalRAM(address)) - // writeVirtualRAM(address, data); - - // Pause until the Last T-State is detected. - while(CPLD_LAST_TSTATE() == 0); - } - else if(isVirtualHW(address)) - { - // Virtual Hardware - call the handler. - switch(realAddress(address)) - { - default: - break; - } - } - else if(isVirtualRAM(address)) - { - // Update virtual memory. - writeVirtualRAM(address, data); - } - // Cannot write to virtual ROM so no logic. -} - -// Primary Opcode fetch method. This method is called each time a single or multi-byte opcode is -// encountered. Opcode data is retrieved via the z80_fetch method. -// -// Depending on the address and the configured page map, the opcode is fetched from hardware -// or internal virtual memory. As this method is the primary timing method for Z80 instructions -// (read/write methods dont affect the timing so much as long as they operate in less than the read/write -// cycle of an original Z80). -// Initially the timing on the virtual memory is set by a governor delay but this will be updated to a more -// precise M/T-State cycle per instruction type delay. -static zuint8 z80_fetch_opcode(void *context, zuint16 address) -{ - // Locals. - zuint8 opcode = 0x00; - uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - volatile uint32_t idx; // Leave as volatile otherwise optimiser will optimise out the delay code. - Z_UNUSED(context) - - // Normally only opcode fetches occur in RAM but allow any physical address as it could be a Z80 programming trick. - if(isPhysical(address)) - { - // Commence cycle to fetch the opcode from potentially Real RAM albeit it could be any physical hardware. - // Optimise SPI according to last address sent to CPLD. - if(addrDiff >=0 && addrDiff < 8) - { - SPI_SEND8((CPLD_CMD_FETCH_ADDR + addrDiff)); - Z80Ctrl->z80PrevAddr += addrDiff; - } else - { - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_FETCH_ADDR); - Z80Ctrl->z80PrevAddr = address; - } - while(CPLD_READY() == 0); - opcode = READ_CPLD_DATA_IN(); - - // Pause until the Last T-State is detected. - while(CPLD_LAST_TSTATE() == 0); - } else - // Virtual fetches only occur in memory as we are not emulating original hardware. - if(isVirtualMemory(address)) - { - for(idx=0; idx < Z80Ctrl->cpuGovernorDelay; idx++); - - // Retrieve data from virtual memory. - opcode = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); - } - return(opcode); -} - -// Method similar to z80_read, kept seperate to avoid additional what-if logic and doesnt require virtual hardware logic. -// -static zuint8 z80_fetch(void *context, zuint16 address) -{ - // Locals. - // - zuint8 data = 0x00; - uint16_t addrDiff = (uint16_t)address - Z80Ctrl->z80PrevAddr; - Z_UNUSED(context) - - // Normally only opcode fetches occur in RAM but allow any physical address as it could be a Z80 programming trick. - if(isPhysical(address)) - { - // Commence cycle to retrieve the data from Real RAM. - // Optimise SPI according to last address sent to CPLD. - if(addrDiff >=0 && addrDiff < 8) - { - SPI_SEND8((CPLD_CMD_READ_ADDR + addrDiff)); - Z80Ctrl->z80PrevAddr += addrDiff; - } else - { - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = address; - } - while(CPLD_READY() == 0); - data = READ_CPLD_DATA_IN(); - - // Pause until the Last T-State is detected. - while(CPLD_LAST_TSTATE() == 0); - } else - if(isVirtualMemory(address)) - { - // Retrieve data from virtual memory. - data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); - } - -// // Keyport data? Store. -// if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 8 && (data & 0x41) == 0) -// { -// Z80Ctrl->keyportShiftCtrl = 0x01; -// } else -// if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 0 && (data & 0x80) == 0) -// { -// Z80Ctrl->keyportHotKey = 0x01; -// } - return(data); -} - -// Method to perform a Z80 input operation. This normally goes to hardware and the CPLD executes the required cycle. -// Some ports are dedicated virtual ports providing virtual services to the host computer/application. These are intercepted -// and processed in this driver. -static zuint8 z80_in(void *context, zuint16 port) -{ - // Locals. - zuint8 value; - uint16_t portDiff = (uint16_t)port - Z80Ctrl->z80PrevPort; - Z_UNUSED(context) - - printk("z80_in\n"); - // Physical port go direct to hardware to retrieve value. - if(isPhysicalIO(port)) - { - // Commence cycle to retrieve the value from the I/O port. Port contains the 16bit BC value. - // Optimise SPI according to last port sent to CPLD. - if(portDiff >=0 && portDiff < 8) - { - SPI_SEND8((CPLD_CMD_READIO_ADDR + portDiff)); - Z80Ctrl->z80PrevPort += portDiff; - } else - { - SPI_SEND32((uint32_t)port << 16 | CPLD_CMD_READIO_ADDR); - Z80Ctrl->z80PrevPort = port; - } - - // Whilst waiting for the CPLD, we now determine if this is a memory management port and update the memory page if required. - switch(port & 0x00FF) - { - // Port is not a memory management port. - default: - break; - } - - // Finally ensure the data from the port is ready and retrieve it. - while(CPLD_READY() == 0); - value = READ_CPLD_DATA_IN(); - } else - // Virtual I/O Port. - { - // Virtual I/O - call the handler. - switch(realPort(port)) - { - default: - value = 0x00; - break; - } - } - return(value); -} - -// Method to perform a Z80 output operation. This normally goes to hardware and the CPLD executes the required cycle. -// Some ports are dedicated virtual ports providing virtual services to the host computer/application. These are intercepted -// and processed in this driver. -// There are also ports which are both hardware and need mirroring in software. These ports, typically memory mapping ports. -// when activated in the hardware need to be mirrored in the page table so correct virtual memory is used when addressed. -static void z80_out(void *context, zuint16 port, zuint8 value) -{ - // Locals. - uint32_t idx; - uint16_t portDiff = (uint16_t)port - Z80Ctrl->z80PrevPort; - Z_UNUSED(context) - - // Physical port go direct to hardware to retrieve value. - if(isPhysicalIO(port)) - { - // Commence cycle to write the value to the I/O port. Port contains the 16bit BC value. - // Optimise SPI according to last port sent to CPLD. - if(portDiff >=0 && portDiff < 8) - { - SPI_SEND16((value << 8) | (CPLD_CMD_WRITEIO_ADDR + portDiff)); - Z80Ctrl->z80PrevPort += portDiff; -//pr_info("OUT:0x%x(%x)=0x%x\n", port, portDiff, value); - } else - { - SPI_SEND32((uint32_t)port << 16 | value << 8 | CPLD_CMD_WRITEIO_ADDR); - Z80Ctrl->z80PrevPort = port; -//pr_info("OUT:0x%x=0x%x\n", port, value); - } - - // Determine if this is a memory management port and update the memory page if required. - switch(port & 0x00FF) - { - // Enable lower 4K block as DRAM - case IO_ADDR_E0: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - break; - - // Enable upper 12K block, including Video/Memory Mapped peripherals area, as DRAM. - case IO_ADDR_E1: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - // MZ-700 mode we only work in first 64K block. - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - } - break; - - // Enable Monitor ROM in lower 4K block - case IO_ADDR_E2: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - break; - - // Enable Video RAM and Memory mapped peripherals in upper 12K block. - case IO_ADDR_E3: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Reset to power on condition memory map. - case IO_ADDR_E4: - // Lower 4K set to Monitor ROM. - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - if(!Z80Ctrl->inhibitMode) - { - // Upper 12K to hardware. - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Inhibit. Backup current page data in region 0xD000-0xFFFF and inhibit it. - case IO_ADDR_E5: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - backupMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_INHIBIT, idx); - } - Z80Ctrl->inhibitMode = 1; - break; - - // Restore D000-FFFF to its original state. - case IO_ADDR_E6: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - restoreMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - } - Z80Ctrl->inhibitMode = 0; - break; - - // Port is not a memory management port. - default: - break; - } - } else - if(isVirtualIO(port)) - { - // MZ700 memory mode switch. - // - // MZ-700 - // |0000:0FFF|1000:CFFF|D000:FFFF - // ------------------------------ - // OUT 0xE0 = |DRAM | | - // OUT 0xE1 = | | |DRAM - // OUT 0xE2 = |MONITOR | | - // OUT 0xE3 = | | |Memory Mapped I/O - // OUT 0xE4 = |MONITOR |DRAM |Memory Mapped I/O - // OUT 0xE5 = | | |Inhibit - // OUT 0xE6 = | | | - // - // = Return to the state prior to the complimentary command being invoked. - - // Determine if this is a memory management port and update the memory page if required. - switch(port & 0x00FF) - { - // Enable lower 4K block as DRAM - case IO_ADDR_E0: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - break; - - // Enable upper 12K block, including Video/Memory Mapped peripherals area, as DRAM. - case IO_ADDR_E1: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - // MZ-700 mode we only work in first 64K block. - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - } - break; - - // Enable MOnitor ROM in lower 4K block - case IO_ADDR_E2: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - break; - - // Enable Video RAM and Memory mapped peripherals in upper 12K block. - case IO_ADDR_E3: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Reset to power on condition memory map. - case IO_ADDR_E4: - // Lower 4K set to Monitor ROM. - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - if(!Z80Ctrl->inhibitMode) - { - // Upper 12K to hardware. - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Inhibit. Backup current page data in region 0xD000-0xFFFF and inhibit it. - case IO_ADDR_E5: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - backupMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_INHIBIT, idx); - } - Z80Ctrl->inhibitMode = 1; - break; - - // Restore D000-FFFF to its original state. - case IO_ADDR_E6: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - restoreMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - } - Z80Ctrl->inhibitMode = 0; - break; - - // Port is not a memory management port. - default: - break; - } - } else - { - // Virtual I/O - call the handler. - switch(realPort(port)) - { - default: - break; - } - } -} - -// NOP - No Operation method. This instruction is used for timing, padding out an application or during -// HALT cycles to ensure Refresh occurs. -// If the address is configured as hardware (via the page table) then a refresh cycle is requested otherwise -// nothing to be done. -static zuint8 z80_nop(void *context, zuint16 address) -{ - // Locals. - Z_UNUSED(context) - - if(isPhysical(address)) - { - // If autorefresh is not enabled, send a single refresh request. - if(Z80Ctrl->refreshDRAM == 0) - SPI_SEND8(CPLD_CMD_REFRESH); - } - return 0x00; -} - -// HALT - CPU executes a HALT instruction which results in the HALT line going active low and then it enters -// a state executing NOP instructions to ensure DRAM refresh until a reset or INT event. -static void z80_halt(void *context, zboolean state) -{ - // Locals. - Z_UNUSED(context) Z_UNUSED(state) - - // Inform CPLD of halt state. - printk("z80_halt\n"); - SPI_SEND8(CPLD_CMD_HALT); - Z80CPU.cycles = Z80_MAXIMUM_CYCLES; -} - -// Methods below are not yet implemented, Work In Progress! -static zuint8 z80_nmia(void *context, zuint16 address) -{ - Z_UNUSED(context) - printk("z80_nmia\n"); - return 0x00; -} -static zuint8 z80_inta(void *context, zuint16 address) -{ - Z_UNUSED(context) - printk("z80_inta\n"); - return 0x00; -} -static zuint8 z80_intFetch(void *context, zuint16 address) -{ - Z_UNUSED(context) - printk("z80_int_fetch\n"); - return 0x00; -} - -// Z80 CPU Emulation Thread -// ------------------------ -// This is a kernel thread, bound to CPU 1 with IRQ's disabled. -// The Z80 is controlled by a mutex protected variable to define run, stop, pause and terminate modes. -int thread_z80(void * thread_nr) -{ - // Locals. - uint8_t canRun = 0; - int t_nr = *(int *) thread_nr; - //struct sched_param param = {.sched_priority = 99}; - spinlock_t spinLock; - unsigned long flags; - - // Initialise spinlock and disable IRQ's. We should be the only process running on core 1. - spin_lock_init(&spinLock); - spin_lock_irqsave(&spinLock, flags); - - // Assign this emulation to high priority realtime scheduling. Also the task will be assigned to an isolated CPU. - //sched_setscheduler(current, SCHED_RR, ¶m); - - // Run the CPU forever or until a stop occurs. - while(!kthread_should_stop()) - { - // Run the Z80 emulation if enabled. - if(canRun) z80_run(&Z80CPU, 100); - - // Reset pressed? - if(CPLD_RESET()) - { - z80_instant_reset(&Z80CPU); - setupMemory(Z80Ctrl->defaultPageMode); - - // Wait for release before restarting CPU. - while(CPLD_RESET()); - } else - { - // Update state to indicate request has been actioned. - mutex_lock(&Z80RunModeMutex); - if(Z80RunMode == Z80_STOP) Z80RunMode = Z80_STOPPED; - if(Z80RunMode == Z80_PAUSE) Z80RunMode = Z80_PAUSED; - if(Z80RunMode == Z80_CONTINUE) Z80RunMode = Z80_RUNNING; - if(Z80RunMode == Z80_RUNNING) canRun=1; else canRun=0; - mutex_unlock(&Z80RunModeMutex); - - // Hotkey pressed? Bring up user menu. - if(Z80Ctrl->keyportShiftCtrl && Z80Ctrl->keyportHotKey) - { - z80menu(); - Z80Ctrl->keyportShiftCtrl = 0; - Z80Ctrl->keyportHotKey = 0; - } - } - } - - // Release spinlock as we are unloading driver. - spin_unlock_irqrestore(&spinLock, flags); - printk("kthread - Z80 Thread %d finished execution!\n", t_nr); - return 0; -} - - -//------------------------------------------------------------------------------------------------------------------------------- -// -// User space driver access. -// -//------------------------------------------------------------------------------------------------------------------------------- - - -// Device close. -// When a user space application terminates or closes the z80drv device driver, this function is called -// to close any open connections, memory and variables required to handle the user space application -// requests. -static int z80drv_release(struct inode *inodep, struct file *filep) -{ - // Locals. - - mutex_unlock(&Z80DRV_MUTEX); - //pr_info("z80drv: Device successfully closed\n"); - - return(0); -} - -// Device open. -// When a user space application open's the z80drv device driver, this function is called -// to initialise and allocate any required memory or devices prior to servicing requests from the -// user space application. -static int z80drv_open(struct inode *inodep, struct file *filep) -{ - // Locals. - int ret = 0; - - if(!mutex_trylock(&Z80DRV_MUTEX)) - { - pr_alert("z80drv: device busy!\n"); - ret = -EBUSY; - goto out; - } - - //pr_info("z80drv: Device opened\n"); - -out: - return(ret); -} - -// Map shared memory. -// The z80drv allocates on the stack a chunk of memory and control variables which is used to control the Z80 Emulation state -// and provide it with internal 'virtual memory'. This virtual memory is either used as the core Z80 memory or as banked extensions -// to the host DRAM. -// The user space application is able to bind with the shared memory to perform tasks such as load/save of applications. -static int z80drv_mmap(struct file *filp, struct vm_area_struct *vma) -{ - // Locals. - int ret = 0; - struct page *page = NULL; - unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start); - - // Make sure requested size is within range of the Z80 control structure. The kernel may page align it so the size may be bigger than - // the structure. - if(size < sizeof(t_Z80Ctrl) || size > sizeof(t_Z80Ctrl)*2) - { - ret = -EINVAL; - goto out; - } - - // Map the memory and exit. - page = virt_to_page((unsigned long)Z80Ctrl + (vma->vm_pgoff << PAGE_SHIFT)); - ret = remap_pfn_range(vma, vma->vm_start, page_to_pfn(page), size, vma->vm_page_prot); - if (ret != 0) - { - goto out; - } - -out: - return ret; -} - -// Device read. -// This method allows an application which opens the z80drv driver to read data in a stream. It is here for -// possible future use. -static ssize_t z80drv_read(struct file *filep, char *buffer, size_t len, loff_t *offset) -{ - // Locals. - int ret; - - if (len > Z80_VIRTUAL_RAM_SIZE) - { - pr_info("read overflow!\n"); - ret = -EFAULT; - goto out; - } - - if (copy_to_user(buffer, Z80Ctrl, len) == 0) - { - pr_info("z80drv: copy %u char to the user\n", len); - ret = len; - } else - { - ret = -EFAULT; - } - -out: - return ret; -} - -// Device write. -// This method allows an application which opens the z80drv driver to write stream data. It is here for -// possible future use. -static ssize_t z80drv_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) -{ - // Locals. - int ret; - - if (copy_from_user(Z80Ctrl, buffer, len)) - { - pr_err("z80drv: write fault!\n"); - ret = -EFAULT; - goto out; - } - pr_info("z80drv: copy %d char from the user\n", len); - ret = len; - -out: - return ret; -} - -// Function to dump out a given section of the physical host memory. -// -int memoryDump(uint32_t memaddr, uint32_t memsize, uint32_t dispaddr, uint8_t dispwidth) -{ - uint8_t displayWidth = dispwidth; - uint32_t pnt = memaddr; - uint32_t endAddr = memaddr + memsize; - uint32_t addr = dispaddr; - uint8_t data; - uint32_t i = 0; - int result = -1; - char c = 0; - - // If not set, calculate output line width according to connected display width. - // - if(displayWidth == 0) - { - switch(MAX_SCREEN_WIDTH) - { - case 40: - displayWidth = 8; - break; - case 80: - displayWidth = 16; - break; - default: - displayWidth = 32; - break; - } - } - - while (1) - { - printk(KERN_INFO "%08X", addr); // print address - printk(KERN_CONT ": "); - - // print hexadecimal data - for (i=0; i < displayWidth; ) - { - if(pnt+i < endAddr) - { - SPI_SEND32((uint16_t)(pnt+i) << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = pnt+i; - while(CPLD_READY() == 0); - data = READ_CPLD_DATA_IN(); - printk(KERN_CONT "%02X", data); - } - else - printk(KERN_CONT " "); - i++; - - printk(KERN_CONT " "); - } - - // print ascii data - printk(KERN_CONT " |"); - - // print single ascii char - for (i=0; i < displayWidth; i++) - { - SPI_SEND32((uint16_t)(pnt+i) << 16 | CPLD_CMD_READ_ADDR); - Z80Ctrl->z80PrevAddr = pnt+i; - while(CPLD_READY() == 0); - c = (char)READ_CPLD_DATA_IN(); - if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~')) - printk(KERN_CONT "%c", (char)c); - else - printk(KERN_CONT " "); - } - - printk(KERN_CONT "|\n"); - - // Move on one row. - pnt += displayWidth; - addr += displayWidth; - - // End of buffer, exit the loop. - if(pnt >= (memaddr + memsize)) - { - break; - } - } - - return(result); -} - -// Method to setup a default memory/IO profile. This profile will be changed by the host processing and also can be tweaked -// by the z80ctrl application. -// -void setupMemory(enum Z80_MEMORY_PROFILE mode) -{ - // Locals. - uint32_t idx; - - if(mode == USE_PHYSICAL_RAM) - { - // Initialise the page pointers and memory to use physical RAM. - for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x1000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - else if(idx >= 0x1000 && idx < 0xD000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - - // Video RAM labelled as HW as we dont want to cache it. - else if(idx >= 0xD000 && idx < 0xE000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - for(idx=0x0000; idx < 0x10000; idx++) - { - Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; - } - // Cancel refresh as using physical RAM for program automatically refreshes DRAM. - Z80Ctrl->refreshDRAM = 0; - } - else if(mode == USE_VIRTUAL_RAM) - { - // Initialise the page pointers and memory to use virtual RAM. - for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x1000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - else if(idx >= 0x1000 && idx < 0xD000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - - // Video RAM labelled as HW as we dont want to cache it. - else if(idx >= 0xD000 && idx < 0xE000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - for(idx=0x0000; idx < 0x10000; idx++) - { - if((idx & 0x00FF) >= IO_ADDR_E0 && (idx & 0x00FF) < IO_ADDR_E7) - Z80Ctrl->iopage[idx] = idx | IO_TYPE_VIRTUAL_HW; - else - Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; - } - // Enable refresh as using virtual RAM stops refresh of host DRAM. - Z80Ctrl->refreshDRAM = 1; - } - - // Enable autorefresh if refreshDRAM is set. - SPI_SEND8(Z80Ctrl->refreshDRAM == 1 ? CPLD_CMD_SET_AUTO_REFRESH : CPLD_CMD_CLEAR_AUTO_REFRESH); - - // Inhibit mode disabled. - Z80Ctrl->inhibitMode = 0; - return; -} - -// IOCTL Method -// This method allows User Space application to control the Z80 CPU and internal functionality of the -// device driver. This is the preferred control method along with the shared memory segment for the driver. -static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) -{ - // Locals. - struct ioctlCmd ioctlCmd; - uint16_t idx; - uint32_t tmp; - enum Z80_RUN_STATES currentRunMode; - enum Z80_RUN_STATES nextRunMode; - - // Get current running mode so any operations on the Z80 return it to original mode unless action overrides it. - mutex_lock(&Z80RunModeMutex); currentRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - - switch(cmd) - { - // Basic commands. - case IOCTL_CMD_SEND: - if(copy_from_user(&ioctlCmd, (int32_t *)arg, sizeof(ioctlCmd))) - printk("IOCTL - Couldnt retrieve command!\n"); - else - { - //printk("IOCTL - Command (%08x)\n", ioctlCmd.cmd); - switch(ioctlCmd.cmd) - { - // Command to stop the Z80 CPU and power off. - case IOCTL_CMD_Z80_STOP: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - z80_power(&Z80CPU, FALSE); - Z80_PC(Z80CPU) = 0; - printk("Z80 stopped.\n"); - break; - - // Command to power on and start the Z80 CPU. - case IOCTL_CMD_Z80_START: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_RUNNING; mutex_unlock(&Z80RunModeMutex); - - z80_power(&Z80CPU, TRUE); - printk("Z80 started.\n"); - break; - - // Command to pause the Z80. - case IOCTL_CMD_Z80_PAUSE: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_PAUSE; mutex_unlock(&Z80RunModeMutex); - printk("Z80 paused.\n"); - break; - - // Command to release a paused Z80. - case IOCTL_CMD_Z80_CONTINUE: - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_CONTINUE; mutex_unlock(&Z80RunModeMutex); - printk("Z80 running.\n"); - break; - - // Command to perform a CPU reset. - case IOCTL_CMD_Z80_RESET: - // Stop the CPU prior to reset. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - z80_instant_reset(&Z80CPU); - setupMemory(Z80Ctrl->defaultPageMode); - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Reset.\n"); - break; - - // Command to setup the page table to use host memory and physical hardware. - case IOCTL_CMD_USE_HOST_RAM: - // Stop the CPU prior to memory reconfiguration. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - Z80Ctrl->defaultPageMode = USE_PHYSICAL_RAM; - setupMemory(Z80Ctrl->defaultPageMode); - z80_instant_reset(&Z80CPU); - - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Set to use Host Memory.\n"); - break; - - // Command to setup the page table to use virtual memory, only physical hardware is accessed on the host. - case IOCTL_CMD_USE_VIRTUAL_RAM: - // Stop the CPU prior to memory reconfiguration. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - Z80Ctrl->defaultPageMode = USE_VIRTUAL_RAM; - setupMemory(Z80Ctrl->defaultPageMode); - z80_instant_reset(&Z80CPU); - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Set to use Virtual Memory.\n"); - break; - - // Command to synchronise virtual memory to host DRAM. - case IOCTL_CMD_SYNC_TO_HOST_RAM: - // Stop the CPU prior to memory sync. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Copy virtual memory to host DRAM. - for(idx=0x1000; idx < 0xD000; idx++) - { - SPI_SEND32((uint32_t)idx << 16 | Z80Ctrl->memory[idx] << 8 | CPLD_CMD_WRITE_ADDR); - } - - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Z80 Host DRAM syncd with Virtual Memory.\n"); - break; - - // Command to dump out host memory. - case IOCTL_CMD_DUMP_MEMORY: - // Need to suspend the Z80 otherwise we will get memory clashes. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_PAUSE; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_PAUSE); - - // Dump out the physical memory address. - memoryDump(ioctlCmd.addr.start, ioctlCmd.addr.end - ioctlCmd.addr.start, ioctlCmd.addr.start, 0); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to set the governor delay to approximate real Z80 cpu frequencies when running in virtual memory. - case IOCTL_CMD_Z80_CPU_FREQ: - switch(ioctlCmd.speed.speedMultiplier) - { - case 2: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_7MHZ; - break; - - case 4: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_14MHZ; - break; - - case 8: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_28MHZ; - break; - - case 16: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_56MHZ; - break; - - case 32: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_112MHZ; - break; - - case 64: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_224MHZ; - break; - - case 128: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_448MHZ; - break; - - case 1: - default: - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_3_54MHZ; - break; - } - break; - - // Command to set the Z80 CPU Program Counter value. - case IOCTL_CMD_SETPC: - // Stop the CPU prior to PC change. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - Z80_PC(Z80CPU) = ioctlCmd.z80.pc; - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - printk("Set PC to %04x\n", ioctlCmd.z80.pc); - break; - - // Method to send adhoc commands to the CPLD, ie for switching active display etc. - case IOCTL_CMD_CPLD_CMD: - // Stop the CPU prior to sending a direct command to the CPLD. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Send the command, small delay then send NOP to retrieve the response. - SPI_SEND32(ioctlCmd.cpld.cmd); - udelay(10); - z80io_SPI_Send32(0x00000000, &tmp); - pr_info("CPLD RX:%08x\n", tmp); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to run a series of SOM to CPLD SPI tests. - case IOCTL_CMD_SPI_TEST: - // Stop the CPU prior to SPI testing. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Perform SPI Tests. - z80io_SPI_Test(); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to run a series of SOM to CPLD Parallel Bus tests. - case IOCTL_CMD_PRL_TEST: - // Stop the CPU prior to SPI testing. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Perform Parallel Bus tests. - z80io_PRL_Test(); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - // Command to run a series of Z80 host memory tests to assess the performance of the SOM->CPLD interface. - case IOCTL_CMD_Z80_MEMTEST: - // Stop the CPU prior to Host memory testing. - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - do { mutex_lock(&Z80RunModeMutex); nextRunMode = Z80RunMode ; mutex_unlock(&Z80RunModeMutex); - } while(nextRunMode == Z80_STOP); - - // Perform host memory tests. - z80io_Z80_TestMemory(); - - // Z80 can continue. - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - break; - - default: - break; - } - } - break; - - default: - printk("IOCTL - Unhandled Command (%08x)\n", ioctlCmd.cmd); - break; - - } - return 0; -} - -// Structure to declare public API methods. -// Standard Linux device driver structure to declare accessible methods within the driver. -static const struct file_operations z80drv_fops = { - .open = z80drv_open, - .read = z80drv_read, - .write = z80drv_write, - .release = z80drv_release, - .mmap = z80drv_mmap, - .unlocked_ioctl = z80drv_ioctl, - .owner = THIS_MODULE, -}; - -// Initialisation. -// This is the entry point into the device driver when loaded into the kernel. -// The method intialises any required hardware (ie. GPIO's, SPI etc), memory and the Z80 -// Emulation. It also allocates the Major and Minor device numbers and sets up the -// device in /dev. -static int __init ModuleInit(void) -{ - // Locals. - int idx; - int ret = 0; - - // Setup the Z80 handlers. - Z80CPU.context = NULL; - Z80CPU.fetch_opcode = z80_fetch_opcode; - Z80CPU.fetch = z80_fetch; - Z80CPU.read = z80_read; - Z80CPU.write = z80_write; - Z80CPU.nop = z80_nop; - Z80CPU.in = z80_in; - Z80CPU.out = z80_out; - Z80CPU.halt = z80_halt; - Z80CPU.nmia = z80_nmia; - Z80CPU.inta = z80_inta; - Z80CPU.int_fetch = z80_intFetch; - Z80CPU.ld_i_a = NULL; - Z80CPU.ld_r_a = NULL; - Z80CPU.reti = NULL; - Z80CPU.retn = NULL; - Z80CPU.hook = NULL; - Z80CPU.illegal = NULL; - Z80CPU.options = Z80_MODEL_ZILOG_NMOS; - - mutex_init(&Z80DRV_MUTEX); - - // Get device Major number. - major = register_chrdev(0, DEVICE_NAME, &z80drv_fops); - if (major < 0) { - pr_info("z80drv: fail to register major number!"); - ret = major; - goto initExit; - } - - class = class_create(THIS_MODULE, CLASS_NAME); - if (IS_ERR(class)){ - unregister_chrdev(major, DEVICE_NAME); - pr_info("z80drv: failed to register device class"); - ret = PTR_ERR(class); - goto initExit; - } - - device = device_create(class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); - if (IS_ERR(device)) { - class_destroy(class); - unregister_chrdev(major, DEVICE_NAME); - ret = PTR_ERR(device); - goto initExit; - } - - // Allocate the Z80 memory to be shared between this kernel space driver and the controlling user space application. - Z80Ctrl = (t_Z80Ctrl *)kmalloc(sizeof(t_Z80Ctrl), GFP_KERNEL); - if (Z80Ctrl == NULL) - { - pr_info("z80drv: failed to allocate memory!"); - ret = -ENOMEM; - goto initExit; - } - - // Initialise the hardware to host interface. - z80io_init(); - - // Initialise the virtual RAM from the HOST DRAM. This is to maintain compatibility as some applications (in my experience) have - // bugs, which Im putting down to not initialising variables. The host DRAM is in a pattern of 0x00..0x00, 0xFF..0xFF repeating - // when first powered on. - for(idx=0; idx < Z80_VIRTUAL_RAM_SIZE; idx++) - { - if(idx >= 0x1000 && idx < 0xD000) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->memory[idx] = z80io_PRL_Read8(1); - } else - { - Z80Ctrl->memory[idx] = 0x00; - } - } - Z80Ctrl->memory[0x1200] = 0x01; - Z80Ctrl->memory[0x1201] = 0x86; - Z80Ctrl->memory[0x1202] = 0xf2; - Z80Ctrl->memory[0x1203] = 0x3e; - Z80Ctrl->memory[0x1204] = 0x15; - Z80Ctrl->memory[0x1205] = 0x3d; - Z80Ctrl->memory[0x1206] = 0x20; - Z80Ctrl->memory[0x1207] = 0xfd; - Z80Ctrl->memory[0x1208] = 0x0b; - Z80Ctrl->memory[0x1209] = 0x78; - Z80Ctrl->memory[0x120a] = 0xb1; - Z80Ctrl->memory[0x120b] = 0x20; - Z80Ctrl->memory[0x120c] = 0xf6; - Z80Ctrl->memory[0x120d] = 0xc3; - Z80Ctrl->memory[0x120e] = 0x00; - Z80Ctrl->memory[0x120f] = 0x00; - - // Copy the host BIOS into the Virtual RAM. - for(idx=0; idx < 0x1000; idx++) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->memory[Z80_VIRTUAL_RAM_SIZE+idx] = z80io_PRL_Read8(1); - } - - // Setup Refresh so that an automatic refresh mode is performed by the CPLD whilst running in Virtual memory. This is necessary as opcode fetches from - // host memory, by the CPLD, normally performs the refresh and when running in virtual memory, these refresh cycles arent performed. - Z80Ctrl->refreshDRAM = 1; - - // Setup the governor delay, it is the delay per opcode fetch to restrict the Z80 CPU to a given speed. - Z80Ctrl->cpuGovernorDelay = MZ700_INSTRUCTION_DELAY_3_54MHZ; - - // Setup the default Page Mode. This is needed if an event such as a reset occurs which needs to return the page and iotable back to default. - Z80Ctrl->defaultPageMode = USE_VIRTUAL_RAM; - - // Setup memory profile to use internal virtual RAM (SOM kernel RAM rather than HOST DRAM). - setupMemory(Z80Ctrl->defaultPageMode); - - // Initialse run control. - mutex_init(&Z80RunModeMutex); - mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_STOP; mutex_unlock(&Z80RunModeMutex); - - // Setup the address and port history for communicating addresses to the CPLD. Used to shorten the instruction length to increase latency. - Z80Ctrl->z80PrevAddr = 0xFFFF; - Z80Ctrl->z80PrevPort = 0xFFFF; - - // Initialse hotkey detection variables. - Z80Ctrl->keyportStrobe = 0x00; - Z80Ctrl->keyportShiftCtrl = 0x00; - Z80Ctrl->keyportHotKey = 0x00; - - // PC to start and power on the CPU - Z80_PC(Z80CPU) = 0; - z80_power(&Z80CPU, TRUE); - - // Create thread to run the Z80 cpu. - kthread_z80 = kthread_create(thread_z80, &threadId_z80, "kthread_z80"); - if(kthread_z80 != NULL) - { - printk("kthread - Thread Z80 was created, waking...!\n"); - kthread_bind(kthread_z80, 1); - wake_up_process(kthread_z80); - } - else { - printk("kthread - Thread Z80 could not be created!\n"); - ret = -1; - goto initExit; - } - -initExit: - return ret; -} - -// Exit -// This method is called when the device driver is removed from the kernel with the rmmod command. -// It is responsible for closing and freeing all allocated memory, terminating all threads and removing -// the device from the /dev directory. -static void __exit ModuleExit(void) -{ - // Stop the internal threads. - //printk("kthread - Stop Z80 thread\n"); - kthread_stop(kthread_z80); - - // Return the memory used for the Z80 'virtual memory' and control variables. - kfree(Z80Ctrl); - - // Nothing to be done for the hardware. - - // Cleanup and remove the device. - mutex_destroy(&Z80DRV_MUTEX); - device_destroy(class, MKDEV(major, 0)); - class_unregister(class); - class_destroy(class); - unregister_chrdev(major, DEVICE_NAME); - - //pr_info("z80drv: unregistered!\n"); -} - -module_init(ModuleInit); -module_exit(ModuleExit); diff --git a/software/FusionX/src/z80drv/MZ700/z80driver.h b/software/FusionX/src/z80drv/MZ700/z80driver.h deleted file mode 100644 index ede3d0db0..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80driver.h +++ /dev/null @@ -1,284 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80driver.h -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 Driver -// This file contains the declarations used in the z80drv device driver. -// -// Credits: Zilog Z80 CPU Emulator v0.2 written by Manuel Sainz de Baranda y Goñi -// The Z80 CPU Emulator is the heart of this driver and in all ways, is compatible with -// the original Z80. -// Copyright: (c) 2019-2022 Philip Smart -// (c) 1999-2022 Manuel Sainz de Baranda y Goñi -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef Z80DRIVER_H -#define Z80DRIVER_H - -// Constants. -#define Z80_VIRTUAL_ROM_SIZE 16384 // Sized to maximum ROM which is the MZ-800 ROM. -#define Z80_VIRTUAL_RAM_SIZE (65536 * 8) // (PAGE_SIZE * 2) // max size mmaped to userspace -#define Z80_VIRTUAL_MEMORY_SIZE Z80_VIRTUAL_RAM_SIZE + Z80_VIRTUAL_ROM_SIZE -#define Z80_MEMORY_PAGE_SIZE 16 -#define MAX_SCREEN_WIDTH 132 -#define DEVICE_NAME "z80drv" -#define CLASS_NAME "mogu" - -// Memory and IO page types. Used to create a memory page which maps type of address space to real address space on host or virtual memory. -#define MEMORY_TYPE_VIRTUAL_MASK 0x00FFFFFF -#define MEMORY_TYPE_REAL_MASK 0x0000FFFF -#define IO_TYPE_MASK 0x0000FFFF -#define MEMORY_TYPE_INHIBIT 0x00000000 -#define MEMORY_TYPE_PHYSICAL_RAM 0x80000000 -#define MEMORY_TYPE_PHYSICAL_ROM 0x40000000 -#define MEMORY_TYPE_PHYSICAL_VRAM 0x20000000 -#define MEMORY_TYPE_PHYSICAL_HW 0x10000000 -#define MEMORY_TYPE_VIRTUAL_RAM 0x08000000 -#define MEMORY_TYPE_VIRTUAL_ROM 0x04000000 -#define MEMORY_TYPE_VIRTUAL_HW 0x02000000 -#define IO_TYPE_PHYSICAL_HW 0x80000000 -#define IO_TYPE_VIRTUAL_HW 0x40000000 - - -// Approximate governor delays to regulate emulated CPU speed. -#define MZ700_INSTRUCTION_DELAY_3_54MHZ 253 -#define MZ700_INSTRUCTION_DELAY_7MHZ 126 -#define MZ700_INSTRUCTION_DELAY_14MHZ 63 -#define MZ700_INSTRUCTION_DELAY_28MHZ 32 -#define MZ700_INSTRUCTION_DELAY_56MHZ 16 -#define MZ700_INSTRUCTION_DELAY_112MHZ 8 -#define MZ700_INSTRUCTION_DELAY_224MHZ 4 -#define MZ700_INSTRUCTION_DELAY_448MHZ 1 - -// IOCTL commands. Passed from user space using the IOCTL method to command the driver to perform an action. -#define IOCTL_CMD_Z80_STOP 's' -#define IOCTL_CMD_Z80_START 'S' -#define IOCTL_CMD_Z80_PAUSE 'P' -#define IOCTL_CMD_Z80_RESET 'R' -#define IOCTL_CMD_Z80_CONTINUE 'C' -#define IOCTL_CMD_USE_HOST_RAM 'x' -#define IOCTL_CMD_USE_VIRTUAL_RAM 'X' -#define IOCTL_CMD_DUMP_MEMORY 'M' -#define IOCTL_CMD_Z80_CPU_FREQ 'F' -#define IOCTL_CMD_CPLD_CMD 'z' -#define IOCTL_CMD_SEND _IOW('c', 'c', int32_t *) -#define IOCTL_CMD_SETPC _IOW('p', 'p', int32_t *) -#define IOCTL_CMD_SYNC_TO_HOST_RAM 'V' -#define IOCTL_CMD_SPI_TEST '1' -#define IOCTL_CMD_PRL_TEST '2' -#define IOCTL_CMD_Z80_MEMTEST '3' - - - -// Chip Select map MZ80K-MZ700. -// -// 0000 - 0FFF = CS_ROMni : R/W : MZ80K/A/700 = Monitor ROM or RAM (MZ80A rom swap) -// 1000 - CFFF = CS_RAMni : R/W : MZ80K/A/700 = RAM -// C000 - CFFF = CS_ROMni : R/W : MZ80A = Monitor ROM (MZ80A rom swap) -// D000 - D7FF = CS_VRAMni : R/W : MZ80K/A/700 = VRAM -// D800 - DFFF = CS_VRAMni : R/W : MZ700 = Colour VRAM (MZ700) -// E000 - E003 = CS_8255n : R/W : MZ80K/A/700 = 8255 -// E004 - E007 = CS_8254n : R/W : MZ80K/A/700 = 8254 -// E008 - E00B = CS_LS367n : R/W : MZ80K/A/700 = LS367 -// E00C - E00F = CS_ESWPn : R : MZ80A = Memory Swap (MZ80A) -// E010 - E013 = CS_ESWPn : R : MZ80A = Reset Memory Swap (MZ80A) -// E014 = CS_E5n : R/W : MZ80A/700 = Normal CRT display (in Video Controller) -// E015 = CS_E6n : R/W : MZ80A/700 = Reverse CRT display (in Video Controller) -// E200 - E2FF = : R/W : MZ80A/700 = VRAM roll up/roll down. -// E800 - EFFF = : R/W : MZ80K/A/700 = User ROM socket or DD Eprom (MZ700) -// F000 - F7FF = : R/W : MZ80K/A/700 = Floppy Disk interface. -// F800 - FFFF = : R/W : MZ80K/A/700 = Floppy Disk interface. -// -// Chip Select map MZ800 -// -// FC - FF = CS_PIOn : R/W : MZ800/MZ1500 = Z80 PIO Printer Interface -// F2 = CS_PSG0n : W : MZ800/MZ1500 = Programable Sound Generator, MZ-800 = Mono, MZ-1500 = Left Channel -// F3 = CS_PSG1n : W : MZ1500 = Programable Sound Generator, MZ-1500 = Right Channel -// E9 = CS_PSG(X)n: W : MZ1500 = Simultaneous write to both PSG's. -// F0 - F1 = CS_JOYSTK : R : MZ800 = Joystick 1 and 2 -// CC = CS_GWF : W : MZ800 = CRTC GWF Write format Register -// CD = CS_GRF : W : MZ800 = CRTC GRF Read format Register -// CE = CS_GDMD : W : MZ800 = CRTC GDMD Mode Register -// CF = CS_GCRTC : W : MZ800 = CRTC GCRTC Control Register -// D4 - D7 = CS -// D000 - DFFF - -// MZ700/MZ800 memory mode switch? -// -// MZ-700 MZ-800 -// |0000:0FFF|1000:1FFF|1000:CFFF|C000:CFFF|D000:FFFF |0000:7FFF|1000:1FFF|2000:7FFF|8000:BFFF|C000:CFFF|C000:DFFF|E000:FFFF -// -------------------------------------------------- ---------------------------------------------------------------------- -// OUT 0xE0 = |DRAM | | | | |DRAM | | | | | | -// OUT 0xE1 = | | | | |DRAM | | | | | | |DRAM -// OUT 0xE2 = |MONITOR | | | | |MONITOR | | | | | | -// OUT 0xE3 = | | | | |Memory Mapped I/O | | | | | | |Upper MONITOR ROM -// OUT 0xE4 = |MONITOR | |DRAM | |Memory Mapped I/O |MONITOR |CGROM |DRAM |VRAM | |DRAM |Upper MONITOR ROM -// OUT 0xE5 = | | | | |Inhibit | | | | | | |Inhibit -// OUT 0xE6 = | | | | | | | | | | | | -// IN 0xE0 = | |CGROM* | |VRAM* | | |CGROM | |VRAM | | | -// IN 0xE1 = | |DRAM | |DRAM | | | | |DRAM | | | -// -// = Return to the state prior to the complimentary command being invoked. -// * = MZ-800 host only. - -// Macros to lookup and test to see if a given memory block or IO byte is of a given type. Also macros to read/write to the memory block and IO byte. -#define MEMORY_BLOCK_GRANULARITY 0x800 -#define MEMORY_BLOCK_SLOTS (0x10000 / MEMORY_BLOCK_GRANULARITY) -#define MEMORY_BLOCK_MASK (0x10000 - MEMORY_BLOCK_GRANULARITY) -#define MEMORY_BLOCK_SHIFT 11 -#define getPageData(a) (Z80Ctrl->page[(a & 0xF800) >> MEMORY_BLOCK_SHIFT]) -#define getIOPageData(a) (Z80Ctrl->iopage[(a & 0xFFFF]) -#define getPageType(a, mask) (getPageData(a) & mask) -#define getPageAddr(a, mask) ((getPageData(a) & mask) + (a & (MEMORY_BLOCK_GRANULARITY-1))) -#define getIOPageType(a, mask) (getIOPageData(a) & mask) -#define getIOPageAddr(a, mask) (getIOPageData(a) & mask) -#define realAddress(a) (Z80Ctrl->page[getPageAddr(a, MEMORY_TYPE_REAL_MASK)]) -#define realPort(a) (Z80Ctrl->iopage[a & 0xFFFF] & IO_TYPE_MASK) -#define isPhysicalRAM(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_RAM)) -#define isPhysicalVRAM(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_VRAM)) -#define isPhysicalROM(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_ROM)) -#define isPhysicalMemory(a) (getPageType(a, (MEMORY_TYPE_PHYSICAL_ROM | MEMORY_TYPE_PHYSICAL_RAM | MEMORY_TYPE_PHYSICAL_VRAM))]) -#define isPhysicalHW(a) (getPageType(a, MEMORY_TYPE_PHYSICAL_HW)) -#define isPhysical(a) (getPageType(a, (MEMORY_TYPE_PHYSICAL_HW | MEMORY_TYPE_PHYSICAL_ROM | MEMORY_TYPE_PHYSICAL_RAM | MEMORY_TYPE_PHYSICAL_VRAM))) -#define isPhysicalIO(a) (Z80Ctrl->iopage[a & 0xFFFF] & IO_TYPE_PHYSICAL_HW) -#define isVirtualRAM(a) (getPageType(a, MEMORY_TYPE_VIRTUAL_RAM)) -#define isVirtualROM(a) (getPageType(a, MEMORY_TYPE_VIRTUAL_ROM)) -#define isVirtualMemory(a) (getPageType(a, (MEMORY_TYPE_VIRTUAL_ROM | MEMORY_TYPE_VIRTUAL_RAM))) -#define isVirtualHW(a) (getPageType(a, MEMORY_TYPE_VIRTUAL_HW)) -#define isVirtualIO(a) (Z80Ctrl->iopage[a & 0xFFFF] & IO_TYPE_VIRTUAL_HW) -#define isHW(a) (getPageType(a, (MEMORY_TYPE_PHYSICAL_HW | MEMORY_TYPE_VIRTUAL_HW))) -#define readVirtualRAM(a) (Z80Ctrl->memory[ getPageAddr(a, MEMORY_TYPE_VIRTUAL_MASK) ]) -#define readVirtualROM(a) (Z80Ctrl->memory[ getPageAddr(a, MEMORY_TYPE_VIRTUAL_MASK) + Z80_VIRTUAL_RAM_SIZE ]) -#define writeVirtualRAM(a, d) { Z80Ctrl->memory[ getPageAddr(a, MEMORY_TYPE_VIRTUAL_MASK) ] = d; } -#define setMemoryType(_block_,_type_,_addr_) { Z80Ctrl->page[_block_] = _type_ | _addr_; } -#define backupMemoryType(_block_) { Z80Ctrl->shadowPage[_block_] = Z80Ctrl->page[_block_]; } -#define restoreMemoryType(_block_) { Z80Ctrl->page[_block_] = Z80Ctrl->shadowPage[_block_]; } - -#define IO_ADDR_E0 0xE0 -#define IO_ADDR_E1 0xE1 -#define IO_ADDR_E2 0xE2 -#define IO_ADDR_E3 0xE3 -#define IO_ADDR_E4 0xE4 -#define IO_ADDR_E5 0xE5 -#define IO_ADDR_E6 0xE6 -#define IO_ADDR_E7 0xE7 - - -enum Z80_RUN_STATES { - Z80_STOP = 0x00, - Z80_STOPPED = 0x01, - Z80_PAUSE = 0x02, - Z80_PAUSED = 0x03, - Z80_CONTINUE = 0x04, - Z80_RUNNING = 0x05, -}; -enum Z80_MEMORY_PROFILE { - USE_PHYSICAL_RAM = 0x00, - USE_VIRTUAL_RAM = 0x01 -}; - -typedef struct { - // Main memory, linear but indexed as though it were banks in 1K pages. - uint8_t memory[Z80_VIRTUAL_MEMORY_SIZE]; - - // Page pointer map. - // - // Each pointer points to a byte or block of bytes in the Z80 Memory frame, 64K Real + Banked. - // This is currently set at a block of size 0x800 per memory pointer for the MZ-700. - // The LSB of the pointer is a direct memory index to a byte or block of bytes, the upper byte of the pointer indicates type of memory space. - // 0x80 - physical host RAM - // 0x40 - physical host ROM - // 0x20 - physical host VRAM - // 0x10 - physical host hardware - // 0x08 - virtual host RAM - // 0x04 - virtual host ROM - // 0x02 - virtual host hardware - // 16bit Input Address -> map -> Pointer to 24bit memory address + type flag. - // -> Pointer+ to 24bit memory address + type flag. - uint32_t page[MEMORY_BLOCK_SLOTS]; - uint32_t shadowPage[MEMORY_BLOCK_SLOTS]; - - // I/O Page map. - // - // This is a map to indicate the use of the I/O page and allow any required remapping. - // <0x80>FF - physical host hardware - // <0x40>FF - virtual host hardware - // 16bit Input Address -> map -> Actual 16bit address to use + type flag. - uint32_t iopage[65536]; - - // Default page mode configured. This value reflects the default page and iotable map. - uint8_t defaultPageMode; - - // Refresh DRAM mode. 1 = Refresh, 0 = No refresh. Only applicable when running code in virtual Kernel RAM. - uint8_t refreshDRAM; - - // Inhibit mode is where certain memory ranges are inhibitted. The memory page is set to inhibit and this flag - // blocks actions which arent allowed during inhibit. - uint8_t inhibitMode; - - // Address caching. Used to minimise instruction length sent to CPLD. - uint16_t z80PrevAddr; - uint16_t z80PrevPort; - - // Keyboard strobe and data. Required to detect hotkey press. - uint8_t keyportStrobe; - uint8_t keyportShiftCtrl; - uint8_t keyportHotKey; - - // Governor is the delay in a 32bit loop per Z80 opcode, used to govern execution speed when using virtual memory. - // This mechanism will eventually be tied into the M/T-state calculation for a more precise delay, but at the moment, - // with the Z80 assigned to an isolated CPU, it allows time sensitive tasks such as the tape recorder to work. - // The lower the value the faster the CPU speed. - uint32_t cpuGovernorDelay; -} t_Z80Ctrl; - -// IOCTL structure for passing data from user space to driver to perform commands. -// -struct z80_addr { - uint32_t start; - uint32_t end; - uint32_t size; -}; -struct z80_ctrl { - uint16_t pc; -}; -struct speed { - uint32_t speedMultiplier; -}; -struct cpld_ctrl { - uint32_t cmd; -}; -struct ioctlCmd { - int32_t cmd; - union { - struct z80_addr addr; - struct z80_ctrl z80; - struct speed speed; - struct cpld_ctrl cpld; - }; -}; - - -// Prototypes. -void setupMemory(enum Z80_MEMORY_PROFILE mode); - - -#endif diff --git a/software/FusionX/src/z80drv/MZ700/z80io.c b/software/FusionX/src/z80drv/MZ700/z80io.c deleted file mode 100644 index 08286199d..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80io.c +++ /dev/null @@ -1,428 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80io.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 IO Interface -// This file contains the methods used in interfacing the SOM to the Z80 socket -// and host hardware via a CPLD. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - - -//#include -//#include -//#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80io.h" - -#include -#include -#include -#include - -//------------------------------------------------------------------------------------------------------------------------------- -// -// User space driver access. -// -//------------------------------------------------------------------------------------------------------------------------------- - - - -// Initialise the SOM hardware used to communicate with the z80 socket and host hardware. -// The SOM interfaces to a CPLD which provides voltage level translation and also encapsulates the Z80 timing cycles as recreating -// them within the SOM is much more tricky. -// -// As this is an embedded device and performance/latency are priorities, minimal structured code is used to keep call stack and -// generated code to a mimimum without relying on the optimiser. -int z80io_init(void) -{ - // Locals. - int ret = 0; - - // Initialise GPIO. We call the HAL api to minimise time but for actual bit set/reset and read we go directly to registers to save time, increase throughput and minimise latency. - // Initialise the HAL. - MHal_GPIO_Init(); - - // Set the pads as GPIO devices. The HAL takes care of allocating and deallocating the padmux resources. - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_0); // Word (16bit) bidirectional bus. Default is read with data set. - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_1); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_2); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_3); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_4); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_5); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_6); - MHal_GPIO_Pad_Set(PAD_Z80IO_IN_DATA_7); - MHal_GPIO_Pad_Set(PAD_Z80IO_HIGH_BYTE); - //MHal_GPIO_Pad_Set(PAD_GPIO8); // SPIO 4wire control lines setup by the spidev driver but controlled directly in this driver. - //MHal_GPIO_Pad_Set(PAD_GPIO9); - //MHal_GPIO_Pad_Set(PAD_GPIO10); - //MHal_GPIO_Pad_Set(PAD_GPIO11); - MHal_GPIO_Pad_Set(PAD_Z80IO_READY); - MHal_GPIO_Pad_Set(PAD_Z80IO_LTSTATE); - MHal_GPIO_Pad_Set(PAD_Z80IO_BUSRQ); - MHal_GPIO_Pad_Set(PAD_Z80IO_BUSACK); - MHal_GPIO_Pad_Set(PAD_Z80IO_INT); - MHal_GPIO_Pad_Set(PAD_Z80IO_NMI); - MHal_GPIO_Pad_Set(PAD_Z80IO_WAIT); - MHal_GPIO_Pad_Set(PAD_Z80IO_RESET); - MHal_GPIO_Pad_Set(PAD_Z80IO_RSV1); -#ifdef NOTNEEDED - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_0); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_1); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_2); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_3); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_4); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_5); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_6); - MHal_GPIO_Pad_Set(PAD_Z80IO_OUT_DATA_7); - MHal_GPIO_Pad_Set(PAD_Z80IO_WRITE); -#endif - - // Set required input pads. - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_0); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_1); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_2); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_3); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_4); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_5); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_6); - MHal_GPIO_Pad_Odn(PAD_Z80IO_IN_DATA_7); - MHal_GPIO_Pad_Odn(PAD_Z80IO_READY); - MHal_GPIO_Pad_Odn(PAD_Z80IO_LTSTATE); - MHal_GPIO_Pad_Odn(PAD_Z80IO_BUSRQ); - MHal_GPIO_Pad_Odn(PAD_Z80IO_BUSACK); - MHal_GPIO_Pad_Odn(PAD_Z80IO_INT); - MHal_GPIO_Pad_Odn(PAD_Z80IO_NMI); - MHal_GPIO_Pad_Odn(PAD_Z80IO_WAIT); - MHal_GPIO_Pad_Odn(PAD_Z80IO_RESET); - MHal_GPIO_Pad_Odn(PAD_Z80IO_RSV1); - - // Set required output pads. -#ifdef NOTNEEDED - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_0); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_1); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_2); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_3); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_4); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_5); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_6); - MHal_GPIO_Pad_Oen(PAD_Z80IO_OUT_DATA_7); - MHal_GPIO_Pad_Oen(PAD_Z80IO_WRITE); - MHal_GPIO_Pull_High(PAD_Z80IO_WRITE); -#endif - - // Control signals. - MHal_GPIO_Pad_Oen(PAD_Z80IO_HIGH_BYTE); - MHal_GPIO_Pull_High(PAD_Z80IO_HIGH_BYTE); - - // Setup the MSPI0 device. - // - // Setup control, interrupts are not used. - MSPI_WRITE(MSPI_CTRL_OFFSET, MSPI_CPU_CLOCK_1_2 | MSPI_CTRL_CPOL_LOW | MSPI_CTRL_CPHA_HIGH | MSPI_CTRL_RESET | MSPI_CTRL_ENABLE_SPI); - - // Setup LSB First mode. - MSPI_WRITE(MSPI_LSB_FIRST_OFFSET, 0x0); - - // Setup clock. - CLK_WRITE(MSPI0_CLK_CFG, 0x1100) - - // Setup the frame size (all buffers to 8bits). - MSPI_WRITE(MSPI_FRAME_WBIT_OFFSET, 0xfff); - MSPI_WRITE(MSPI_FRAME_WBIT_OFFSET+1, 0xfff); - MSPI_WRITE(MSPI_FRAME_RBIT_OFFSET, 0xfff); - MSPI_WRITE(MSPI_FRAME_RBIT_OFFSET+1, 0xfff); - - // Setup Chip Selects to inactive. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Switch Video and Audio to host. - z80io_SPI_Send16(0x00f0, NULL); - - return ret; -} - - -//-------------------------------------------------------- -// Parallel bus Methods. -//-------------------------------------------------------- - -// Methods to read data from the parallel bus. -// The CPLD returns status and Z80 data on the 8bit bus as it is marginally quicker than retrieving it over the SPI bus. -// -inline uint8_t z80io_PRL_Read8(uint8_t dataFlag) -{ - // Locals. - uint8_t result = 0; - - // Byte according to flag. - if(dataFlag) - SET_CPLD_READ_DATA() - else - SET_CPLD_READ_STATUS() - - // Read the input registers and set value accordingly. - result = READ_CPLD_DATA_IN(); - - // Return 16bit value read from CPLD. - return(result); -} - -inline uint16_t z80io_PRL_Read16(void) -{ - // Locals. - uint16_t result = 0; - - // Low byte first. - CLEAR_CPLD_HIGH_BYTE(); - - // Read the input registers and set value accordingly. - result = (uint16_t)READ_CPLD_DATA_IN(); - - // High byte next. - SET_CPLD_HIGH_BYTE(); - - // Read the input registers and set value accordingly. - result |= (uint16_t)(READ_CPLD_DATA_IN() << 8); - - // Return 16bit value read from CPLD. - return(result); -} - - -// Parallel Bus methods were tried and tested but due to the GPIO bits being controlled by individual registers per bit, the setup time was longer -// than the transmission time of SPI. These methods are thus deprecated and a fusion of SPI and 8bit parallel is now used. -#ifdef NOTNEEDED -inline uint8_t z80io_PRL_Send8(uint8_t txData) -{ - // Locals. - // - - // Low byte only. - MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE].r_out) &= (~gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out); - - // Setup data. - if(txData & 0x0080) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_7].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_7].m_out); } - if(txData & 0x0040) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_6].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_6].m_out); } - if(txData & 0x0020) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_5].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_5].m_out); } - if(txData & 0x0010) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_4].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_4].m_out); } - if(txData & 0x0008) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_3].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_3].m_out); } - if(txData & 0x0004) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_2].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_2].m_out); } - if(txData & 0x0002) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_1].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_1].m_out); } - if(txData & 0x0001) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_0].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_0].m_out); } - - // Clock data. - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) &= (~gpio_table[PAD_Z80IO_WRITE ].m_out); - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) |= gpio_table[PAD_Z80IO_WRITE ].m_out; - - return(0); -} - -inline uint8_t z80io_PRL_Send16(uint16_t txData) -{ - // Locals. - // - - // Low byte first. - MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE].r_out) &= (~gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out); - - // Setup data. - if(txData & 0x0080) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_7].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_7].m_out); } - if(txData & 0x0040) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_6].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_6].m_out); } - if(txData & 0x0020) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_5].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_5].m_out); } - if(txData & 0x0010) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_4].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_4].m_out); } - if(txData & 0x0008) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_3].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_3].m_out); } - if(txData & 0x0004) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_2].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_2].m_out); } - if(txData & 0x0002) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_1].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_1].m_out); } - if(txData & 0x0001) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_0].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_0].m_out); } - - // Clock data. - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) &= (~gpio_table[PAD_Z80IO_WRITE ].m_out); - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) |= gpio_table[PAD_Z80IO_WRITE ].m_out; - - // High byte next. - MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE ].r_out) |= gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out; - - // Setup high byte. - if(txData & 0x8000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_7].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_7].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_7].m_out); } - if(txData & 0x4000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_6].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_6].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_6].m_out); } - if(txData & 0x2000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_5].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_5].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_5].m_out); } - if(txData & 0x1000) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_4].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_4].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_4].m_out); } - if(txData & 0x0800) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_3].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_3].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_3].m_out); } - if(txData & 0x0400) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_2].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_2].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_2].m_out); } - if(txData & 0x0200) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_1].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_1].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_1].m_out); } - if(txData & 0x0100) { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) |= gpio_table[PAD_Z80IO_OUT_DATA_0].m_out; } else { MHal_RIU_REG(gpio_table[PAD_Z80IO_OUT_DATA_0].r_out) &= (~gpio_table[PAD_Z80IO_OUT_DATA_0].m_out); } - - // Clock data. - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) &= (~gpio_table[PAD_Z80IO_WRITE ].m_out); - MHal_RIU_REG(gpio_table[PAD_Z80IO_WRITE].r_out) |= gpio_table[PAD_Z80IO_WRITE ].m_out; - - return(0); -} -#endif - - -//-------------------------------------------------------- -// SPI Methods. -//-------------------------------------------------------- - -// Methods to send 8,16 or 32 bits. Each method is seperate to minimise logic and execution time, 8bit being most sensitive. -// Macros have also been defined for inline inclusion which dont read back the response data. -// -uint8_t z80io_SPI_Send8(uint8_t txData, uint8_t *rxData) -{ - // Locals. - uint32_t timeout = MAX_CHECK_CNT; - - // Insert data into write buffers. - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)txData); - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); - - // Enable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); - - // Send. - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); - - // Wait for completion. - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) - { - if(--timeout == 0) - break; - } - - // Disable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Clear flag. - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); - - // Fetch data. - if(rxData != NULL) *rxData = (uint8_t)MSPI_READ(MSPI_FULL_DEPLUX_RD00); - - // Done. - return(timeout == 0); -} -uint8_t z80io_SPI_Send16(uint16_t txData, uint16_t *rxData) -{ - // Locals. - uint32_t timeout = MAX_CHECK_CNT; - - // Insert data into write buffers. - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, txData); - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); - - // Enable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); - - // Send. - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); - - // Wait for completion. - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) - { - if(--timeout == 0) - break; - } - - // Disable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Clear flag. - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); - - // Fetch data. - if(rxData != NULL) *rxData = MSPI_READ(MSPI_FULL_DEPLUX_RD00); - - // Done. - return(timeout == 0); -} -uint8_t z80io_SPI_Send32(uint32_t txData, uint32_t *rxData) -{ - // Locals. - uint32_t timeout = MAX_CHECK_CNT; - - // Insert data into write buffers. - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)txData); - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)(txData >> 16)); - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); - - // Enable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); - - // Send. - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); - - // Wait for completion. - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) - { - if(--timeout == 0) - break; - } - - // Disable SPI select. - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); - - // Clear flag. - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); - - // Fetch data. - if(rxData != NULL) *rxData = (uint32_t)(MSPI_READ(MSPI_FULL_DEPLUX_RD00) | (MSPI_READ(MSPI_FULL_DEPLUX_RD02) << 16)); - - // Done. - return(timeout == 0); -} - -//-------------------------------------------------------- -// Test Methods. -//-------------------------------------------------------- -#ifdef INCLUDE_TEST_METHODS -#include "z80io_test.c" -#else -uint8_t z80io_Z80_TestMemory(void) -{ - pr_info("Z80 Test Memory functionality not built-in.\n"); - return(0); -} -uint8_t z80io_SPI_Test(void) -{ - pr_info("SPI Test functionality not built-in.\n"); - return(0); -} -uint8_t z80io_PRL_Test(void) -{ - pr_info("Parallel Bus Test functionality not built-in.\n"); - return(0); -} -#endif diff --git a/software/FusionX/src/z80drv/MZ700/z80io.h b/software/FusionX/src/z80drv/MZ700/z80io.h deleted file mode 100755 index 9e5c1b0f2..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80io.h +++ /dev/null @@ -1,483 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80io.h -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 IO Interface -// This file contains the declarations used in interfacing the SOM to the Z80 socket -// and host hardware via a CPLD. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef Z80IO_H -#define Z80IO_H - -#ifdef __cplusplus - extern "C" { -#endif - -// Definitions to control compilation. -#define INCLUDE_TEST_METHODS 1 - -// CPLD Commands. -#define CPLD_CMD_FETCH_ADDR 0x10 -#define CPLD_CMD_FETCH_ADDR_P1 0x11 -#define CPLD_CMD_FETCH_ADDR_P2 0x12 -#define CPLD_CMD_FETCH_ADDR_P3 0x13 -#define CPLD_CMD_FETCH_ADDR_P4 0x14 -#define CPLD_CMD_FETCH_ADDR_P5 0x15 -#define CPLD_CMD_FETCH_ADDR_P6 0x16 -#define CPLD_CMD_FETCH_ADDR_P7 0x17 -#define CPLD_CMD_WRITE_ADDR 0x18 -#define CPLD_CMD_WRITE_ADDR_P1 0x19 -#define CPLD_CMD_WRITE_ADDR_P2 0x1A -#define CPLD_CMD_WRITE_ADDR_P3 0x1B -#define CPLD_CMD_WRITE_ADDR_P4 0x1C -#define CPLD_CMD_WRITE_ADDR_P5 0x1D -#define CPLD_CMD_WRITE_ADDR_P6 0x1E -#define CPLD_CMD_WRITE_ADDR_P7 0x1F -#define CPLD_CMD_READ_ADDR 0x20 -#define CPLD_CMD_READ_ADDR_P1 0x21 -#define CPLD_CMD_READ_ADDR_P2 0x22 -#define CPLD_CMD_READ_ADDR_P3 0x23 -#define CPLD_CMD_READ_ADDR_P4 0x24 -#define CPLD_CMD_READ_ADDR_P5 0x25 -#define CPLD_CMD_READ_ADDR_P6 0x26 -#define CPLD_CMD_READ_ADDR_P7 0x27 -#define CPLD_CMD_WRITEIO_ADDR 0x28 -#define CPLD_CMD_WRITEIO_ADDR_P1 0x29 -#define CPLD_CMD_WRITEIO_ADDR_P2 0x2A -#define CPLD_CMD_WRITEIO_ADDR_P3 0x2B -#define CPLD_CMD_WRITEIO_ADDR_P4 0x2C -#define CPLD_CMD_WRITEIO_ADDR_P5 0x2D -#define CPLD_CMD_WRITEIO_ADDR_P6 0x2E -#define CPLD_CMD_WRITEIO_ADDR_P7 0x2F -#define CPLD_CMD_READIO_ADDR 0x30 -#define CPLD_CMD_READIO_ADDR_P1 0x31 -#define CPLD_CMD_READIO_ADDR_P2 0x32 -#define CPLD_CMD_READIO_ADDR_P3 0x33 -#define CPLD_CMD_READIO_ADDR_P4 0x34 -#define CPLD_CMD_READIO_ADDR_P5 0x35 -#define CPLD_CMD_READIO_ADDR_P6 0x36 -#define CPLD_CMD_READIO_ADDR_P7 0x37 -#define CPLD_CMD_HALT 0x50 -#define CPLD_CMD_REFRESH 0x51 -#define CPLD_CMD_SET_SIGROUP1 0xF0 -#define CPLD_CMD_SET_AUTO_REFRESH 0xF1 -#define CPLD_CMD_CLEAR_AUTO_REFRESH 0xF2 -#define CPLD_CMD_SET_SPI_LOOPBACK 0xFE -#define CPLD_CMD_NOP1 0x00 -#define CPLD_CMD_NOP2 0xFF - - -// Pad numbers for using the MHal GPIO library. -#define PAD_Z80IO_IN_DATA_0 PAD_GPIO0 -#define PAD_Z80IO_IN_DATA_1 PAD_GPIO1 -#define PAD_Z80IO_IN_DATA_2 PAD_GPIO2 -#define PAD_Z80IO_IN_DATA_3 PAD_GPIO3 -#define PAD_Z80IO_IN_DATA_4 PAD_GPIO4 -#define PAD_Z80IO_IN_DATA_5 PAD_GPIO5 -#define PAD_Z80IO_IN_DATA_6 PAD_GPIO6 -#define PAD_Z80IO_IN_DATA_7 PAD_GPIO7 -#define PAD_SPIO_0 PAD_GPIO8 -#define PAD_SPIO_1 PAD_GPIO9 -#define PAD_SPIO_2 PAD_GPIO10 -#define PAD_SPIO_3 PAD_GPIO11 -#define PAD_Z80IO_HIGH_BYTE PAD_SAR_GPIO2 // Byte requiured, 0 = Low Byte, 1 = High Byte. -#define PAD_Z80IO_READY PAD_GPIO12 -#define PAD_Z80IO_LTSTATE PAD_PM_IRIN // IRIN -#define PAD_Z80IO_BUSRQ PAD_GPIO13 -#define PAD_Z80IO_BUSACK PAD_GPIO14 -#define PAD_Z80IO_INT PAD_UART0_RX // GPIO47 -#define PAD_Z80IO_NMI PAD_UART0_TX // GPIO48 -#define PAD_Z80IO_WAIT PAD_HSYNC_OUT // GPIO85 -#define PAD_Z80IO_RESET PAD_VSYNC_OUT // GPIO86 -#define PAD_Z80IO_RSV1 PAD_SATA_GPIO // GPIO90 - -// Physical register addresses. -#define PAD_Z80IO_IN_DATA_0_ADDR 0x103C00 -#define PAD_Z80IO_IN_DATA_1_ADDR 0x103C02 -#define PAD_Z80IO_IN_DATA_2_ADDR 0x103C04 -#define PAD_Z80IO_IN_DATA_3_ADDR 0x103C06 -#define PAD_Z80IO_IN_DATA_4_ADDR 0x103C08 -#define PAD_Z80IO_IN_DATA_5_ADDR 0x103C0A -#define PAD_Z80IO_IN_DATA_6_ADDR 0x103C0C -#define PAD_Z80IO_IN_DATA_7_ADDR 0x103C0E -#define PAD_SPIO_0_ADDR 0x103C10 -#define PAD_SPIO_1_ADDR 0x103C12 -#define PAD_SPIO_2_ADDR 0x103C14 -#define PAD_SPIO_3_ADDR 0x103C16 -#define PAD_Z80IO_HIGH_BYTE_ADDR 0x1425 -#define PAD_Z80IO_READY_ADDR 0x103C18 -#define PAD_Z80IO_LTSTATE_ADDR 0xF28 // IRIN -#define PAD_Z80IO_BUSRQ_ADDR 0x103C1A -#define PAD_Z80IO_BUSACK_ADDR 0x103C1C -#define PAD_Z80IO_INT_ADDR 0x103C30 // GPIO47 -#define PAD_Z80IO_NMI_ADDR 0x103C32 // GPIO48 -#define PAD_Z80IO_WAIT_ADDR 0x103C80 // GPIO85 -#define PAD_Z80IO_RESET_ADDR 0x103C82 // GPIO86 -#define PAD_Z80IO_RSV1_ADDR 0x103C8A // GPIO90 - -#ifdef NOTNEEDED -#define PAD_Z80IO_OUT_DATA_0 PAD_GPIO12 -#define PAD_Z80IO_OUT_DATA_1 PAD_GPIO13 -#define PAD_Z80IO_OUT_DATA_2 PAD_GPIO14 -#define PAD_Z80IO_OUT_DATA_3 PAD_UART0_RX // GPIO47 -#define PAD_Z80IO_OUT_DATA_4 PAD_UART0_TX // GPIO48 -#define PAD_Z80IO_OUT_DATA_5 PAD_HSYNC_OUT // GPIO85 -#define PAD_Z80IO_OUT_DATA_6 PAD_VSYNC_OUT // GPIO86 -#define PAD_Z80IO_OUT_DATA_7 PAD_SATA_GPIO // GPIO90 -#define PAD_Z80IO_WRITE PAD_PM_IRIN // Write data clock. -#endif - -//------------------------------------------------------------------------------------------------- -// The definitions below come from SigmaStar kernel drivers. No header file exists hence the -// duplication. -//------------------------------------------------------------------------------------------------- - -#define SUPPORT_SPI_1 0 -#define MAX_SUPPORT_BITS 16 - -#define BANK_TO_ADDR32(b) (b<<9) -#define BANK_SIZE 0x200 - -#define MS_BASE_REG_RIU_PA 0x1F000000 -#define gChipBaseAddr 0xFD203C00 -#define gPmSleepBaseAddr 0xFD001C00 -#define gSarBaseAddr 0xFD002800 -#define gRIUBaseAddr 0xFD000000 -#define gMOVDMAAddr 0xFD201600 -#define gClkBaseAddr 0xFD207000 -#define gMspBaseAddr 0xfd222000 - -#define MHal_CHIPTOP_REG(addr) (*(volatile U8*)((gChipBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_PM_SLEEP_REG(addr) (*(volatile U8*)((gPmSleepBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_SAR_GPIO_REG(addr) (*(volatile U8*)((gSarBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_RIU_REG(addr) (*(volatile U8*)((gRIUBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) - - -#define MSPI0_BANK_ADDR 0x1110 -#define MSPI1_BANK_ADDR 0x1111 -#define CLK__BANK_ADDR 0x1038 -#define CHIPTOP_BANK_ADDR 0x101E -#define MOVDMA_BANK_ADDR 0x100B - -#define BASE_REG_MSPI0_ADDR MSPI0_BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x111000) -#define BASE_REG_MSPI1_ADDR MSPI1_BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x111100) -#define BASE_REG_CLK_ADDR CLK__BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x103800) -#define BASE_REG_CHIPTOP_ADDR CHIPTOP_BANK_ADDR*0x200 //GET_BASE_ADDR_BY_BANK(IO_ADDRESS(MS_BASE_REG_RIU_PA), 0x101E00) - -//------------------------------------------------------------------------------------------------- -// Hardware Register Capability -//------------------------------------------------------------------------------------------------- -#define MSPI_WRITE_BUF_OFFSET 0x40 -#define MSPI_READ_BUF_OFFSET 0x44 -#define MSPI_WBF_SIZE_OFFSET 0x48 -#define MSPI_RBF_SIZE_OFFSET 0x48 - // read/ write buffer size -#define MSPI_RWSIZE_MASK 0xFF -#define MSPI_RSIZE_BIT_OFFSET 0x8 -#define MAX_READ_BUF_SIZE 0x8 -#define MAX_WRITE_BUF_SIZE 0x8 -// CLK config -#define MSPI_CTRL_OFFSET 0x49 -#define MSPI_CLK_CLOCK_OFFSET 0x49 -#define MSPI_CLK_CLOCK_BIT_OFFSET 0x08 -#define MSPI_CLK_CLOCK_MASK 0xFF -#define MSPI_CLK_PHASE_MASK 0x40 -#define MSPI_CLK_PHASE_BIT_OFFSET 0x06 -#define MSPI_CLK_POLARITY_MASK 0x80 -#define MSPI_CLK_POLARITY_BIT_OFFSET 0x07 -#define MSPI_CLK_PHASE_MAX 0x1 -#define MSPI_CLK_POLARITY_MAX 0x1 -#define MSPI_CLK_CLOCK_MAX 0x7 -#define MSPI_CTRL_CPOL_LOW 0x00 -#define MSPI_CTRL_CPOL_HIGH 0x80 -#define MSPI_CTRL_CPHA_LOW 0x00 -#define MSPI_CTRL_CPHA_HIGH 0x40 -#define MSPI_CTRL_3WIRE 0x10 -#define MSPI_CTRL_INTEN 0x04 -#define MSPI_CTRL_RESET 0x02 -#define MSPI_CTRL_ENABLE_SPI 0x01 -// DC config -#define MSPI_DC_MASK 0xFF -#define MSPI_DC_BIT_OFFSET 0x08 -#define MSPI_DC_TR_START_OFFSET 0x4A -#define MSPI_DC_TRSTART_MAX 0xFF -#define MSPI_DC_TR_END_OFFSET 0x4A -#define MSPI_DC_TREND_MAX 0xFF -#define MSPI_DC_TB_OFFSET 0x4B -#define MSPI_DC_TB_MAX 0xFF -#define MSPI_DC_TRW_OFFSET 0x4B -#define MSPI_DC_TRW_MAX 0xFF -// Frame Config -#define MSPI_FRAME_WBIT_OFFSET 0x4C -#define MSPI_FRAME_RBIT_OFFSET 0x4E -#define MSPI_FRAME_BIT_MAX 0x07 -#define MSPI_FRAME_BIT_MASK 0x07 -#define MSPI_FRAME_BIT_FIELD 0x03 -#define MSPI_LSB_FIRST_OFFSET 0x50 -#define MSPI_TRIGGER_OFFSET 0x5A -#define MSPI_DONE_OFFSET 0x5B -#define MSPI_DONE_CLEAR_OFFSET 0x5C -#define MSPI_CHIP_SELECT_OFFSET 0x5F -#define MSPI_CS1_DISABLE 0x01 -#define MSPI_CS1_ENABLE 0x00 -#define MSPI_CS2_DISABLE 0x02 -#define MSPI_CS2_ENABLE 0x00 -#define MSPI_CS3_DISABLE 0x04 -#define MSPI_CS3_ENABLE 0x00 -#define MSPI_CS4_DISABLE 0x08 -#define MSPI_CS4_ENABLE 0x00 -#define MSPI_CS5_DISABLE 0x10 -#define MSPI_CS5_ENABLE 0x00 -#define MSPI_CS6_DISABLE 0x20 -#define MSPI_CS6_ENABLE 0x00 -#define MSPI_CS7_DISABLE 0x40 -#define MSPI_CS7_ENABLE 0x00 -#define MSPI_CS8_DISABLE 0x80 -#define MSPI_CS8_ENABLE 0x00 - -#define MSPI_FULL_DEPLUX_RD_CNT (0x77) -#define MSPI_FULL_DEPLUX_RD00 (0x78) -#define MSPI_FULL_DEPLUX_RD01 (0x78) -#define MSPI_FULL_DEPLUX_RD02 (0x79) -#define MSPI_FULL_DEPLUX_RD03 (0x79) -#define MSPI_FULL_DEPLUX_RD04 (0x7a) -#define MSPI_FULL_DEPLUX_RD05 (0x7a) -#define MSPI_FULL_DEPLUX_RD06 (0x7b) -#define MSPI_FULL_DEPLUX_RD07 (0x7b) - -#define MSPI_FULL_DEPLUX_RD08 (0x7c) -#define MSPI_FULL_DEPLUX_RD09 (0x7c) -#define MSPI_FULL_DEPLUX_RD10 (0x7d) -#define MSPI_FULL_DEPLUX_RD11 (0x7d) -#define MSPI_FULL_DEPLUX_RD12 (0x7e) -#define MSPI_FULL_DEPLUX_RD13 (0x7e) -#define MSPI_FULL_DEPLUX_RD14 (0x7f) -#define MSPI_FULL_DEPLUX_RD15 (0x7f) - -//chip select bit map -#define MSPI_CHIP_SELECT_MAX 0x07 - -// control bit -#define MSPI_DONE_FLAG 0x01 -#define MSPI_TRIGGER 0x01 -#define MSPI_CLEAR_DONE 0x01 -#define MSPI_INT_ENABLE 0x04 -#define MSPI_RESET 0x02 -#define MSPI_ENABLE 0x01 - -// clk_mspi0 -#define MSPI0_CLK_CFG 0x33 //bit 2 ~bit 3 -#define MSPI0_CLK_108M 0x00 -#define MSPI0_CLK_54M 0x04 -#define MSPI0_CLK_12M 0x08 -#define MSPI0_CLK_MASK 0x0F - -// clk_mspi1 -#define MSPI1_CLK_CFG 0x33 //bit 10 ~bit 11 -#define MSPI1_CLK_108M 0x0000 -#define MSPI1_CLK_54M 0x0400 -#define MSPI1_CLK_12M 0x0800 -#define MSPI1_CLK_MASK 0x0F00 - -// clk_mspi -#define MSPI_CLK_CFG 0x33 -#define MSPI_SELECT_0 0x0000 -#define MSPI_SELECT_1 0x4000 -#define MSPI_CLK_MASK 0xF000 - -// Clock settings -#define MSPI_CPU_CLOCK_1_2 0x0000 -#define MSPI_CPU_CLOCK_1_4 0x0100 -#define MSPI_CPU_CLOCK_1_8 0x0200 -#define MSPI_CPU_CLOCK_1_16 0x0300 -#define MSPI_CPU_CLOCK_1_32 0x0400 -#define MSPI_CPU_CLOCK_1_64 0x0500 -#define MSPI_CPU_CLOCK_1_128 0x0600 -#define MSPI_CPU_CLOCK_1_256 0x0700 - -//CHITOP 101E mspi mode select -#define MSPI0_MODE 0x0C //bit0~bit1 -#define MSPI0_MODE_MASK 0x07 -#define MSPI1_MODE 0x0C //bit4~bit5 -#define MSPI1_MODE_MASK 0x70 -#define EJTAG_MODE 0xF -#define EJTAG_MODE_1 0x01 -#define EJTAG_MODE_2 0x02 -#define EJTAG_MODE_3 0x03 -#define EJTAG_MODE_MASK 0x03 - -//MOVDMA 100B -#define MOV_DMA_SRC_ADDR_L 0x03 -#define MOV_DMA_SRC_ADDR_H 0x04 -#define MOV_DMA_DST_ADDR_L 0x05 -#define MOV_DMA_DST_ADDR_H 0x06 -#define MOV_DMA_BYTE_CNT_L 0x07 -#define MOV_DMA_BYTE_CNT_H 0x08 -#define DMA_MOVE0_IRQ_CLR 0x28 -#define MOV_DMA_IRQ_FINAL_STATUS 0x2A -#define DMA_MOVE0_ENABLE 0x00 -#define DMA_RW 0x50 //0 for dma write to device, 1 for dma read from device -#define DMA_READ 0x01 -#define DMA_WRITE 0x00 -#define DMA_DEVICE_MODE 0x51 -#define DMA_DEVICE_SEL 0x52 - -//spi dma -#define MSPI_DMA_DATA_LENGTH_L 0x30 -#define MSPI_DMA_DATA_LENGTH_H 0x31 -#define MSPI_DMA_ENABLE 0x32 -#define MSPI_DMA_RW_MODE 0x33 -#define MSPI_DMA_WRITE 0x00 -#define MSPI_DMA_READ 0x01 - -#define MSTAR_SPI_TIMEOUT_MS 30000 -#define MSTAR_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA /*| SPI_CS_HIGH | SPI_NO_CS | SPI_LSB_FIRST*/) - - -//------------------------------------------------------------------------------------------------- -// Macros -//------------------------------------------------------------------------------------------------- - - -#define MHal_CHIPTOP_REG(addr) (*(volatile U8*)((gChipBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_PM_SLEEP_REG(addr) (*(volatile U8*)((gPmSleepBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_SAR_GPIO_REG(addr) (*(volatile U8*)((gSarBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define MHal_RIU_REG(addr) (*(volatile U8*)((gRIUBaseAddr) + (((addr) & ~1)<<1) + (addr & 1))) -#define READ_BYTE(_reg) (*(volatile u8*)(_reg)) -#define READ_WORD(_reg) (*(volatile u16*)(_reg)) -#define READ_LONG(_reg) (*(volatile u32*)(_reg)) -#define WRITE_BYTE(_reg, _val) {(*((volatile u8*)(_reg))) = (u8)(_val); } -#define WRITE_WORD(_reg, _val) {(*((volatile u16*)(_reg))) = (u16)(_val); } -#define WRITE_LONG(_reg, _val) {(*((volatile u32*)(_reg))) = (u32)(_val); } -#define WRITE_WORD_MASK(_reg, _val, _mask) {(*((volatile u16*)(_reg))) = ((*((volatile u16*)(_reg))) & ~(_mask)) | ((u16)(_val) & (_mask)); } -#define READ_CPLD_DATA_IN() ((MHal_RIU_REG(PAD_Z80IO_IN_DATA_7_ADDR) & 0x1) << 7 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_6_ADDR) & 0x1) << 6 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_5_ADDR) & 0x1) << 5 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_4_ADDR) & 0x1) << 4 |\ - (MHal_RIU_REG(PAD_Z80IO_IN_DATA_3_ADDR) & 0x1) << 3 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_2_ADDR) & 0x1) << 2 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_1_ADDR) & 0x1) << 1 | (MHal_RIU_REG(PAD_Z80IO_IN_DATA_0_ADDR) & 0x1)) -#define SET_CPLD_READ_DATA() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) |= 0x4;} -#define SET_CPLD_READ_STATUS() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) &= ~0x4;} -#define SET_CPLD_HIGH_BYTE() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) |= 0x4;} -#define CLEAR_CPLD_HIGH_BYTE() {MHal_RIU_REG(PAD_Z80IO_HIGH_BYTE_ADDR) &= ~0x4;} -#define CPLD_READY() (MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) -#define CPLD_RESET() (MHal_RIU_REG(PAD_Z80IO_RESET_ADDR) & 0x1) -#define CPLD_LAST_TSTATE() (MHal_RIU_REG(PAD_Z80IO_LTSTATE_ADDR) & 0x4) -#define SPI_SEND8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); \ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE);\ - } -#define SPI_SEND16(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ - } -#define SPI_SEND32(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ - MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ - MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ - MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ - } - -// read 2 byte -#define MSPI_READ(_reg_) READ_WORD(gMspBaseAddr + ((_reg_)<<2)) -// write 2 byte -//#define MSPI_WRITE(_reg_, _val_) {pr_info("PDS: MSPI_WRITE(0x%x, 0x%x, 0x%x)\n", _reg_, _val_, gMspBaseAddr + ((_reg_)<<2)); WRITE_WORD(gMspBaseAddr + ((_reg_)<<2), (_val_)); } -#define MSPI_WRITE(_reg_, _val_) WRITE_WORD(gMspBaseAddr + ((_reg_)<<2), (_val_)); -//write 2 byte mask -//#define MSPI_WRITE_MASK(_reg_, _val_, mask) {pr_info("PDS: WRITE_LONG(0x%x, 0x%x, mask=0x%x)\n", _reg_, _val_, mask); WRITE_WORD_MASK(gMspBaseAddr + ((_reg_)<<2), (_val_), (mask)); } -#define MSPI_WRITE_MASK(_reg_, _val_, mask) WRITE_WORD_MASK(gMspBaseAddr + ((_reg_)<<2), (_val_), (mask)); - -#define CLK_READ(_reg_) READ_WORD(gClkBaseAddr + ((_reg_)<<2)) -//#define CLK_WRITE(_reg_, _val_) {pr_info("PDS: CLK_WRITE(0x%x, 0x%x)\n", _reg_, _val_); WRITE_WORD(gClkBaseAddr + ((_reg_)<<2), (_val_)); } -#define CLK_WRITE(_reg_, _val_) WRITE_WORD(gClkBaseAddr + ((_reg_)<<2), (_val_)); - -#define CHIPTOP_READ(_reg_) READ_WORD(gChipBaseAddr + ((_reg_)<<2)) -//#define CHIPTOP_WRITE(_reg_, _val_) {pr_info("PDS: CHIPTOP_WRITE(0x%x, 0x%x)\n", _reg_, _val_); WRITE_WORD(gChipBaseAddr + ((_reg_)<<2), (_val_)); } -#define CHIPTOP_WRITE(_reg_, _val_) WRITE_WORD(gChipBaseAddr + ((_reg_)<<2), (_val_)); - -#define MOVDMA_READ(_reg_) READ_WORD(gMOVDMAAddr + ((_reg_)<<2)) -//#define MOVDMA_WRITE(_reg_, _val_) {pr_info("PDS: MOVDMA_WRITE(0x%x, 0x%x)\n", _reg_, _val_); WRITE_WORD(gMOVDMAAddr + ((_reg_)<<2), (_val_)); } -#define MOVDMA_WRITE(_reg_, _val_) WRITE_WORD(gMOVDMAAddr + ((_reg_)<<2), (_val_)); - -#define _HAL_MSPI_ClearDone() MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET,MSPI_CLEAR_DONE) -#define MAX_CHECK_CNT 2000 - -#define MSPI_READ_INDEX 0x0 -#define MSPI_WRITE_INDEX 0x1 - -#define SPI_MIU0_BUS_BASE 0x20000000 -#define SPI_MIU1_BUS_BASE 0xFFFFFFFF - - -// Function definitions. -// -int z80io_init(void); -uint8_t z80io_SPI_Send8(uint8_t txData, uint8_t *rxData); -uint8_t z80io_SPI_Send16(uint16_t txData, uint16_t *rxData); -uint8_t z80io_SPI_Send32(uint32_t txData, uint32_t *rxData); -#ifdef NOTNEEDED -uint8_t z80io_PRL_Send8(uint8_t txData); -uint8_t z680io_PRL_Send16(uint16_t txData); -#endif -uint8_t z80io_PRL_Read8(uint8_t dataFlag); -uint16_t z80io_PRL_Read16(void); -uint8_t z80io_SPI_Test(void); -uint8_t z80io_PRL_Test(void); -uint8_t z80io_Z80_TestMemory(void); - -extern void MHal_GPIO_Init(void); -extern void MHal_GPIO_Pad_Set(uint8_t u8IndexGPIO); -extern int MHal_GPIO_PadGroupMode_Set(uint32_t u32PadMode); -extern int MHal_GPIO_PadVal_Set(uint8_t u8IndexGPIO, uint32_t u32PadMode); -extern void MHal_GPIO_Pad_Oen(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Pad_Odn(uint8_t u8IndexGPIO); -extern uint8_t MHal_GPIO_Pad_Level(uint8_t u8IndexGPIO); -extern uint8_t MHal_GPIO_Pad_InOut(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Pull_High(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Pull_Low(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Set_High(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Set_Low(uint8_t u8IndexGPIO); -extern void MHal_Enable_GPIO_INT(uint8_t u8IndexGPIO); -extern int MHal_GPIO_To_Irq(uint8_t u8IndexGPIO); -extern void MHal_GPIO_Set_POLARITY(uint8_t u8IndexGPIO, uint8_t reverse); -extern void MHal_GPIO_Set_Driving(uint8_t u8IndexGPIO, uint8_t setHigh); -extern void MHal_GPIO_PAD_32K_OUT(uint8_t u8Enable); - -#ifdef __cplusplus -} -#endif -#endif // Z80IO_H diff --git a/software/FusionX/src/z80drv/MZ700/z80io_test.c b/software/FusionX/src/z80drv/MZ700/z80io_test.c deleted file mode 100644 index a0b6899cf..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80io_test.c +++ /dev/null @@ -1,541 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80io_test.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 IO Interface Test Methods -// This file contains the methods used to test the SOM to CPLD interface and evaluate -// it's performance. Production builds wont include these methods. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -//-------------------------------------------------------- -// Test Methods. -//-------------------------------------------------------- -uint8_t z80io_Z80_TestMemory(void) -{ - // Locals. - // - uint32_t addr; - uint32_t fullCmd; - uint8_t cmd; - struct timeval start, stop; - uint32_t iterations = 100; - uint32_t errorCount; - uint32_t idx; - long totalTime; - long bytesMSec; - uint8_t result; - spinlock_t spinLock; - unsigned long flags; - - SPI_SEND8(CPLD_CMD_CLEAR_AUTO_REFRESH); - - SPI_SEND32(0x00E30000 | (0x07 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E80000 | (0x82 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E20000 | (0x58 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E00000 | (0xF7 << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00E90000 | (0x0F << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00EB0000 | (0xCF << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - SPI_SEND32(0x00EB0000 | (0xFF << 8) | CPLD_CMD_WRITEIO_ADDR); - udelay(100); - pr_info("Z80 Host Test - IO.\n"); - for(idx=0; idx < 1000000; idx++) - { - SPI_SEND32(0x00E80000 | (0xD3 << 8) | CPLD_CMD_WRITEIO_ADDR); - SPI_SEND32(0xD0000000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); - SPI_SEND32(0xD0100000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); - SPI_SEND32(0xD0200000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); - SPI_SEND32(0xD0300000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); - SPI_SEND32(0xD0400000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); - SPI_SEND32(0xD0500000 | (0x41 << 8) | CPLD_CMD_WRITE_ADDR); - } - - spin_lock_init(&spinLock); - pr_info("Z80 Host Test - Testing IO Write performance.\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - for(addr=0x0000; addr < 0x10000; addr++) - { - fullCmd = 0x00000000| ((uint8_t)addr) << 8 | CPLD_CMD_WRITEIO_ADDR; - SPI_SEND32(fullCmd); - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - spin_lock_init(&spinLock); - pr_info("Z80 Host Test - Testing IO Read performance.\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible IO ports and write to it. - for(addr=0x0000; addr < 0x10000; addr++) - { - fullCmd = 0x00000000 | ((uint8_t)addr) << 8 | CPLD_CMD_READIO_ADDR; - SPI_SEND32(fullCmd); - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - spin_lock_init(&spinLock); - pr_info("Z80 Host Test - Testing RAM Write performance.\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x1000; addr < 0xD000; addr++) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Write performance (opt).\n"); - do_gettimeofday(&start); - spin_lock_irqsave(&spinLock, flags); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x1000; addr < 0xD000; addr++) - { - if(addr == 0x1000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND16(((uint8_t)addr) << 8 | cmd); - } - } - } - spin_unlock_irqrestore(&spinLock, flags); - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Write/Fetch performance (opt).\n"); - errorCount = 0; - SET_CPLD_READ_DATA(); - //MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE ].r_out) |= gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x8000; addr < 0xD000; addr++) - { - if(addr == 0x8000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND16(((uint8_t)addr) << 8 | cmd); - } - - // Read back the same byte. - cmd = 0x10; - SPI_SEND8(cmd); - while(CPLD_READY() == 0); - - result = READ_CPLD_DATA_IN(); - if(result != (uint8_t)addr) - { - if(errorCount < 50) pr_info("Read byte:0x%x, Written:0x%x\n", result, (uint8_t)addr); - errorCount++; - } - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, errorCount=%d, %ldBytes/sec\n", totalTime/1000, errorCount, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Write/Read performance (opt).\n"); - errorCount = 0; - SET_CPLD_READ_DATA(); - //MHal_RIU_REG(gpio_table[PAD_Z80IO_HIGH_BYTE ].r_out) |= gpio_table[PAD_Z80IO_HIGH_BYTE ].m_out; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and write to it. - for(addr=0x8000; addr < 0xD000; addr++) - { - if(addr == 0x8000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND16(((uint8_t)addr) << 8 | cmd); - } - - // Read back the same byte. - cmd = 0x20; - SPI_SEND8(cmd); - while(CPLD_READY() == 0); - - result = READ_CPLD_DATA_IN(); - if(result != (uint8_t)addr) - { - if(errorCount < 50) pr_info("Read byte:0x%x, Written:0x%x\n", result, (uint8_t)addr); - errorCount++; - } - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, errorCount=%d, %ldBytes/sec\n", totalTime/1000, errorCount, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Fetch performance.\n"); - SET_CPLD_READ_DATA(); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and read from it. - for(addr=0x1000; addr < 0xD000; addr++) - { - if(addr == 0x1000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x10; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x11; - SPI_SEND8(cmd); - } - while(CPLD_READY() == 0); - result = READ_CPLD_DATA_IN(); - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Z80 Host Test - Testing RAM Read performance (opt).\n"); - SET_CPLD_READ_DATA(); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible RAM and read from it. - for(addr=0x1000; addr < 0xD000; addr++) - { - if(addr == 0x1000) - { - fullCmd = (addr << 16) | ((uint8_t)addr) << 8 | 0x20; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x21; - SPI_SEND8(cmd); - } - while(CPLD_READY() == 0); - result = READ_CPLD_DATA_IN(); - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations*0xC000)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - // Go through all the accessible attribute VRAM and initialise it. - pr_info("Z80 Host Test - Testing VRAM Write performance.\n"); - SPI_SEND32(0x00E80000 | (0xD3 << 8) | CPLD_CMD_WRITEIO_ADDR); - iterations = 256*10; - do_gettimeofday(&start); - for(addr=0xD800; addr < 0xE000; addr++) - { - //while(CPLD_READY() == 0); - if(addr == 0xD800) - { - fullCmd = (addr << 16) |(0x71 << 8) | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND8(cmd); - } - } - for(idx=0; idx < iterations; idx++) - { - // Go through all the accessible VRAM and write to it. - for(addr=0xD000; addr < 0xD800; addr++) - { - //while(CPLD_READY() == 0); - if(addr == 0xD000) - { - fullCmd = (addr << 16) | ((uint8_t)idx << 8) | 0x18; - SPI_SEND32(fullCmd); - } else - { - cmd = 0x19; - SPI_SEND8(cmd); - } - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)((1*iterations*0x800)+0x800)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - return(0); -} - - -// A simple test to verify the SOM to CPLD SPI connectivity and give an estimate of its performance. -// The performance is based on the SPI setup and transmit time along with the close and received data processing. -// In real use, the driver will just send a command and generally ignore received data so increased throughput can be achieved. -// -uint8_t z80io_SPI_Test(void) -{ - // Locals. - // - struct timeval start, stop; - uint32_t iterations = 10000000; - uint32_t idx; - uint8_t rxData8; - uint16_t rxData16; - uint16_t rxData16Last; - uint32_t rxData32; - uint32_t rxData32Last; - uint32_t errorCount; - long totalTime; - long bytesMSec; - - // Place the CPLD into echo test mode. - z80io_SPI_Send8(0xfe, &rxData8); - - // 1st. test, 8bit. - pr_info("SPI Test - Testing 8 bit performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - z80io_SPI_Send8((uint8_t)idx, &rxData8); - if(idx > 1 && (uint8_t)(idx-1) != rxData8) - { - if(errorCount < 20) - pr_info("0x%x: Last(0x%x) /= New(0x%x)\n",idx, (uint8_t)(idx-1), rxData8 ); - errorCount++; - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(1*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 2nd. test, 16bit. - pr_info("SPI Test - Testing 16 bit performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Byte re-ordering required as the CPLD echo's back the last 8bits received, it doesnt know if a transmission is 8/16/32bits. - z80io_SPI_Send16((uint16_t)idx, &rxData16); - if(idx > 0 && (uint16_t)(idx-1) != (uint16_t)(((rxData16&0x00ff) << 8) | ((rxData16Last & 0xff00) >> 8))) - { - if(errorCount < 20) - pr_info("0x%x: Last(0x%x) /= New(0x%x)\n",idx, (uint16_t)(idx-1), (uint16_t)(((rxData16&0x00ff) << 8) | ((rxData16Last & 0xff00) >> 8))); - errorCount++; - } - rxData16Last = rxData16; - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 3rd. test, 32bit. - pr_info("SPI Test - Testing 32 bit performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - z80io_SPI_Send32((uint32_t)idx, &rxData32); - if(idx > 0 && (uint32_t)(idx-1) != (uint32_t)(((rxData32&0x00ff) << 8) | ((rxData32Last & 0xff000000) >> 8) | ((rxData32Last & 0xff0000) >> 8) | ((rxData32Last & 0xff00) >> 8))) - { - if(errorCount < 20) - pr_info("0x%x: Last(0x%x) /= New(0x%x)\n",idx, (uint32_t)(idx-1), (uint32_t)(((rxData32&0x00ff) << 8) | ((rxData32Last & 0xff000000) >> 8) | ((rxData32Last & 0xff0000) >> 8) | ((rxData32Last & 0xff00) >> 8))); - errorCount++; - } - rxData32Last = rxData32; - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(4*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - pr_info("Press host RESET button Once to reset the CPLD.\n"); - return(0); -} - -// Method to test the parallel bus, verifying integrity and assessing performance. -uint8_t z80io_PRL_Test(void) -{ - // Locals. - // - struct timeval start, stop; - uint32_t iterations = 10000000; - uint32_t idx; - uint8_t rxData8; - uint16_t rxData16; - long totalTime; - long bytesMSec; -#ifdef NOTNEEDED - uint32_t errorCount; -#endif - - // Place the CPLD into echo test mode. - - // 1st. test, 8bit RW. -#ifdef NOTNEEDED - pr_info("Parallel Test - Testing 8 bit r/w performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Write byte and readback to compare. - z80io_PRL_Send8((uint8_t)idx); - rxData8 = z80io_PRL_Read8(); - if((uint8_t)idx != rxData8) - { - pr_info("0x%x: Written(0x%x) /= Read(0x%x)\n", idx, (uint8_t)(idx), rxData8); - errorCount++; - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 2nd. test, 8bit Write. - pr_info("Parallel Test - Testing 8 bit write performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Write byte. - z80io_PRL_Send8((uint8_t)idx); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); -#endif - - // 3rd. test, 8bit Read. - pr_info("Parallel Test - Testing 8 bit read performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Read byte. - rxData8 = z80io_PRL_Read8(0); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - -#ifdef NOTNEEDED - // 4th test, 16bit. - pr_info("Parallel Test - Testing 16 bit r/w performance.\n"); - errorCount=0; - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Byte re-ordering required as the CPLD echo's back the last 8bits received, it doesnt know if a transmission is 8/16/32bits. - z80io_PRL_Send16((uint16_t)idx); - rxData16 = z80io_PRL_Read16(); - if((uint16_t)idx != rxData16) - { - pr_info("0x%x: Written(0x%x) /= Read(0x%x)\n", idx, (uint16_t)(idx), rxData16); - errorCount++; - } - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode errorCount: %d, time=%ldms, %ldBytes/sec\n", errorCount, totalTime/1000, (bytesMSec*1000)); - - // 5th test, 16bit Write. - pr_info("Parallel Test - Testing 16 bit write performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Write word. - z80io_PRL_Send16((uint16_t)idx); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); -#endif - - // 6th test, 16bit Read. - pr_info("Parallel Test - Testing 16 bit read performance.\n"); - do_gettimeofday(&start); - for(idx=0; idx < iterations; idx++) - { - // Read word. - rxData16 = z80io_PRL_Read16(); - } - do_gettimeofday(&stop); - totalTime = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; - bytesMSec = (long)(2*iterations)/((long)totalTime/1000); - pr_info("Loop mode time=%ldms, %ldBytes/sec\n", totalTime/1000, (bytesMSec*1000)); - - pr_info("Press host RESET button Once to reset the CPLD.\n"); - return(0); -} diff --git a/software/FusionX/src/z80drv/MZ700/z80menu.c b/software/FusionX/src/z80drv/MZ700/z80menu.c deleted file mode 100644 index bcc28a2ca..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80menu.c +++ /dev/null @@ -1,57 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80menu.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 User Menu -// This file contains the methods used to present a menu of options to a user to aid -// in configuration and load/save of applications and data. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80io.h" -#include "z80menu.h" - -#include -#include -#include -#include - -void z80menu(void) -{ - // Locals. - -} diff --git a/software/FusionX/src/z80drv/MZ700/z80menu.h b/software/FusionX/src/z80drv/MZ700/z80menu.h deleted file mode 100755 index 107583682..000000000 --- a/software/FusionX/src/z80drv/MZ700/z80menu.h +++ /dev/null @@ -1,44 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80menu.h -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 User Interface Menu -// This file contains the declarations required to provide a menu system allowing a -// user to configure and load/save applications/data. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef Z80MENU_H -#define Z80MENU_H - -#ifdef __cplusplus - extern "C" { -#endif - -// Function definitions. -// -void z80menu(void); - -#ifdef __cplusplus -} -#endif -#endif // Z80MENU_H diff --git a/software/FusionX/src/z80drv/MZ80A/optparse.h b/software/FusionX/src/z80drv/MZ80A/optparse.h deleted file mode 100644 index f96184add..000000000 --- a/software/FusionX/src/z80drv/MZ80A/optparse.h +++ /dev/null @@ -1,403 +0,0 @@ -/* Optparse --- portable, reentrant, embeddable, getopt-like option parser - * - * This is free and unencumbered software released into the public domain. - * - * To get the implementation, define OPTPARSE_IMPLEMENTATION. - * Optionally define OPTPARSE_API to control the API's visibility - * and/or linkage (static, __attribute__, __declspec). - * - * The POSIX getopt() option parser has three fatal flaws. These flaws - * are solved by Optparse. - * - * 1) Parser state is stored entirely in global variables, some of - * which are static and inaccessible. This means only one thread can - * use getopt(). It also means it's not possible to recursively parse - * nested sub-arguments while in the middle of argument parsing. - * Optparse fixes this by storing all state on a local struct. - * - * 2) The POSIX standard provides no way to properly reset the parser. - * This means for portable code that getopt() is only good for one - * run, over one argv with one option string. It also means subcommand - * options cannot be processed with getopt(). Most implementations - * provide a method to reset the parser, but it's not portable. - * Optparse provides an optparse_arg() function for stepping over - * subcommands and continuing parsing of options with another option - * string. The Optparse struct itself can be passed around to - * subcommand handlers for additional subcommand option parsing. A - * full reset can be achieved by with an additional optparse_init(). - * - * 3) Error messages are printed to stderr. This can be disabled with - * opterr, but the messages themselves are still inaccessible. - * Optparse solves this by writing an error message in its errmsg - * field. The downside to Optparse is that this error message will - * always be in English rather than the current locale. - * - * Optparse should be familiar with anyone accustomed to getopt(), and - * it could be a nearly drop-in replacement. The option string is the - * same and the fields have the same names as the getopt() global - * variables (optarg, optind, optopt). - * - * Optparse also supports GNU-style long options with optparse_long(). - * The interface is slightly different and simpler than getopt_long(). - * - * By default, argv is permuted as it is parsed, moving non-option - * arguments to the end. This can be disabled by setting the `permute` - * field to 0 after initialization. - */ -#ifndef OPTPARSE_H -#define OPTPARSE_H - -#ifndef OPTPARSE_API -# define OPTPARSE_API -#endif - -struct optparse { - char **argv; - int permute; - int optind; - int optopt; - char *optarg; - char errmsg[64]; - int subopt; -}; - -enum optparse_argtype { - OPTPARSE_NONE, - OPTPARSE_REQUIRED, - OPTPARSE_OPTIONAL -}; - -struct optparse_long { - const char *longname; - int shortname; - enum optparse_argtype argtype; -}; - -/** - * Initializes the parser state. - */ -OPTPARSE_API -void optparse_init(struct optparse *options, char **argv); - -/** - * Read the next option in the argv array. - * @param optstring a getopt()-formatted option string. - * @return the next option character, -1 for done, or '?' for error - * - * Just like getopt(), a character followed by no colons means no - * argument. One colon means the option has a required argument. Two - * colons means the option takes an optional argument. - */ -OPTPARSE_API -int optparse(struct optparse *options, const char *optstring); - -/** - * Handles GNU-style long options in addition to getopt() options. - * This works a lot like GNU's getopt_long(). The last option in - * longopts must be all zeros, marking the end of the array. The - * longindex argument may be NULL. - */ -OPTPARSE_API -int optparse_long(struct optparse *options, - const struct optparse_long *longopts, - int *longindex); - -/** - * Used for stepping over non-option arguments. - * @return the next non-option argument, or NULL for no more arguments - * - * Argument parsing can continue with optparse() after using this - * function. That would be used to parse the options for the - * subcommand returned by optparse_arg(). This function allows you to - * ignore the value of optind. - */ -OPTPARSE_API -char *optparse_arg(struct optparse *options); - -/* Implementation */ -#ifdef OPTPARSE_IMPLEMENTATION - -#define OPTPARSE_MSG_INVALID "invalid option" -#define OPTPARSE_MSG_MISSING "option requires an argument" -#define OPTPARSE_MSG_TOOMANY "option takes no arguments" - -static int -optparse_error(struct optparse *options, const char *msg, const char *data) -{ - unsigned p = 0; - const char *sep = " -- '"; - while (*msg) - options->errmsg[p++] = *msg++; - while (*sep) - options->errmsg[p++] = *sep++; - while (p < sizeof(options->errmsg) - 2 && *data) - options->errmsg[p++] = *data++; - options->errmsg[p++] = '\''; - options->errmsg[p++] = '\0'; - return '?'; -} - -OPTPARSE_API -void -optparse_init(struct optparse *options, char **argv) -{ - options->argv = argv; - options->permute = 1; - options->optind = 1; - options->subopt = 0; - options->optarg = 0; - options->errmsg[0] = '\0'; -} - -static int -optparse_is_dashdash(const char *arg) -{ - return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; -} - -static int -optparse_is_shortopt(const char *arg) -{ - return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; -} - -static int -optparse_is_longopt(const char *arg) -{ - return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; -} - -static void -optparse_permute(struct optparse *options, int index) -{ - char *nonoption = options->argv[index]; - int i; - for (i = index; i < options->optind - 1; i++) - options->argv[i] = options->argv[i + 1]; - options->argv[options->optind - 1] = nonoption; -} - -static int -optparse_argtype(const char *optstring, char c) -{ - int count = OPTPARSE_NONE; - if (c == ':') - return -1; - for (; *optstring && c != *optstring; optstring++); - if (!*optstring) - return -1; - if (optstring[1] == ':') - count += optstring[2] == ':' ? 2 : 1; - return count; -} - -OPTPARSE_API -int -optparse(struct optparse *options, const char *optstring) -{ - int type; - char *next; - char *option = options->argv[options->optind]; - options->errmsg[0] = '\0'; - options->optopt = 0; - options->optarg = 0; - if (option == 0) { - return -1; - } else if (optparse_is_dashdash(option)) { - options->optind++; /* consume "--" */ - return -1; - } else if (!optparse_is_shortopt(option)) { - if (options->permute) { - int index = options->optind++; - int r = optparse(options, optstring); - optparse_permute(options, index); - options->optind--; - return r; - } else { - return -1; - } - } - option += options->subopt + 1; - options->optopt = option[0]; - type = optparse_argtype(optstring, option[0]); - next = options->argv[options->optind + 1]; - switch (type) { - case -1: { - char str[2] = {0, 0}; - str[0] = option[0]; - options->optind++; - return optparse_error(options, OPTPARSE_MSG_INVALID, str); - } - case OPTPARSE_NONE: - if (option[1]) { - options->subopt++; - } else { - options->subopt = 0; - options->optind++; - } - return option[0]; - case OPTPARSE_REQUIRED: - options->subopt = 0; - options->optind++; - if (option[1]) { - options->optarg = option + 1; - } else if (next != 0) { - options->optarg = next; - options->optind++; - } else { - char str[2] = {0, 0}; - str[0] = option[0]; - options->optarg = 0; - return optparse_error(options, OPTPARSE_MSG_MISSING, str); - } - return option[0]; - case OPTPARSE_OPTIONAL: - options->subopt = 0; - options->optind++; - if (option[1]) - options->optarg = option + 1; - else - options->optarg = 0; - return option[0]; - } - return 0; -} - -OPTPARSE_API -char * -optparse_arg(struct optparse *options) -{ - char *option = options->argv[options->optind]; - options->subopt = 0; - if (option != 0) - options->optind++; - return option; -} - -static int -optparse_longopts_end(const struct optparse_long *longopts, int i) -{ - return !longopts[i].longname && !longopts[i].shortname; -} - -static void -optparse_from_long(const struct optparse_long *longopts, char *optstring) -{ - char *p = optstring; - int i; - for (i = 0; !optparse_longopts_end(longopts, i); i++) { - if (longopts[i].shortname && longopts[i].shortname < 127) { - int a; - *p++ = longopts[i].shortname; - for (a = 0; a < (int)longopts[i].argtype; a++) - *p++ = ':'; - } - } - *p = '\0'; -} - -/* Unlike strcmp(), handles options containing "=". */ -static int -optparse_longopts_match(const char *longname, const char *option) -{ - const char *a = option, *n = longname; - if (longname == 0) - return 0; - for (; *a && *n && *a != '='; a++, n++) - if (*a != *n) - return 0; - return *n == '\0' && (*a == '\0' || *a == '='); -} - -/* Return the part after "=", or NULL. */ -static char * -optparse_longopts_arg(char *option) -{ - for (; *option && *option != '='; option++); - if (*option == '=') - return option + 1; - else - return 0; -} - -static int -optparse_long_fallback(struct optparse *options, - const struct optparse_long *longopts, - int *longindex) -{ - int result; - char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ - optparse_from_long(longopts, optstring); - result = optparse(options, optstring); - if (longindex != 0) { - *longindex = -1; - if (result != -1) { - int i; - for (i = 0; !optparse_longopts_end(longopts, i); i++) - if (longopts[i].shortname == options->optopt) - *longindex = i; - } - } - return result; -} - -OPTPARSE_API -int -optparse_long(struct optparse *options, - const struct optparse_long *longopts, - int *longindex) -{ - int i; - char *option = options->argv[options->optind]; - if (option == 0) { - return -1; - } else if (optparse_is_dashdash(option)) { - options->optind++; /* consume "--" */ - return -1; - } else if (optparse_is_shortopt(option)) { - return optparse_long_fallback(options, longopts, longindex); - } else if (!optparse_is_longopt(option)) { - if (options->permute) { - int index = options->optind++; - int r = optparse_long(options, longopts, longindex); - optparse_permute(options, index); - options->optind--; - return r; - } else { - return -1; - } - } - - /* Parse as long option. */ - options->errmsg[0] = '\0'; - options->optopt = 0; - options->optarg = 0; - option += 2; /* skip "--" */ - options->optind++; - for (i = 0; !optparse_longopts_end(longopts, i); i++) { - const char *name = longopts[i].longname; - if (optparse_longopts_match(name, option)) { - char *arg; - if (longindex) - *longindex = i; - options->optopt = longopts[i].shortname; - arg = optparse_longopts_arg(option); - if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { - return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); - } if (arg != 0) { - options->optarg = arg; - } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { - options->optarg = options->argv[options->optind]; - if (options->optarg == 0) - return optparse_error(options, OPTPARSE_MSG_MISSING, name); - else - options->optind++; - } - return options->optopt; - } - } - return optparse_error(options, OPTPARSE_MSG_INVALID, option); -} - -#endif /* OPTPARSE_IMPLEMENTATION */ -#endif /* OPTPARSE_H */ diff --git a/software/FusionX/src/z80drv/MZ80A/z80menu.c b/software/FusionX/src/z80drv/MZ80A/z80menu.c deleted file mode 100644 index bcc28a2ca..000000000 --- a/software/FusionX/src/z80drv/MZ80A/z80menu.c +++ /dev/null @@ -1,57 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80menu.c -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 User Menu -// This file contains the methods used to present a menu of options to a user to aid -// in configuration and load/save of applications and data. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "z80io.h" -#include "z80menu.h" - -#include -#include -#include -#include - -void z80menu(void) -{ - // Locals. - -} diff --git a/software/FusionX/src/z80drv/MZ80A/z80menu.h b/software/FusionX/src/z80drv/MZ80A/z80menu.h deleted file mode 100755 index 107583682..000000000 --- a/software/FusionX/src/z80drv/MZ80A/z80menu.h +++ /dev/null @@ -1,44 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: z80menu.h -// Created: Oct 2022 -// Author(s): Philip Smart -// Description: Z80 User Interface Menu -// This file contains the declarations required to provide a menu system allowing a -// user to configure and load/save applications/data. -// Credits: -// Copyright: (c) 2019-2022 Philip Smart -// -// History: Oct 2022 - Initial write of the z80 kernel driver software. -// -// Notes: See Makefile to enable/disable conditional components -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// This source file is free software: you can redistribute it and#or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This source file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef Z80MENU_H -#define Z80MENU_H - -#ifdef __cplusplus - extern "C" { -#endif - -// Function definitions. -// -void z80menu(void); - -#ifdef __cplusplus -} -#endif -#endif // Z80MENU_H diff --git a/software/FusionX/src/z80drv/Makefile b/software/FusionX/src/z80drv/Makefile index b4a3ee8f9..271061c08 100644 --- a/software/FusionX/src/z80drv/Makefile +++ b/software/FusionX/src/z80drv/Makefile @@ -1,40 +1,85 @@ #MODEL := MZ2000 #MODEL := MZ700 -MODEL := MZ80A +#MODEL := MZ80A +#MODEL := PCW8XXX +#MODEL := PCW9XXX KERNEL := $(PWD)/../../../linux/kernel FUSIONX := $(PWD)/../.. CROSS := arm-linux-gnueabihf- -ccflags-y += -O2 -I${src}/Zeta/API -I${src}/Z80/API -I${KERNEL}/drivers/sstar/include -I${KERNEL}/drivers/sstar/include/infinity2m -I${KERNEL}/drivers/sstar/gpio/infinity2m -D__KERNEL_DRIVER__ -CTRLINC += -IZeta/API -IZ80/API +ccflags-y = -O2 -I${src}/Zeta/API -I${src}/Z80/API -I${KERNEL}/drivers/sstar/include -I${KERNEL}/drivers/sstar/include/infinity2m -I${KERNEL}/drivers/sstar/gpio/infinity2m -D__KERNEL_DRIVER__ -DTARGET_HOST_$(MODEL)=1 +CTRLINC = -IZeta/API -IZ80/API -DTARGET_HOST_$(MODEL)=1 obj-m += z80drv.o -z80drv-objs += $(MODEL)/z80driver.o Z80.o $(MODEL)/z80io.o $(MODEL)/z80menu.o # emumz.o sharpmz.o osd.o +z80drv-objs += src/z80driver.o Z80.o src/z80io.o src/z80menu.o # emumz.o sharpmz.o osd.o z80drv-objs += ../../../linux/kernel/drivers/sstar/gpio/infinity2m/gpio_table.o z80drv-objs += ../../../linux/kernel/drivers/sstar/gpio/infinity2m/mhal_gpio.o z80drv-objs += ../../../linux/kernel/drivers/sstar/gpio/infinity2m/mhal_pinmux.o z80drv-objs += ../../../linux/kernel/drivers/sstar/gpio/infinity2m/padmux_tables.o -all: +all: + @echo "Specify target host, ie. make " + @echo "Supported hosts: MZ80A, MZ700, MZ2000, PCW8XXX, PCW9XXX" + +MZ80A: MODEL_MZ80A +MZ700: MODEL_MZ700 +MZ2000: MODEL_MZ2000 +PCW8XXX: MODEL_PCW8XXX +PCW9XXX: MODEL_PCW9XXX + +MODEL_MZ80A: + $(MAKE) MODEL=MZ80A BUILD_MZ80A +MODEL_MZ700: + $(MAKE) MODEL=MZ700 BUILD_MZ700 +MODEL_MZ2000: + $(MAKE) MODEL=MZ2000 BUILD_MZ2000 +MODEL_PCW8XXX: + $(MAKE) MODEL=PCW8XXX BUILD_PCW8XXX +MODEL_PCW9XXX: + $(MAKE) MODEL=PCW8XXX BUILD_PCW9XXX + +BUILD_MZ80A: sharpbiter k64fcpu kmod z80ctrl +BUILD_MZ700: sharpbiter k64fcpu kmod z80ctrl +BUILD_MZ2000: sharpbiter k64fcpu kmod z80ctrl +BUILD_PCW8XXX: kmod z80ctrl +BUILD_PCW9XXX: kmod z80ctrl + + +sharpbiter: @echo "" @echo "Build Sharp MZ Arbiter for host: $(MODEL)" - $(CROSS)gcc $(CTRLINC) $(MODEL)/sharpbiter.c -o sharpbiter + $(CROSS)gcc $(CTRLINC) src/sharpbiter.c -o sharpbiter + +k64fcpu: @echo "" @echo "Build K64F Daemon for host: $(MODEL)" - $(CROSS)gcc $(CTRLINC) $(MODEL)/k64fcpu.c -o k64fcpu + $(CROSS)gcc $(CTRLINC) src/k64fcpu.c -o k64fcpu + +kmod: @echo "" @echo "Build driver for host: $(MODEL)" make -C $(KERNEL) ARCH=arm CROSS_COMPILE=$(CROSS) M="$(PWD)" modules + +z80ctrl: @echo "" @echo "Build z80ctrl tool for host: $(MODEL)" - $(CROSS)gcc $(CTRLINC) $(MODEL)/z80ctrl.c -o z80ctrl + $(CROSS)gcc $(CTRLINC) src/z80ctrl.c -o z80ctrl install: @echo "Copy kernel driver..." @cp z80drv.ko $(FUSIONX)/modules/ @echo "Copy z80ctrl app..." @cp z80ctrl $(FUSIONX)/bin/ + @if [ -f sharpbiter ]; then\ + echo "Copy sharpbiter app...";\ + cp sharpbiter $(FUSIONX)/bin/;\ + fi + @if [ -f k64fcpu ]; then\ + echo "Copy k64fcpu app...";\ + cp k64fcpu $(FUSIONX)/bin/;\ + fi clean: make -C $(KERNEL) M=$(PWD) clean + @rm -f sharpbiter k64fcpu z80ctrl diff --git a/software/FusionX/src/z80drv/Z80 b/software/FusionX/src/z80drv/Z80 index 75d01a9cc..ada1e2921 160000 --- a/software/FusionX/src/z80drv/Z80 +++ b/software/FusionX/src/z80drv/Z80 @@ -1 +1 @@ -Subproject commit 75d01a9ccace36b471e4514911edb5ab477f819c +Subproject commit ada1e2921fc37fb916736802ee376a34335db05e diff --git a/software/FusionX/src/z80drv/src/emumz.c b/software/FusionX/src/z80drv/src/emumz.c new file mode 100644 index 000000000..cddfe0d43 --- /dev/null +++ b/software/FusionX/src/z80drv/src/emumz.c @@ -0,0 +1,6127 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: emumz.c +// Created: May 2021 +// Version: v1.0 +// Author(s): Philip Smart +// Description: The MZ Emulator Control Logic +// This source file contains the logic to present an on screen display menu, interact +// with the user to set the config or perform machine actions (tape load) and provide +// overall control functionality in order to service the running Sharp MZ Series +// emulation. +// +// Credits: +// Copyright: (c) 2019-2021 Philip Smart +// +// History: v1.0 May 2021 - Initial write of the EmuMZ software. +// v1.1 Nov 2021 - Further work on Video Controller and infrastructure. +// v1.2 Nov 2021 - Adding MZ2000 logic. +// v1.3 Dec 2021 - Adding MZ800 logic. +// v1.4 Jan 2022 - Adding floppy disk support. +// v1.5 Mar 2022 - Consolidation and bug rectification. +// +// Notes: See Makefile to enable/disable conditional components +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// This source file is free software: you can redistribute it and#or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "z80io.h" + +#include +#include +#include +#include + +#include "osd.h" +#include "sharpz.h" +#include "emumz.h" + +// Debug enable. +#define __EMUMZ_DEBUG__ 1 + +// Debug macros +#define debugf(a, ...) if(emuControl.debug) { printf("\033[1;31m%s: " a "\033[0m\n", __func__, ##__VA_ARGS__); } +#define debugfx(a, ...) if(emuControl.debug) { printf("\033[1;32m%s: " a "\033[0m\n", __func__, ##__VA_ARGS__); } + +// Version data. +#define EMUMZ_VERSION 1.50 +#define EMUMZ_VERSION_DATE "16/03/2022" + +////////////////////////////////////////////////////////////// +// Sharp MZ Series Emulation Service Methods // +////////////////////////////////////////////////////////////// + +#ifndef __APP__ // Protected methods which should only reside in the kernel on zOS. + +// Global scope variables used within the emuMZ core. +// +// First the ROM based default constants. These are used in the initial startup/configuration or configuration reset. They are copied into working memory as needed. +const static t_emuControl emuControlDefault = { + .active = 0, .debug = 1, .activeDialog = DIALOG_MENU, + .activeMenu = { + .menu[0] = MENU_DISABLED, .activeRow[0] = 0, .menuIdx = 0 + }, + .activeDir = { + .dir[0] = NULL, .activeRow[0] = 0, .dirIdx = 0 + }, + .menu = { + .rowPixelStart = 15, .colPixelStart = 40, .padding = 2, .colPixelsEnd = 12, + .inactiveFgColour = WHITE, .inactiveBgColour = BLACK, .greyedFgColour = BLUE, .greyedBgColour = BLACK, .textFgColour = PURPLE, .textBgColour = BLACK, .activeFgColour = BLUE, .activeBgColour = WHITE, + .font = FONT_7X8, .rowFontptr = &font7x8extended, + .activeRow = -1 + }, + .fileList = { + .rowPixelStart = 15, .colPixelStart = 40, .padding = 2, .colPixelsEnd = 12, .selectDir = 0, + .inactiveFgColour = WHITE, .inactiveBgColour = BLACK, .activeFgColour = BLUE, .activeBgColour = WHITE, + .font = FONT_5X7, .rowFontptr = &font5x7extended, + .activeRow = -1 + } + }; + +// Default configuration values for each emulation. As the number of target hosts on which the tranZPUter and emuMZ increase this will increase. This initial aim is that one binary fits all targets, ie. upload this software into an MZ-700/MZ-80A/MZ-2000 +// hosted tranZPUter it will detect the hardware and adapt. This is fine so long as there is free resources in the MK64FX512's 512KB ROM and FPGA but may need to be revisited in future, ie. be stored on disk. +const static t_emuConfig emuConfigDefault_MZ700 = { + .machineModel = MZ80K, .machineGroup = GROUP_MZ80K, .machineChanged = 1, + .params[MZ80K] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80K", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 0, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sp1002.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80k_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_80K_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80kfdif.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000400 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80C] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80C", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 0, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sp1002.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80c_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_80C_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80kfdif.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000400 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ1200] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ1200", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sa1510.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80c_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_1200_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80a_fdc.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000800 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80A] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80A", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sa1510.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "0:\\TZFS\\sa1510-8.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80a_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_80A_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_USER_ROM_ADDR, .loadSize = 0x00000800 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80a_fdc.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000800 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ700] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ700", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\1z-013a.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "0:\\TZFS\\1z-013a-8.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz700_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_700_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz-1e05.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00001000 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ800] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ800", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz800_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00004000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz800_cgrom.rom", .romEnabled = 0, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_800_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ1500] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ1500", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz1500_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00004000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz1500_cgrom.rom", .romEnabled = 0, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_1500_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80B] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 2, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80B", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz80b_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00000800 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00000800 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80b_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_80B_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2000] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 4, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2000", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2000_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2000_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_2000_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2200] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2200", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2200-ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2200_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_2200_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2500] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2500", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2500-ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2500_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\700_2500_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + } + }; + +// Default configuration for an MZ-80A host. +const static t_emuConfig emuConfigDefault_MZ80A = { + .machineModel = MZ80K, .machineGroup = GROUP_MZ80K, .machineChanged = 1, + .params[MZ80K] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80K", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 0, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sp1002.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80k_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_80K_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80kfdif.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000400 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80C] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80C", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 0, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sp1002.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80c_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_80C_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80kfdif.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000400 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ1200] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ1200", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sa1510.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80c_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_1200_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80a_fdc.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000800 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80A] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80A", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sa1510.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "0:\\TZFS\\sa1510-8.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80a_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_80A_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_USER_ROM_ADDR, .loadSize = 0x00000800 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80a_fdc.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000800 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ700] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ700", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\1z-013a.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "0:\\TZFS\\1z-013a-8.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz700_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_700_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz-1e05.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00001000 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ800] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ800", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz800_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00004000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz800_cgrom.rom", .romEnabled = 0, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_800_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ1500] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ1500", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz1500_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00004000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz1500_cgrom.rom", .romEnabled = 0, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_1500_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80B] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 2, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80B", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz80b_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00000800 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00000800 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80b_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_80B_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2000] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 4, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2000", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2000_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2000_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_2000_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2200] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2200", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2200-ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2200_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_2200_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2500] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_640x480, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2500", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2500-ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2500_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\80A_2500_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + } + }; + +// Default configuration for an MZ-2000 host. +const static t_emuConfig emuConfigDefault_MZ2000 = { + .machineModel = MZ80K, .machineGroup = GROUP_MZ80K, .machineChanged = 1, + .params[MZ80K] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80K", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 0, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sp1002.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80k_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_80K_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80kfdif.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000400 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80C] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80C", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 0, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_160K, .polarity = POLARITY_NORMAL, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sp1002.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80c_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_80C_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80kfdif.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000400 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ1200] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ1200", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sa1510.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80c_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_1200_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80a_fdc.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000800 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80A] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80A", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\sa1510.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "0:\\TZFS\\sa1510-8.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80a_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_80A_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_USER_ROM_ADDR, .loadSize = 0x00000800 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz80a_fdc.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00000800 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ700] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ700", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\1z-013a.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "0:\\TZFS\\1z-013a-8.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz700_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_700_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "0:\\TZFS\\mz-1e05.rom", .romEnabled = 1, .loadAddr = MZ_EMU_FDC_ROM_ADDR, .loadSize = 0x00001000 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ800] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ800", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz800_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00004000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz800_cgrom.rom", .romEnabled = 0, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_800_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ1500] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ1500", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz1500_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00004000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz1500_cgrom.rom", .romEnabled = 0, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00001000 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_1500_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00001000 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ80B] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 2, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ80B", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz80b_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00000800 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00000800 }, + .romCG = { .romFileName = "0:\\TZFS\\mz80b_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_80B_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2000] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 4, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2000", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2000_ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2000_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_2000_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2200] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_MONO, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2200", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2200-ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2200_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_2200_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + }, + .params[MZ2500] = { + .cpuSpeed = 0 , .memSize = 1, .audioSource = 0, .audioHardware = 1, .audioVolume = 1, .audioMute = 0, .audioMix = 0, .displayType = MZ_EMU_DISPLAY_COLOUR, .displayOption = 0, .displayOutput = VMMODE_VGA_INT, + .vramMode = 0, .vramWaitMode = 0, .gramMode = 0, .pcgMode = 0, .aspectRatio = 0, .scanDoublerFX = 0, .loadDirectFilter = 0, + .mz800Mode = 0, .mz800Printer = 0, .mz800TapeIn = 0, .queueTapeFilter = 0, .tapeButtons = 3, .fastTapeLoad = 2, .tapeSavePath = "0:\\MZF\\MZ2500", + .cmtAsciiMapping = 3, .cmtMode = 0, .fddEnabled = 1, .autoStart = 0, + .fdd[0] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[1] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[2] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .fdd[3] = { .fileName = "", .imgType = IMAGETYPE_IMG, .mounted = 0, .diskType = DISKTYPE_320K, .polarity = POLARITY_INVERTED, .updateMode = UPDATEMODE_READWRITE }, + .romMonitor40 = { .romFileName = "0:\\TZFS\\mz2500-ipl.rom", .romEnabled = 1, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romMonitor80 = { .romFileName = "", .romEnabled = 0, .loadAddr = MZ_EMU_ROM_ADDR, .loadSize = 0x00001000 }, + .romCG = { .romFileName = "0:\\TZFS\\mz2500_cgrom.rom", .romEnabled = 1, .loadAddr = MZ_EMU_CGROM_ADDR, .loadSize = 0x00000800 }, + .romKeyMap = { .romFileName = "0:\\TZFS\\2000_2500_km.rom", .romEnabled = 1, .loadAddr = MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_MAP_ADDR, .loadSize = 0x00000200 }, + .romUser = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .romFDC = { .romFileName = "", .romEnabled = 0, .loadAddr = 0x000000, .loadSize = 0x00000100 }, + .loadApp = { .appFileName = "", .appEnabled = 0, .preKeyInsertion = {}, .postKeyInsertion = {} } + } + }; + +const static t_scanMap mapToScanCode[] = { // MZ-80K MZ-80C MZ-1200 MZ-80A MZ-700 MZ-1500 MZ-800 MZ-80B MZ-2000 MZ-2200 MZ-2500 + { 'A', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 1, 0xf7, KEY_NOCTRL_BIT }, { 1, 0xf7, KEY_NOCTRL_BIT }, { 4, 0x7f, KEY_NOCTRL_BIT }, { 4, 0x7f, KEY_NOCTRL_BIT }, { 4, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'B', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xfe, KEY_NOCTRL_BIT }, { 3, 0xfe, KEY_NOCTRL_BIT }, { 4, 0xbf, KEY_NOCTRL_BIT }, { 4, 0xbf, KEY_NOCTRL_BIT }, { 4, 0xbf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'C', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xfe, KEY_NOCTRL_BIT }, { 2, 0xfe, KEY_NOCTRL_BIT }, { 4, 0xdf, KEY_NOCTRL_BIT }, { 4, 0xdf, KEY_NOCTRL_BIT }, { 4, 0xdf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'D', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xf7, KEY_NOCTRL_BIT }, { 2, 0xf7, KEY_NOCTRL_BIT }, { 4, 0xef, KEY_NOCTRL_BIT }, { 4, 0xef, KEY_NOCTRL_BIT }, { 4, 0xef, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'E', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xef, KEY_NOCTRL_BIT }, { 2, 0xef, KEY_NOCTRL_BIT }, { 4, 0xf7, KEY_NOCTRL_BIT }, { 4, 0xf7, KEY_NOCTRL_BIT }, { 4, 0xf7, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'F', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xfb, KEY_NOCTRL_BIT }, { 3, 0xfb, KEY_NOCTRL_BIT }, { 4, 0xfb, KEY_NOCTRL_BIT }, { 4, 0xfb, KEY_NOCTRL_BIT }, { 4, 0xfb, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'G', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xf7, KEY_NOCTRL_BIT }, { 3, 0xf7, KEY_NOCTRL_BIT }, { 4, 0xfd, KEY_NOCTRL_BIT }, { 4, 0xfd, KEY_NOCTRL_BIT }, { 4, 0xfd, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'H', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xfb, KEY_NOCTRL_BIT }, { 4, 0xfb, KEY_NOCTRL_BIT }, { 4, 0xfe, KEY_NOCTRL_BIT }, { 4, 0xfe, KEY_NOCTRL_BIT }, { 4, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'I', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xdf, KEY_NOCTRL_BIT }, { 4, 0xdf, KEY_NOCTRL_BIT }, { 3, 0x7f, KEY_NOCTRL_BIT }, { 3, 0x7f, KEY_NOCTRL_BIT }, { 3, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'J', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xf7, KEY_NOCTRL_BIT }, { 4, 0xf7, KEY_NOCTRL_BIT }, { 3, 0xbf, KEY_NOCTRL_BIT }, { 3, 0xbf, KEY_NOCTRL_BIT }, { 3, 0xbf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'K', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 3, 0xdf, KEY_NOCTRL_BIT }, { 3, 0xdf, KEY_NOCTRL_BIT }, { 3, 0xdf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'L', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xf7, KEY_NOCTRL_BIT }, { 5, 0xf7, KEY_NOCTRL_BIT }, { 3, 0xef, KEY_NOCTRL_BIT }, { 3, 0xef, KEY_NOCTRL_BIT }, { 3, 0xef, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'M', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xfe, KEY_NOCTRL_BIT }, { 5, 0xfe, KEY_NOCTRL_BIT }, { 3, 0xf7, KEY_NOCTRL_BIT }, { 3, 0xf7, KEY_NOCTRL_BIT }, { 3, 0xf7, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'N', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xfd, KEY_NOCTRL_BIT }, { 4, 0xfd, KEY_NOCTRL_BIT }, { 3, 0xfb, KEY_NOCTRL_BIT }, { 3, 0xfb, KEY_NOCTRL_BIT }, { 3, 0xfb, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'O', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 3, 0xfd, KEY_NOCTRL_BIT }, { 3, 0xfd, KEY_NOCTRL_BIT }, { 3, 0xfd, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'P', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xdf, KEY_NOCTRL_BIT }, { 5, 0xdf, KEY_NOCTRL_BIT }, { 3, 0xfe, KEY_NOCTRL_BIT }, { 3, 0xfe, KEY_NOCTRL_BIT }, { 3, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'Q', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 2, 0x7f, KEY_NOCTRL_BIT }, { 2, 0x7f, KEY_NOCTRL_BIT }, { 2, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'R', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xdf, KEY_NOCTRL_BIT }, { 2, 0xdf, KEY_NOCTRL_BIT }, { 2, 0xbf, KEY_NOCTRL_BIT }, { 2, 0xbf, KEY_NOCTRL_BIT }, { 2, 0xbf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'S', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xfb, KEY_NOCTRL_BIT }, { 2, 0xfb, KEY_NOCTRL_BIT }, { 2, 0xdf, KEY_NOCTRL_BIT }, { 2, 0xdf, KEY_NOCTRL_BIT }, { 2, 0xdf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'T', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xef, KEY_NOCTRL_BIT }, { 3, 0xef, KEY_NOCTRL_BIT }, { 2, 0xef, KEY_NOCTRL_BIT }, { 2, 0xef, KEY_NOCTRL_BIT }, { 2, 0xef, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'U', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xef, KEY_NOCTRL_BIT }, { 4, 0xef, KEY_NOCTRL_BIT }, { 2, 0xf7, KEY_NOCTRL_BIT }, { 2, 0xf7, KEY_NOCTRL_BIT }, { 2, 0xf7, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'V', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xfd, KEY_NOCTRL_BIT }, { 3, 0xfd, KEY_NOCTRL_BIT }, { 2, 0xfb, KEY_NOCTRL_BIT }, { 2, 0xfb, KEY_NOCTRL_BIT }, { 2, 0xfb, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'W', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 1, 0xdf, KEY_NOCTRL_BIT }, { 1, 0xdf, KEY_NOCTRL_BIT }, { 2, 0xfd, KEY_NOCTRL_BIT }, { 2, 0xfd, KEY_NOCTRL_BIT }, { 2, 0xfd, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'X', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xfd, KEY_NOCTRL_BIT }, { 2, 0xfd, KEY_NOCTRL_BIT }, { 2, 0xfe, KEY_NOCTRL_BIT }, { 2, 0xfe, KEY_NOCTRL_BIT }, { 2, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'Y', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xdf, KEY_NOCTRL_BIT }, { 3, 0xdf, KEY_NOCTRL_BIT }, { 1, 0x7f, KEY_NOCTRL_BIT }, { 1, 0x7f, KEY_NOCTRL_BIT }, { 1, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 'Z', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 1, 0xfe, KEY_NOCTRL_BIT }, { 1, 0xfe, KEY_NOCTRL_BIT }, { 1, 0xbf, KEY_NOCTRL_BIT }, { 1, 0xbf, KEY_NOCTRL_BIT }, { 1, 0xbf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + + { '0', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0x7f, KEY_NOCTRL_BIT }, { 5, 0x7f, KEY_NOCTRL_BIT }, { 6, 0xf7, KEY_NOCTRL_BIT }, { 6, 0xf7, KEY_NOCTRL_BIT }, { 6, 0xf7, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '1', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 1, 0xbf, KEY_NOCTRL_BIT }, { 1, 0xbf, KEY_NOCTRL_BIT }, { 5, 0x7f, KEY_NOCTRL_BIT }, { 5, 0x7f, KEY_NOCTRL_BIT }, { 5, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '2', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 1, 0x7f, KEY_NOCTRL_BIT }, { 1, 0x7f, KEY_NOCTRL_BIT }, { 5, 0xbf, KEY_NOCTRL_BIT }, { 5, 0xbf, KEY_NOCTRL_BIT }, { 5, 0xbf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '3', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xbf, KEY_NOCTRL_BIT }, { 2, 0xbf, KEY_NOCTRL_BIT }, { 5, 0xdf, KEY_NOCTRL_BIT }, { 5, 0xdf, KEY_NOCTRL_BIT }, { 5, 0xdf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '4', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0x7f, KEY_NOCTRL_BIT }, { 2, 0x7f, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '5', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xbf, KEY_NOCTRL_BIT }, { 3, 0xbf, KEY_NOCTRL_BIT }, { 5, 0xf7, KEY_NOCTRL_BIT }, { 5, 0xf7, KEY_NOCTRL_BIT }, { 5, 0xf7, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '6', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0x7f, KEY_NOCTRL_BIT }, { 3, 0x7f, KEY_NOCTRL_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '7', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xbf, KEY_NOCTRL_BIT }, { 4, 0xbf, KEY_NOCTRL_BIT }, { 5, 0xfd, KEY_NOCTRL_BIT }, { 5, 0xfd, KEY_NOCTRL_BIT }, { 5, 0xfd, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '8', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0x7f, KEY_NOCTRL_BIT }, { 4, 0x7f, KEY_NOCTRL_BIT }, { 5, 0xfe, KEY_NOCTRL_BIT }, { 5, 0xfe, KEY_NOCTRL_BIT }, { 5, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '9', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xbf, KEY_NOCTRL_BIT }, { 5, 0xbf, KEY_NOCTRL_BIT }, { 6, 0xfb, KEY_NOCTRL_BIT }, { 6, 0xfb, KEY_NOCTRL_BIT }, { 6, 0xfb, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + + { '_', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0x7f, KEY_SHIFT_BIT }, { 5, 0x7f, KEY_SHIFT_BIT }, { 0, 0xdf, KEY_NOCTRL_BIT }, { 0, 0xdf, KEY_NOCTRL_BIT }, { 0, 0xdf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '!', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 1, 0xbf, KEY_SHIFT_BIT }, { 1, 0xbf, KEY_SHIFT_BIT }, { 5, 0x7f, KEY_SHIFT_BIT }, { 5, 0x7f, KEY_SHIFT_BIT }, { 5, 0x7f, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '"', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 1, 0x7f, KEY_SHIFT_BIT }, { 1, 0x7f, KEY_SHIFT_BIT }, { 5, 0xbf, KEY_SHIFT_BIT }, { 5, 0xbf, KEY_SHIFT_BIT }, { 5, 0xbf, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '#', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0xbf, KEY_SHIFT_BIT }, { 2, 0xbf, KEY_SHIFT_BIT }, { 5, 0xdf, KEY_SHIFT_BIT }, { 5, 0xdf, KEY_SHIFT_BIT }, { 5, 0xdf, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '$', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 2, 0x7f, KEY_SHIFT_BIT }, { 2, 0x7f, KEY_SHIFT_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 5, 0xef, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '%', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0xbf, KEY_SHIFT_BIT }, { 3, 0xbf, KEY_SHIFT_BIT }, { 5, 0xf7, KEY_SHIFT_BIT }, { 5, 0xf7, KEY_SHIFT_BIT }, { 5, 0xf7, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '&', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 3, 0x7f, KEY_SHIFT_BIT }, { 3, 0x7f, KEY_SHIFT_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 5, 0xfb, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '\'', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xbf, KEY_SHIFT_BIT }, { 4, 0xbf, KEY_SHIFT_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '(', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0x7f, KEY_SHIFT_BIT }, { 4, 0x7f, KEY_SHIFT_BIT }, { 5, 0xfe, KEY_SHIFT_BIT }, { 5, 0xfe, KEY_SHIFT_BIT }, { 5, 0xfe, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { ')', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xbf, KEY_SHIFT_BIT }, { 5, 0xbf, KEY_SHIFT_BIT }, { 6, 0xfb, KEY_SHIFT_BIT }, { 6, 0xfb, KEY_SHIFT_BIT }, { 6, 0xfb, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '^', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 6, 0xbf, KEY_NOCTRL_BIT }, { 6, 0xbf, KEY_NOCTRL_BIT }, { 6, 0xbf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '~', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_SHIFT_BIT }, { 6, 0x7f, KEY_SHIFT_BIT }, { 6, 0xbf, KEY_SHIFT_BIT }, { 6, 0xbf, KEY_SHIFT_BIT }, { 6, 0xbf, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '-', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xbf, KEY_NOCTRL_BIT }, { 6, 0xbf, KEY_NOCTRL_BIT }, { 1, 0xdf, KEY_SHIFT_BIT }, { 1, 0xdf, KEY_SHIFT_BIT }, { 1, 0xdf, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '=', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xbf, KEY_SHIFT_BIT }, { 6, 0xbf, KEY_SHIFT_BIT }, { 6, 0xdf, KEY_SHIFT_BIT }, { 6, 0xdf, KEY_SHIFT_BIT }, { 6, 0xdf, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '\\', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 7, 0xbf, KEY_NOCTRL_BIT }, { 7, 0xbf, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 6, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '|', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 7, 0xbf, KEY_SHIFT_BIT }, { 7, 0xbf, KEY_SHIFT_BIT }, { 6, 0x7f, KEY_SHIFT_BIT }, { 6, 0x7f, KEY_SHIFT_BIT }, { 6, 0x7f, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '[', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xdf, KEY_NOCTRL_BIT }, { 6, 0xdf, KEY_NOCTRL_BIT }, { 1, 0xef, KEY_NOCTRL_BIT }, { 1, 0xef, KEY_NOCTRL_BIT }, { 1, 0xef, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '{', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xdf, KEY_SHIFT_BIT }, { 6, 0xdf, KEY_SHIFT_BIT }, { 1, 0xef, KEY_SHIFT_BIT }, { 1, 0xef, KEY_SHIFT_BIT }, { 1, 0xef, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { ']', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 7, 0xfb, KEY_NOCTRL_BIT }, { 7, 0xfb, KEY_NOCTRL_BIT }, { 1, 0xf7, KEY_NOCTRL_BIT }, { 1, 0xf7, KEY_NOCTRL_BIT }, { 1, 0xf7, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '}', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 7, 0xfb, KEY_SHIFT_BIT }, { 7, 0xfb, KEY_SHIFT_BIT }, { 1, 0xf7, KEY_SHIFT_BIT }, { 1, 0xf7, KEY_SHIFT_BIT }, { 1, 0xf7, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { ':', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xf7, KEY_NOCTRL_BIT }, { 6, 0xf7, KEY_NOCTRL_BIT }, { 0, 0xfd, KEY_NOCTRL_BIT }, { 0, 0xfd, KEY_NOCTRL_BIT }, { 0, 0xfd, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '*', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xf7, KEY_SHIFT_BIT }, { 6, 0xf7, KEY_SHIFT_BIT }, { 0, 0xfd, KEY_SHIFT_BIT }, { 0, 0xfd, KEY_SHIFT_BIT }, { 0, 0xfd, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { ';', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xfb, KEY_NOCTRL_BIT }, { 6, 0xfb, KEY_NOCTRL_BIT }, { 0, 0xfb, KEY_NOCTRL_BIT }, { 0, 0xfb, KEY_NOCTRL_BIT }, { 0, 0xfb, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '+', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xfb, KEY_SHIFT_BIT }, { 6, 0xfb, KEY_SHIFT_BIT }, { 0, 0xfb, KEY_SHIFT_BIT }, { 0, 0xfb, KEY_SHIFT_BIT }, { 0, 0xfb, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { ',', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xfd, KEY_NOCTRL_BIT }, { 5, 0xfd, KEY_NOCTRL_BIT }, { 6, 0xfd, KEY_NOCTRL_BIT }, { 6, 0xfd, KEY_NOCTRL_BIT }, { 6, 0xfd, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '<', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 5, 0xfd, KEY_SHIFT_BIT }, { 5, 0xfd, KEY_SHIFT_BIT }, { 6, 0xfd, KEY_SHIFT_BIT }, { 6, 0xfd, KEY_SHIFT_BIT }, { 6, 0xfd, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '.', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xfe, KEY_NOCTRL_BIT }, { 6, 0xfe, KEY_NOCTRL_BIT }, { 6, 0xfe, KEY_NOCTRL_BIT }, { 6, 0xfe, KEY_NOCTRL_BIT }, { 6, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '>', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xfe, KEY_SHIFT_BIT }, { 6, 0xfe, KEY_SHIFT_BIT }, { 6, 0xfe, KEY_SHIFT_BIT }, { 6, 0xfe, KEY_SHIFT_BIT }, { 6, 0xfe, KEY_SHIFT_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '/', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 6, 0xfd, KEY_NOCTRL_BIT }, { 6, 0xfd, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { '?', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 7, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 0x0d, { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 7, 0xf7, KEY_NOCTRL_BIT }, { 7, 0xf7, KEY_NOCTRL_BIT }, { 0, 0xfe, KEY_NOCTRL_BIT }, { 0, 0xfe, KEY_NOCTRL_BIT }, { 0, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { ' ', { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 4, 0xfe, KEY_NOCTRL_BIT }, { 4, 0xfe, KEY_NOCTRL_BIT }, { 6, 0xef, KEY_NOCTRL_BIT }, { 6, 0xef, KEY_NOCTRL_BIT }, { 6, 0xef, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 0xf8, { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0, 0xfe, KEY_NOCTRL_BIT }, { 0, 0xfe, KEY_NOCTRL_BIT }, { 8, 0xfe, KEY_NOCTRL_BIT }, { 8, 0xfe, KEY_NOCTRL_BIT }, { 8, 0xfe, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 0xf9, { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0, 0x7f, KEY_NOCTRL_BIT }, { 0, 0x7f, KEY_NOCTRL_BIT }, { 8, 0xbf, KEY_NOCTRL_BIT }, { 8, 0xbf, KEY_NOCTRL_BIT }, { 8, 0xbf, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + { 0xfa, { { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0, 0x7f, KEY_NOCTRL_BIT }, { 0, 0x7f, KEY_NOCTRL_BIT }, { 8, 0x7f, KEY_NOCTRL_BIT }, { 8, 0x7f, KEY_NOCTRL_BIT }, { 8, 0x7f, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT }, { 0xff, 0xff, KEY_NOCTRL_BIT } } }, + }; + +// Configuration working structures. Declared static rather than malloc'd as they are used so often and malloc doesnt offer any benefit for an integral data block. +static t_emuControl emuControl; +static t_emuConfig emuConfig; + +// Real time millisecond counter, interrupt driven. Needs to be volatile in order to prevent the compiler optimising it away. +uint32_t volatile *ms = &systick_millis_count; + +// Method to return the emulation control software version. +static const char version[8]; +const char *EMZGetVersion(void) +{ + sprintf(version, "v%.2f", EMUMZ_VERSION); + return(version); +} + +// Method to return the emulation control software version date. +static const char versionDate[sizeof(EMUMZ_VERSION_DATE)+1]; +const char *EMZGetVersionDate(void) +{ + sprintf(versionDate, "%s", EMUMZ_VERSION_DATE); + return(versionDate); +} + +// Method to lookup a given key for a given machine and if found return the keyboard row/col scan codes and any key modifier. +// +t_numCnv EMZMapToScanCode(enum MACHINE_HW_TYPES machine, uint8_t key) +{ + // Locals. + uint16_t idx; + uint8_t row = 0xff, shiftRow = 0xff, ctrlRow = 0xff, breakRow = 0xff; + uint8_t col = 0xff, shiftCol = 0xff, ctrlCol = 0xff, breakCol = 0xff; + uint8_t mod = 0; + t_numCnv result; + + // Loop through the lookup table, based on the host layout, and try to find a key match. + for(idx=0; idx < NUMELEM(mapToScanCode); idx++) + { + // Key matched? + if(mapToScanCode[idx].key == toupper(key)) + { + row = mapToScanCode[idx].code[machine].scanRow; + col = mapToScanCode[idx].code[machine].scanCol; + mod = mapToScanCode[idx].code[machine].scanCtrl; + } + + // Match SHIFT? + if(mapToScanCode[idx].key == 0xf8) + { + shiftRow = mapToScanCode[idx].code[machine].scanRow; + shiftCol = mapToScanCode[idx].code[machine].scanCol; + } + + // Match CTRL? + if(mapToScanCode[idx].key == 0xf9) + { + ctrlRow = mapToScanCode[idx].code[machine].scanRow; + ctrlCol = mapToScanCode[idx].code[machine].scanCol; + } + + // Match BREAK? + if(mapToScanCode[idx].key == 0xfa) + { + breakRow = mapToScanCode[idx].code[machine].scanRow; + breakCol = mapToScanCode[idx].code[machine].scanCol; + } + } + // Lower case keys arent stored in the table so update the shift modifier if lower case. + if(row != 0xff && key >= 'a' && key <= 'z') + { + mod = KEY_SHIFT_BIT; + } + + // Put data into correct part of the 32bit return word. 0 = Key Row, 1 = Key Col, 2 = Modifier Row, 3 = Modifier Col. 0xff = not valid. + result.b[0] = row; + result.b[1] = col; + result.b[2] = mod == KEY_SHIFT_BIT ? shiftRow : mod == KEY_CTRL_BIT ? ctrlRow : mod == KEY_BREAK_BIT ? breakRow : 0xff; + result.b[3] = mod == KEY_SHIFT_BIT ? shiftCol : mod == KEY_CTRL_BIT ? ctrlCol : mod == KEY_BREAK_BIT ? breakCol : 0xff; + + // Return result. + return(result); +} + +// Method to set the menu row padding (ie. pixel spacing above/below the characters). +void EMZSetMenuRowPadding(uint8_t padding) +{ + // Sanity check. + // + if(padding > ((uint16_t)OSDGet(ACTIVE_MAX_Y) / 8)) + return; + + // Store padding in private member. + emuControl.menu.padding = padding; + return; +} + +// Method to set the font for use in row characters. +// +void EMZSetMenuFont(enum FONTS font) +{ + emuControl.menu.rowFontptr = OSDGetFont(font); + emuControl.menu.font = font; +} + +// Method to change the row active colours. +// +void EMZSetRowColours(enum COLOUR rowFg, enum COLOUR rowBg, enum COLOUR greyedFg, enum COLOUR greyedBg, enum COLOUR textFg, enum COLOUR textBg, enum COLOUR activeFg, enum COLOUR activeBg) +{ + emuControl.menu.inactiveFgColour = rowFg; + emuControl.menu.inactiveBgColour = rowBg; + emuControl.menu.greyedFgColour = greyedFg; + emuControl.menu.greyedBgColour = greyedBg; + emuControl.menu.textFgColour = textFg; + emuControl.menu.textBgColour = textBg; + emuControl.menu.activeFgColour = activeFg; + emuControl.menu.activeBgColour = activeBg; +} + +// Method to get the maximum number of columns available for a menu row with the current selected font. +// +uint16_t EMZGetMenuColumnWidth(void) +{ + uint16_t maxPixels = OSDGet(ACTIVE_MAX_X); + return( (maxPixels - emuControl.menu.colPixelStart - emuControl.menu.colPixelsEnd) / (emuControl.menu.rowFontptr->width + emuControl.menu.rowFontptr->spacing) ); +} + +// Get the group to which the current machine belongs: +// 0 - MZ80K/C/A type +// 1 - MZ700 type +// 2 - MZ80B/2000 type +// +short EMZGetMachineGroup(void) +{ + short machineGroup = GROUP_MZ80K; + + // Set value according to machine model. + // + switch(emuConfig.machineModel) + { + // These machines currently underdevelopment, so fall through to MZ80K + case MZ80B: + case MZ2000: + case MZ2200: + case MZ2500: + machineGroup = GROUP_MZ80B; + break; + + case MZ80K: + case MZ80C: + case MZ1200: + case MZ80A: + machineGroup = GROUP_MZ80K; + break; + + case MZ700: + case MZ1500: + case MZ800: + machineGroup = GROUP_MZ700; + break; + + default: + machineGroup = GROUP_MZ80K; + break; + } + + return(machineGroup); +} + +// Method to return a char string which represents the current selected machine name. +const char *EMZGetMachineModelChoice(void) +{ + // Locals. + // + + return(MZMACHINES[emuConfig.machineModel]); +} + +// Method to make the side bar title from the active machine. +char *EMZGetMachineTitle(void) +{ + static char title[MAX_MACHINE_TITLE_LEN]; + + sprintf(title, "SHARP %s", EMZGetMachineModelChoice()); + return(title); +} + + +// Method to change the emulated machine, choice based on the actual implemented machines in the FPGA core. +void EMZNextMachineModel(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + // Forward to next active machine - skip machines under development or not instantiated. + do { + emuConfig.machineModel = (emuConfig.machineModel+1 >= MAX_MZMACHINES ? 0 : emuConfig.machineModel+1); + emuConfig.machineGroup = EMZGetMachineGroup(); + } while(MZ_ACTIVE[emuConfig.machineModel] == 0); + emuConfig.machineChanged = 1; + + // Need to rewrite the menu as the choice will affect displayed items. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + return; +} + +// Method to return a char string which represents the current selected CPU speed. +const char *EMZGetCPUSpeedChoice(void) +{ + // Locals. + // + + return(SHARPMZ_CPU_SPEED[emuConfig.machineGroup][emuConfig.params[emuConfig.machineModel].cpuSpeed]); +} + +// Method to change the CPU Speed, choice based on the actual selected machine. +void EMZNextCPUSpeed(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].cpuSpeed = (emuConfig.params[emuConfig.machineModel].cpuSpeed+1 >= NUMELEM(SHARPMZ_CPU_SPEED[emuConfig.machineGroup]) || SHARPMZ_CPU_SPEED[emuConfig.machineGroup][emuConfig.params[emuConfig.machineModel].cpuSpeed+1] == NULL ? 0 : emuConfig.params[emuConfig.machineModel].cpuSpeed+1); + } + return; +} + +// Method to return a char string which represents the current selected memory size. +const char *EMZGetMemSizeChoice(void) +{ + // Locals. + // + + return(SHARPMZ_MEM_SIZE[emuConfig.machineModel][emuConfig.params[emuConfig.machineModel].memSize]); +} + +// Method to change the memory size, choice based on the actual selected machine. +void EMZNextMemSize(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + // Move to the next valid entry, looping round as necessary. + do { + emuConfig.params[emuConfig.machineModel].memSize = (emuConfig.params[emuConfig.machineModel].memSize+1 >= NUMELEM(SHARPMZ_MEM_SIZE[emuConfig.machineModel]) ? 0 : emuConfig.params[emuConfig.machineModel].memSize+1); + } while(SHARPMZ_MEM_SIZE[emuConfig.machineModel][emuConfig.params[emuConfig.machineModel].memSize] == NULL); + } + return; +} + +// Method to convert the memory size into a bit value for uploading to hardware. Normally a 1:1 but allow leeway for deviations. +uint8_t EMZGetMemSizeValue(void) +{ + // Locals. + uint8_t result; + + // Decode according to machine selected. + // + switch(emuConfig.machineModel) + { + case MZ80K: + case MZ80C: + case MZ1200: + case MZ80A: + case MZ700: + case MZ1500: + case MZ800: + case MZ80B: + case MZ2000: + case MZ2200: + result = emuConfig.params[emuConfig.machineModel].memSize; + break; + + case MZ2500: + result = 0x00; + break; + + } + return(result); +} + +// Method to return a char string which represents the current selected MZ800 Mode. +const char *EMZGetMZ800ModeChoice(void) +{ + // Locals. + // + return(SHARPMZ_MZ800_MODE[emuConfig.params[emuConfig.machineModel].mz800Mode]); +} + +// Method to change the MZ800 Mode. +void EMZNextMZ800Mode(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].mz800Mode = (emuConfig.params[emuConfig.machineModel].mz800Mode+1 >= NUMELEM(SHARPMZ_MZ800_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].mz800Mode+1); + } + return; +} + +// Method to return a char string which represents the current selected MZ800 Printer setting. +const char *EMZGetMZ800PrinterChoice(void) +{ + // Locals. + // + return(SHARPMZ_MZ800_PRINTER[emuConfig.params[emuConfig.machineModel].mz800Printer]); +} + +// Method to change the MZ800 Printer setting. +void EMZNextMZ800Printer(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].mz800Printer = (emuConfig.params[emuConfig.machineModel].mz800Printer+1 >= NUMELEM(SHARPMZ_MZ800_PRINTER) ? 0 : emuConfig.params[emuConfig.machineModel].mz800Printer+1); + } + return; +} + +// Method to return a char string which represents the current selected MZ800 Printer setting. +const char *EMZGetMZ800TapeInChoice(void) +{ + // Locals. + // + return(SHARPMZ_MZ800_TAPEIN[emuConfig.params[emuConfig.machineModel].mz800TapeIn]); +} + +// Method to change the MZ800 Tape Input setting. +void EMZNextMZ800TapeIn(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].mz800TapeIn = (emuConfig.params[emuConfig.machineModel].mz800TapeIn+1 >= NUMELEM(SHARPMZ_MZ800_TAPEIN) ? 0 : emuConfig.params[emuConfig.machineModel].mz800TapeIn+1); + } + return; +} + +// Method to return a char string which represents the current selected Audio Source. +const char *EMZGetAudioSourceChoice(void) +{ + // Locals. + // + return(SHARPMZ_AUDIO_SOURCE[emuConfig.params[emuConfig.machineModel].audioSource]); +} + +// Method to change the Audio Source, choice based on the actual selected machine. +void EMZNextAudioSource(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].audioSource = (emuConfig.params[emuConfig.machineModel].audioSource+1 >= NUMELEM(SHARPMZ_AUDIO_SOURCE) ? 0 : emuConfig.params[emuConfig.machineModel].audioSource+1); + + // Write the updated value immediately so as to change the audio source. + emuConfig.emuRegisters[MZ_EMU_REG_AUDIO] = emuConfig.params[emuConfig.machineModel].audioHardware << 7 | emuConfig.params[emuConfig.machineModel].audioMix << 5 | (emuConfig.params[emuConfig.machineModel].audioMute == 1 ? 0 : emuConfig.params[emuConfig.machineModel].audioVolume << 1) | emuConfig.params[emuConfig.machineModel].audioSource; + writeZ80Array(MZ_EMU_ADDR_REG_AUDIO, &emuConfig.emuRegisters[MZ_EMU_REG_AUDIO], 1, FPGA); + } + return; +} + +// Method to return a char string which represents the current selected Audio Hardware Driver. +const char *EMZGetAudioHardwareChoice(void) +{ + // Locals. + // + return(SHARPMZ_AUDIO_HARDWARE[emuConfig.params[emuConfig.machineModel].audioHardware]); +} + +// Method to change the Audio Hardware Driver, choice based on the actual selected machine. +void EMZNextAudioHardware(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].audioHardware = (emuConfig.params[emuConfig.machineModel].audioHardware+1 >= NUMELEM(SHARPMZ_AUDIO_HARDWARE) ? 0 : emuConfig.params[emuConfig.machineModel].audioHardware+1); + + // Write the updated value immediately so as to change the sound hardware. + emuConfig.emuRegisters[MZ_EMU_REG_AUDIO] = emuConfig.params[emuConfig.machineModel].audioHardware << 7 | emuConfig.params[emuConfig.machineModel].audioMix << 5 | (emuConfig.params[emuConfig.machineModel].audioMute == 1 ? 0 : emuConfig.params[emuConfig.machineModel].audioVolume << 1) | emuConfig.params[emuConfig.machineModel].audioSource; + writeZ80Array(MZ_EMU_ADDR_REG_AUDIO, &emuConfig.emuRegisters[MZ_EMU_REG_AUDIO], 1, FPGA); + } + + // Need to rewrite the menu as the choice will affect displayed items. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + return; +} + +// Method to return a char string which represents the current selected Audio Volume. +const char *EMZGetAudioVolumeChoice(void) +{ + // Locals. + // + return(SHARPMZ_AUDIO_VOLUME[emuConfig.params[emuConfig.machineModel].audioVolume]); +} + +// Method to change the Audio Volume, choice based on the actual selected machine. +void EMZNextAudioVolume(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].audioVolume = (emuConfig.params[emuConfig.machineModel].audioVolume+1 >= NUMELEM(SHARPMZ_AUDIO_VOLUME) ? 0 : emuConfig.params[emuConfig.machineModel].audioVolume+1); + + // Write the updated value immediately so as to adjust the sound volume. + emuConfig.emuRegisters[MZ_EMU_REG_AUDIO] = emuConfig.params[emuConfig.machineModel].audioHardware << 7 | emuConfig.params[emuConfig.machineModel].audioMix << 5 | (emuConfig.params[emuConfig.machineModel].audioMute == 1 ? 0 : emuConfig.params[emuConfig.machineModel].audioVolume << 1) | emuConfig.params[emuConfig.machineModel].audioSource; + writeZ80Array(MZ_EMU_ADDR_REG_AUDIO, &emuConfig.emuRegisters[MZ_EMU_REG_AUDIO], 1, FPGA); + } + return; +} + +// Method to return a char string which represents the current selected Audio Mute. +const char *EMZGetAudioMuteChoice(void) +{ + // Locals. + // + return(SHARPMZ_AUDIO_MUTE[emuConfig.params[emuConfig.machineModel].audioMute]); +} + +// Method to change the Audio Mute, choice based on the actual selected machine. +void EMZNextAudioMute(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].audioMute = (emuConfig.params[emuConfig.machineModel].audioMute+1 >= NUMELEM(SHARPMZ_AUDIO_MUTE) ? 0 : emuConfig.params[emuConfig.machineModel].audioMute+1); + + // Write the updated value immediately so as to mute the sound. + emuConfig.emuRegisters[MZ_EMU_REG_AUDIO] = emuConfig.params[emuConfig.machineModel].audioHardware << 7 | emuConfig.params[emuConfig.machineModel].audioMix << 5 | (emuConfig.params[emuConfig.machineModel].audioMute == 1 ? 0 : emuConfig.params[emuConfig.machineModel].audioVolume << 1) | emuConfig.params[emuConfig.machineModel].audioSource; + writeZ80Array(MZ_EMU_ADDR_REG_AUDIO, &emuConfig.emuRegisters[MZ_EMU_REG_AUDIO], 1, FPGA); + } + return; +} + +// Method to return a char string which represents the current selected Audio channel mix. +const char *EMZGetAudioMixChoice(void) +{ + // Locals. + // + return(SHARPMZ_AUDIO_MIX[emuConfig.params[emuConfig.machineModel].audioMix]); +} + +// Method to change the Audio channel mix, choice based on the actual selected machine. +void EMZNextAudioMix(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].audioMix = (emuConfig.params[emuConfig.machineModel].audioMix+1 >= NUMELEM(SHARPMZ_AUDIO_MIX) ? 0 : emuConfig.params[emuConfig.machineModel].audioMix+1); + + // Write the updated value immediately so as to change the channel mix. + emuConfig.emuRegisters[MZ_EMU_REG_AUDIO] = emuConfig.params[emuConfig.machineModel].audioHardware << 7 | emuConfig.params[emuConfig.machineModel].audioMix << 5 | (emuConfig.params[emuConfig.machineModel].audioMute == 1 ? 0 : emuConfig.params[emuConfig.machineModel].audioVolume << 1) | emuConfig.params[emuConfig.machineModel].audioSource; + writeZ80Array(MZ_EMU_ADDR_REG_AUDIO, &emuConfig.emuRegisters[MZ_EMU_REG_AUDIO], 1, FPGA); + } + return; +} + +// Method to return a char string which represents the current selected Display Type. +const char *EMZGetDisplayTypeChoice(void) +{ + // Locals. + // + return(SHARPMZ_DISPLAY_TYPE[emuConfig.machineModel][emuConfig.params[emuConfig.machineModel].displayType]); +} + +// Method to change the Display Type, choice based on the actual selected machine. +void EMZNextDisplayType(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + // Move to the next valid entry, looping round as necessary. + do { + emuConfig.params[emuConfig.machineModel].displayType = (emuConfig.params[emuConfig.machineModel].displayType+1 >= NUMELEM(SHARPMZ_DISPLAY_TYPE[emuConfig.machineModel]) ? 0 : emuConfig.params[emuConfig.machineModel].displayType+1); + } while(SHARPMZ_DISPLAY_TYPE[emuConfig.machineModel][emuConfig.params[emuConfig.machineModel].displayType] == NULL); + } + return; +} + +// Method to return a char string which represents the current selected Display Option installed. +const char *EMZGetDisplayOptionChoice(void) +{ + // Locals. + // + + return(SHARPMZ_DISPLAY_OPTION[emuConfig.machineModel][emuConfig.params[emuConfig.machineModel].displayOption]); +} + +// Method to change the installed display option, choice based on the actual selected machine. +void EMZNextDisplayOption(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + // Move to the next valid entry, looping round as necessary. + do { + emuConfig.params[emuConfig.machineModel].displayOption = (emuConfig.params[emuConfig.machineModel].displayOption+1 >= NUMELEM(SHARPMZ_DISPLAY_OPTION[emuConfig.machineModel]) ? 0 : emuConfig.params[emuConfig.machineModel].displayOption+1); + } while(SHARPMZ_DISPLAY_OPTION[emuConfig.machineModel][emuConfig.params[emuConfig.machineModel].displayOption] == NULL); + + // Need to rewrite the menu as the choice will affect displayed items. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + return; +} + +// Method to translate the selected options into an option byte which can be written to hardware. This mechanism needs to be table driven eventually! +// +uint8_t EMZGetDisplayOptionValue(void) +{ + // Locals. + uint8_t result; + + // Decode according to machine selected. + // + switch(emuConfig.machineModel) + { + case MZ80K: + case MZ80C: + case MZ1200: + result = 0; + break; + + case MZ80A: + case MZ700: + result |= emuConfig.params[emuConfig.machineModel].displayOption == 1 ? 0x08 : 0x00; + break; + + case MZ1500: + result = 0x08; + break; + + case MZ800: + result = emuConfig.params[emuConfig.machineModel].displayOption == 1 ? 0x10 : 0x00; + break; + + case MZ80B: + result = 0x00; + result |= emuConfig.params[emuConfig.machineModel].displayOption == 1 ? 0x01 : 0x00; + result |= emuConfig.params[emuConfig.machineModel].displayOption == 2 ? 0x03 : 0x00; + printf("displayOption=%d,%d\n", emuConfig.params[emuConfig.machineModel].displayOption, result); + break; + + case MZ2000: + result = 0x00; + result |= emuConfig.params[emuConfig.machineModel].displayOption == 1 ? 0x01 : 0x00; + result |= emuConfig.params[emuConfig.machineModel].displayOption == 2 ? 0x03 : 0x00; + result |= emuConfig.params[emuConfig.machineModel].displayOption == 3 ? 0x05 : 0x00; + result |= emuConfig.params[emuConfig.machineModel].displayOption == 4 ? 0x07 : 0x00; + break; + + case MZ2200: + result = 0x07; + break; + + case MZ2500: + result = 0x00; + break; + + } + return(result); +} + +// Method to return a char string which represents the current selected Display Output. +const char *EMZGetDisplayOutputChoice(void) +{ + // Locals. + // + return(SHARPMZ_DISPLAY_OUTPUT[emuConfig.params[emuConfig.machineModel].displayOutput]); +} + +// Method to change the Display Output, choice based on the actual selected machine. +void EMZNextDisplayOutput(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].displayOutput = (emuConfig.params[emuConfig.machineModel].displayOutput+1 >= NUMELEM(SHARPMZ_DISPLAY_OUTPUT) ? 0 : emuConfig.params[emuConfig.machineModel].displayOutput+1); + } + return; +} + +// Method to return a char string which represents the current selected VRAM Mode. +const char *EMZGetVRAMModeChoice(void) +{ + // Locals. + // + return(SHARPMZ_VRAMDISABLE_MODE[emuConfig.params[emuConfig.machineModel].vramMode]); +} + +// Method to change the VRAM Mode, choice based on the actual selected machine. +void EMZNextVRAMMode(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].vramMode = (emuConfig.params[emuConfig.machineModel].vramMode+1 >= NUMELEM(SHARPMZ_VRAMDISABLE_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].vramMode+1); + } + return; +} + +// Method to return a char string which represents the current selected GRAM Mode. +const char *EMZGetGRAMModeChoice(void) +{ + // Locals. + // + return(SHARPMZ_GRAMDISABLE_MODE[emuConfig.params[emuConfig.machineModel].gramMode]); +} + +// Method to change the GRAM Mode, choice based on the actual selected machine. +void EMZNextGRAMMode(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].gramMode = (emuConfig.params[emuConfig.machineModel].gramMode+1 >= NUMELEM(SHARPMZ_GRAMDISABLE_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].gramMode+1); + } + return; +} + +// Method to return a char string which represents the current selected VRAM CPU Wait Mode. +const char *EMZGetVRAMWaitModeChoice(void) +{ + // Locals. + // + return(SHARPMZ_VRAMWAIT_MODE[emuConfig.params[emuConfig.machineModel].vramWaitMode]); +} + +// Method to change the VRAM Wait Mode, choice based on the actual selected machine. +void EMZNextVRAMWaitMode(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].vramWaitMode = (emuConfig.params[emuConfig.machineModel].vramWaitMode+1 >= NUMELEM(SHARPMZ_VRAMWAIT_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].vramWaitMode+1); + } + return; +} + +// Method to return a char string which represents the current selected PCG Mode. +const char *EMZGetPCGModeChoice(void) +{ + // Locals. + // + return(SHARPMZ_PCG_MODE[emuConfig.params[emuConfig.machineModel].pcgMode]); +} + +// Method to change the PCG Mode, choice based on the actual selected machine. +void EMZNextPCGMode(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].pcgMode = (emuConfig.params[emuConfig.machineModel].pcgMode+1 >= NUMELEM(SHARPMZ_PCG_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].pcgMode+1); + } + return; +} + +// Method to return a char string which represents the current selected Aspect Ratio. +const char *EMZGetAspectRatioChoice(void) +{ + // Locals. + // + return(SHARPMZ_ASPECT_RATIO[emuConfig.params[emuConfig.machineModel].aspectRatio]); +} + +// Method to change the Aspect Ratio, choice based on the actual selected machine. +void EMZNextAspectRatio(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].aspectRatio = (emuConfig.params[emuConfig.machineModel].aspectRatio+1 >= NUMELEM(SHARPMZ_ASPECT_RATIO) ? 0 : emuConfig.params[emuConfig.machineModel].aspectRatio+1); + } + return; +} + +// Method to return a char string which represents the current selected Scan Doubler. +const char *EMZGetScanDoublerFXChoice(void) +{ + // Locals. + // + return(SHARPMZ_SCANDOUBLER_FX[emuConfig.params[emuConfig.machineModel].scanDoublerFX]); +} + +// Method to change the Scan Doubler FX, choice based on the actual selected machine. +void EMZNextScanDoublerFX(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].scanDoublerFX = (emuConfig.params[emuConfig.machineModel].scanDoublerFX+1 >= NUMELEM(SHARPMZ_SCANDOUBLER_FX) ? 0 : emuConfig.params[emuConfig.machineModel].scanDoublerFX+1); + } + return; +} + +// Method to return a char string which represents the current file filter. +const char *EMZGetLoadDirectFileFilterChoice(void) +{ + // Locals. + // + return(SHARPMZ_FILE_FILTERS[emuConfig.params[emuConfig.machineModel].loadDirectFilter]); +} + +// Method to change the Load Direct to RAM file filter, choice based on the actual selected machine. +void EMZNextLoadDirectFileFilter(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].loadDirectFilter = (emuConfig.params[emuConfig.machineModel].loadDirectFilter+1 >= NUMELEM(SHARPMZ_FILE_FILTERS) ? 0 : emuConfig.params[emuConfig.machineModel].loadDirectFilter+1); + } + return; +} + +// Method to return a char string which represents the current Tape Queueing file filter. +const char *EMZGetQueueTapeFileFilterChoice(void) +{ + // Locals. + // + return(SHARPMZ_FILE_FILTERS[emuConfig.params[emuConfig.machineModel].queueTapeFilter]); +} + +// Method to change the Queue Tape file filter, choice based on the actual selected machine. +void EMZNextQueueTapeFileFilter(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].queueTapeFilter = (emuConfig.params[emuConfig.machineModel].queueTapeFilter+1 >= NUMELEM(SHARPMZ_FILE_FILTERS) ? 0 : emuConfig.params[emuConfig.machineModel].queueTapeFilter+1); + } + return; +} + +// Method to return a char string which represents the current selected tape save path. +const char *EMZGetTapeSaveFilePathChoice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].tapeSavePath); +} + +// Method to return a char string which represents the current selected cmt hardware selection setting. +const char *EMZGetCMTModeChoice(void) +{ + // Locals. + // + return(SHARPMZ_TAPE_MODE[emuConfig.params[emuConfig.machineModel].cmtMode]); +} + +// Method to change the cmt hardware setting, choice based on the actual selected machine. +void EMZNextCMTMode(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].cmtMode = (emuConfig.params[emuConfig.machineModel].cmtMode+1 >= NUMELEM(SHARPMZ_TAPE_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].cmtMode+1); + } + return; +} + +// Method to select the FPGA based CMT or the hardware CMT. +// +void EMZChangeCMTMode(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + // Need to change choice then rewrite the menu as the choice will affect displayed items. + EMZNextCMTMode(mode); + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } +} + +// Method to return a char string which represents the current selected fast tape load mode. +const char *EMZGetFastTapeLoadChoice(void) +{ + // Locals. + // + + return(SHARPMZ_FAST_TAPE[emuConfig.machineGroup][emuConfig.params[emuConfig.machineModel].fastTapeLoad]); +} + +// Method to change the Fast Tape Load mode, choice based on the actual selected machine. +void EMZNextFastTapeLoad(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].fastTapeLoad = (emuConfig.params[emuConfig.machineModel].fastTapeLoad+1 >= NUMELEM(SHARPMZ_FAST_TAPE[emuConfig.machineGroup]) || SHARPMZ_FAST_TAPE[emuConfig.machineGroup][emuConfig.params[emuConfig.machineModel].fastTapeLoad+1] == NULL ? 0 : emuConfig.params[emuConfig.machineModel].fastTapeLoad+1); + } + return; +} + +// Method to return a char string which represents the current selected tape button setting. +const char *EMZGetTapeButtonsChoice(void) +{ + // Locals. + // + return(SHARPMZ_TAPE_BUTTONS[emuConfig.params[emuConfig.machineModel].tapeButtons]); +} + +// Method to change the tape button setting, choice based on the actual selected machine. +void EMZNextTapeButtons(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].tapeButtons = (emuConfig.params[emuConfig.machineModel].tapeButtons+1 >= NUMELEM(SHARPMZ_TAPE_BUTTONS) ? 0 : emuConfig.params[emuConfig.machineModel].tapeButtons+1); + } + return; +} + +// Method to return a char string which represents the current selected Sharp<->ASCII mapping for CMT operations. +const char *EMZGetCMTAsciiMappingChoice(void) +{ + // Locals. + // + return(SHARPMZ_ASCII_MAPPING[emuConfig.params[emuConfig.machineModel].cmtAsciiMapping]); +} + +// Method to change the Sharp<->ASCII mapping setting, choice based on the actual selected machine. +void EMZNextCMTAsciiMapping(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].cmtAsciiMapping = (emuConfig.params[emuConfig.machineModel].cmtAsciiMapping+1 >= NUMELEM(SHARPMZ_ASCII_MAPPING) ? 0 : emuConfig.params[emuConfig.machineModel].cmtAsciiMapping+1); + } + return; +} + +// Method to return a char string which represents the current selected floppy disk drive hardware selection setting. +const char *EMZGetFDDModeChoice(void) +{ + // Locals. + // + return(SHARPMZ_FDD_MODE[emuConfig.params[emuConfig.machineModel].fddEnabled]); +} + +// Method to change the floppy disk drive hardware setting, choice based on the actual selected machine. +void EMZNextFDDMode(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].fddEnabled = (emuConfig.params[emuConfig.machineModel].fddEnabled+1 >= NUMELEM(SHARPMZ_FDD_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].fddEnabled+1); + } + return; +} + +// Method to enable or disable the FDD hardware within the FPGA. +// +void EMZChangeFDDMode(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + // Need to change choice then rewrite the menu as the choice will affect displayed items. + EMZNextFDDMode(mode); + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } +} + + +// Method to change the type of disk the WD1793 controller reports. Also used to aportion the disk image correctly. +// +void EMZNextFDDDriveType(enum ACTIONMODE mode, uint8_t drive) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + do { + emuConfig.params[emuConfig.machineModel].fdd[drive].diskType = (emuConfig.params[emuConfig.machineModel].fdd[drive].diskType+1 >= NUMELEM(SHARPMZ_FDD_DISK_TYPE) ? 0 : emuConfig.params[emuConfig.machineModel].fdd[drive].diskType+1); + } while(SHARPMZ_FDD_DISK_TYPE[emuConfig.params[emuConfig.machineModel].fdd[drive].diskType] == NULL); + } + return; +} +void EMZNextFDDDriveType0(enum ACTIONMODE mode) +{ + EMZNextFDDDriveType(mode, 0); +} +void EMZNextFDDDriveType1(enum ACTIONMODE mode) +{ + EMZNextFDDDriveType(mode, 1); +} +void EMZNextFDDDriveType2(enum ACTIONMODE mode) +{ + EMZNextFDDDriveType(mode, 2); +} +void EMZNextFDDDriveType3(enum ACTIONMODE mode) +{ + EMZNextFDDDriveType(mode, 3); +} + +// Method to return a string to indicate the current Disk Type setting. +const char *EMZGetFDDDriveTypeChoice(uint8_t drive) +{ + // Locals. + // + return(SHARPMZ_FDD_DISK_TYPE[emuConfig.params[emuConfig.machineModel].fdd[drive].diskType]); +} +const char *EMZGetFDDDriveType0Choice(void) +{ + return(EMZGetFDDDriveTypeChoice(0)); +} +const char *EMZGetFDDDriveType1Choice(void) +{ + return(EMZGetFDDDriveTypeChoice(1)); +} +const char *EMZGetFDDDriveType2Choice(void) +{ + return(EMZGetFDDDriveTypeChoice(2)); +} +const char *EMZGetFDDDriveType3Choice(void) +{ + return(EMZGetFDDDriveTypeChoice(3)); +} + +// Method to change the image polarity. The underlying controller expects inverted data due to the original MB8866 controller IC using an inverted data bus +// but it is hard without tools to work with or create new images. Thus an option exists to use a non-standard image which has non-inverted data and this +// option inverts it prior to sending to the controller. +// +void EMZNextFDDImagePolarity(enum ACTIONMODE mode, uint8_t drive) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].fdd[drive].polarity = (emuConfig.params[emuConfig.machineModel].fdd[drive].polarity+1 >= NUMELEM(SHARPMZ_FDD_IMAGE_POLARITY) ? 0 : emuConfig.params[emuConfig.machineModel].fdd[drive].polarity+1); + } + return; +} +void EMZNextFDDImagePolarity0(enum ACTIONMODE mode) +{ + EMZNextFDDImagePolarity(mode, 0); +} +void EMZNextFDDImagePolarity1(enum ACTIONMODE mode) +{ + EMZNextFDDImagePolarity(mode, 1); +} +void EMZNextFDDImagePolarity2(enum ACTIONMODE mode) +{ + EMZNextFDDImagePolarity(mode, 2); +} +void EMZNextFDDImagePolarity3(enum ACTIONMODE mode) +{ + EMZNextFDDImagePolarity(mode, 3); +} + +// Method to return a string to indicate the current Disk Polarity setting. +const char *EMZGetFDDImagePolarityChoice(uint8_t drive) +{ + // Locals. + // + return(SHARPMZ_FDD_IMAGE_POLARITY[emuConfig.params[emuConfig.machineModel].fdd[drive].polarity]); +} +const char *EMZGetFDDImagePolarity0Choice(void) +{ + return(EMZGetFDDImagePolarityChoice(0)); +} +const char *EMZGetFDDImagePolarity1Choice(void) +{ + return(EMZGetFDDImagePolarityChoice(1)); +} +const char *EMZGetFDDImagePolarity2Choice(void) +{ + return(EMZGetFDDImagePolarityChoice(2)); +} +const char *EMZGetFDDImagePolarity3Choice(void) +{ + return(EMZGetFDDImagePolarityChoice(3)); +} + +// Nethod to change the floppy disk update mode to enable/disable writes. +// +void EMZNextFDDUpdateMode(enum ACTIONMODE mode, uint8_t drive) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].fdd[drive].updateMode = (emuConfig.params[emuConfig.machineModel].fdd[drive].updateMode+1 >= NUMELEM(SHARPMZ_FDD_UPDATE_MODE) ? 0 : emuConfig.params[emuConfig.machineModel].fdd[drive].updateMode+1); + } + return; +} +void EMZNextFDDUpdateMode0(enum ACTIONMODE mode) +{ + EMZNextFDDUpdateMode(mode, 0); +} +void EMZNextFDDUpdateMode1(enum ACTIONMODE mode) +{ + EMZNextFDDUpdateMode(mode, 1); +} +void EMZNextFDDUpdateMode2(enum ACTIONMODE mode) +{ + EMZNextFDDUpdateMode(mode, 2); +} +void EMZNextFDDUpdateMode3(enum ACTIONMODE mode) +{ + EMZNextFDDUpdateMode(mode, 3); +} + +// Method to return a string to indicate the current Disk Update Mode setting. +const char *EMZGetFDDUpdateModeChoice(uint8_t drive) +{ + // Locals. + // + return(SHARPMZ_FDD_UPDATE_MODE[emuConfig.params[emuConfig.machineModel].fdd[drive].updateMode]); +} +const char *EMZGetFDDUpdateMode0Choice(void) +{ + return(EMZGetFDDUpdateModeChoice(0)); +} +const char *EMZGetFDDUpdateMode1Choice(void) +{ + return(EMZGetFDDUpdateModeChoice(1)); +} +const char *EMZGetFDDUpdateMode2Choice(void) +{ + return(EMZGetFDDUpdateModeChoice(2)); +} +const char *EMZGetFDDUpdateMode3Choice(void) +{ + return(EMZGetFDDUpdateModeChoice(3)); +} + +// Method to select the disk image to be used for a Floppy Disk Drive. +// +void EMZFDDSetDriveImage(enum ACTIONMODE mode, uint8_t drive) +{ + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, EMZGetFDDDriveFileFilterChoice()); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + switch(drive) + { + case 1: + emuControl.fileList.returnCallback = EMZFDDDriveImage1Set; + break; + case 2: + emuControl.fileList.returnCallback = EMZFDDDriveImage2Set; + break; + case 3: + emuControl.fileList.returnCallback = EMZFDDDriveImage3Set; + break; + case 0: + default: + emuControl.fileList.returnCallback = EMZFDDDriveImage0Set; + break; + } + } +} +void EMZFDDSetDriveImage0(enum ACTIONMODE mode) +{ + EMZFDDSetDriveImage(mode, 0); +} +void EMZFDDSetDriveImage1(enum ACTIONMODE mode) +{ + EMZFDDSetDriveImage(mode, 1); +} +void EMZFDDSetDriveImage2(enum ACTIONMODE mode) +{ + EMZFDDSetDriveImage(mode, 2); +} +void EMZFDDSetDriveImage3(enum ACTIONMODE mode) +{ + EMZFDDSetDriveImage(mode, 3); +} + +// Method to store the selected file name. This method is called as a callback when the user selects a disk image file. +void EMZFDDDriveImageSet(char *param, uint8_t driveNo) +{ + // Locals. + short imgType; + + // If a filename has been provided, check it and store details. + if(strlen(param) < MAX_FILENAME_LEN) + { + // Validate file selected. + if((imgType = EMZCheckFDDImage(param)) != -1) + { + // If the image is valid, store the information and set mounted flag. + if(EMZSetFDDImageParams(param, driveNo, (enum IMAGETYPES)imgType) != -1) + { + emuConfig.params[emuConfig.machineModel].fdd[driveNo].mounted = 1; + emuConfig.params[emuConfig.machineModel].fdd[driveNo].imgType = (enum IMAGETYPES)imgType; + } + } else + { + // Raise error message here. + } + } +} +void EMZFDDDriveImage0Set(char *param) +{ + EMZFDDDriveImageSet(param, 0); +} +void EMZFDDDriveImage1Set(char *param) +{ + EMZFDDDriveImageSet(param, 1); +} +void EMZFDDDriveImage2Set(char *param) +{ + EMZFDDDriveImageSet(param, 2); +} +void EMZFDDDriveImage3Set(char *param) +{ + EMZFDDDriveImageSet(param, 3); +} + +// Method to return a char string which represents the current floppy image file filter. +const char *EMZGetFDDDriveFileFilterChoice(void) +{ + // Locals. + // + return(SHARPMZ_FDD_FILE_FILTERS[emuConfig.params[emuConfig.machineModel].fddImageFilter]); +} + +// Method to return a char string which represents the current floppy image file filter. +const char *EMZGetFDDDriveFileChoice(uint8_t drive) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].fdd[drive].fileName); +} +const char *EMZGetFDDDrive0FileChoice(void) +{ + return(EMZGetFDDDriveFileChoice(0)); +} +const char *EMZGetFDDDrive1FileChoice(void) +{ + return(EMZGetFDDDriveFileChoice(1)); +} +const char *EMZGetFDDDrive2FileChoice(void) +{ + return(EMZGetFDDDriveFileChoice(2)); +} +const char *EMZGetFDDDrive3FileChoice(void) +{ + return(EMZGetFDDDriveFileChoice(3)); +} + +// Method to change the floppy image selection filter, choice based on the actual selected machine. +void EMZNextDriveImageFilter(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].fddImageFilter = (emuConfig.params[emuConfig.machineModel].fddImageFilter+1 >= NUMELEM(SHARPMZ_FDD_FILE_FILTERS) ? 0 : emuConfig.params[emuConfig.machineModel].fddImageFilter+1); + } + return; +} + +// Method to eject/unmount the current floppy image. +void EMZMountDrive(enum ACTIONMODE mode, uint8_t drive, uint8_t mount) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + // Only mount the disk image if a file has been selected. + emuConfig.params[emuConfig.machineModel].fdd[drive].mounted = mount == 1 && strlen(emuConfig.params[emuConfig.machineModel].fdd[drive].fileName) > 0 ? 1 : 0; + } + return; +} +void EMZNextMountDrive0(enum ACTIONMODE mode) +{ + EMZMountDrive(mode, 0, emuConfig.params[emuConfig.machineModel].fdd[0].mounted == 0 ? 1 : 0); +} +void EMZNextMountDrive1(enum ACTIONMODE mode) +{ + EMZMountDrive(mode, 1, emuConfig.params[emuConfig.machineModel].fdd[1].mounted == 0 ? 1 : 0); +} +void EMZNextMountDrive2(enum ACTIONMODE mode) +{ + EMZMountDrive(mode, 2, emuConfig.params[emuConfig.machineModel].fdd[2].mounted == 0 ? 1 : 0); +} +void EMZNextMountDrive3(enum ACTIONMODE mode) +{ + EMZMountDrive(mode, 3, emuConfig.params[emuConfig.machineModel].fdd[3].mounted == 0 ? 1 : 0); +} + +const char *EMZGetFDDMountChoice(uint8_t drive) +{ + // Locals. + // + return(SHARPMZ_FDD_MOUNT[emuConfig.params[emuConfig.machineModel].fdd[drive].mounted]); +} +const char *EMZGetFDDMount0Choice(void) +{ + // Locals. + // + return(EMZGetFDDMountChoice(0)); +} +const char *EMZGetFDDMount1Choice(void) +{ + // Locals. + // + return(EMZGetFDDMountChoice(1)); +} +const char *EMZGetFDDMount2Choice(void) +{ + // Locals. + // + return(EMZGetFDDMountChoice(2)); +} +const char *EMZGetFDDMount3Choice(void) +{ + // Locals. + // + return(EMZGetFDDMountChoice(3)); +} + +// Method to return a char string which represents the current selected 40x25 Monitor ROM setting. +const char *EMZGetMonitorROM40Choice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].romMonitor40.romEnabled ? emuConfig.params[emuConfig.machineModel].romMonitor40.romFileName : "Disabled"); +} + +// Method to change the 40x25 Monitor ROM setting, disabled or selected file, choice based on the actual selected machine. +void EMZNextMonitorROM40(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].romMonitor40.romEnabled = (emuConfig.params[emuConfig.machineModel].romMonitor40.romEnabled == 1 ? 0 : 1); + } + return; +} + +// Method to return a char string which represents the current selected 80x25 Monitor ROM setting. +const char *EMZGetMonitorROM80Choice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].romMonitor80.romEnabled ? emuConfig.params[emuConfig.machineModel].romMonitor80.romFileName : "Disabled"); +} + +// Method to change the 80x25 Monitor ROM setting, disabled or selected file, choice based on the actual selected machine. +void EMZNextMonitorROM80(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].romMonitor80.romEnabled = (emuConfig.params[emuConfig.machineModel].romMonitor80.romEnabled == 1 ? 0 : 1); + } + return; +} + +// Method to return a char string which represents the current selected character generator ROM setting. +const char *EMZGetCGROMChoice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].romCG.romEnabled ? emuConfig.params[emuConfig.machineModel].romCG.romFileName : "Disabled"); +} + +// Method to change the character generator ROM setting, disabled or selected file, choice based on the actual selected machine. +void EMZNextCGROM(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].romCG.romEnabled = (emuConfig.params[emuConfig.machineModel].romCG.romEnabled == 1 ? 0 : 1); + } + return; +} + +// Method to return a char string which represents the current selected key mapping ROM setting. +const char *EMZGetKeyMappingROMChoice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].romKeyMap.romEnabled ? emuConfig.params[emuConfig.machineModel].romKeyMap.romFileName : "Disabled"); +} + +// Method to change the key mapping ROM setting, disabled or selected file, choice based on the actual selected machine. +void EMZNextKeyMappingROM(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].romKeyMap.romEnabled = (emuConfig.params[emuConfig.machineModel].romKeyMap.romEnabled == 1 ? 0 : 1); + } + return; +} + +// Method to return a char string which represents the current selected User ROM setting. +const char *EMZGetUserROMChoice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].romUser.romEnabled ? emuConfig.params[emuConfig.machineModel].romUser.romFileName : "Disabled"); +} + +// Method to change the User ROM setting, disabled or selected file, choice based on the actual selected machine. +void EMZNextUserROM(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].romUser.romEnabled = (emuConfig.params[emuConfig.machineModel].romUser.romEnabled == 1 ? 0 : 1); + } + return; +} + +// Method to return a char string which represents the current selected Floppy Disk ROM setting. +const char *EMZGetFloppyDiskROMChoice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].romFDC.romEnabled ? emuConfig.params[emuConfig.machineModel].romFDC.romFileName : "Disabled"); +} + +// Method to change the Floppy Disk ROM setting, disabled or selected file, choice based on the actual selected machine. +void EMZNextFloppyDiskROM(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].romFDC.romEnabled = (emuConfig.params[emuConfig.machineModel].romFDC.romEnabled == 1 ? 0 : 1); + } + return; +} + +// Method to return a string representation of the tape type held in the last tape accessed buffer. +const char *EMZGetTapeType(void) +{ + // Locals. + // + return(SHARPMZ_TAPE_TYPE[emuControl.tapeHeader.dataType >= NUMELEM(SHARPMZ_TAPE_TYPE) ? NUMELEM(SHARPMZ_TAPE_TYPE) - 1 : emuControl.tapeHeader.dataType]); +} + +// Method to return a char string which represents the current selected application autostart setting. +const char *EMZGetLoadApplicationChoice(void) +{ + // Locals. + // + return(emuConfig.params[emuConfig.machineModel].loadApp.appEnabled ? emuConfig.params[emuConfig.machineModel].loadApp.appFileName : "Disabled"); +} + +// Method to change the current select application autostart setting. +void EMZNextLoadApplication(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].loadApp.appEnabled = (emuConfig.params[emuConfig.machineModel].loadApp.appEnabled == 1 ? 0 : 1); + } + return; +} + +// Method to return a char string which represents the current state of the autostart feature. +const char *EMZGetAutoStartChoice(void) +{ + // Locals. + // + return(SHARPMZ_AUTOSTART[emuConfig.params[emuConfig.machineModel].autoStart]); +} + +// Method to change the start of the autostart feature, choice based on the actual selected machine. +void EMZNextAutoStart(enum ACTIONMODE mode) +{ + // Locals. + // + + if(mode == ACTION_DEFAULT || mode == ACTION_TOGGLECHOICE) + { + emuConfig.params[emuConfig.machineModel].autoStart = (emuConfig.params[emuConfig.machineModel].autoStart+1 >= NUMELEM(SHARPMZ_AUTOSTART) ? 0 : emuConfig.params[emuConfig.machineModel].autoStart+1); + } + return; +} + +// Method to select the autostart mode. +// +void EMZChangeAutoStart(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + // Need to change choice then rewrite the menu as the choice will affect displayed items. + EMZNextAutoStart(mode); + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } +} + +// Method to add a line into the displayed menu. +// +void EMZAddToMenu(uint8_t row, uint8_t active, char *text, char hotKey, enum MENUTYPES type, enum MENUSTATE state, t_menuCallback mcb, enum MENUCALLBACK cbAction, t_choiceCallback ccb, t_viewCallback vcb) +{ + // Locals. + uint32_t textLen = strlen(text); + uint32_t idx; + + // Sanity check. + if(row >= MAX_MENU_ROWS) + return; + + if(emuControl.menu.data[row] != NULL) + { + free(emuControl.menu.data[row]); + emuControl.menu.data[row] = NULL; + } + emuControl.menu.data[row] = (t_menuItem *)malloc(sizeof(t_menuItem)); + if(emuControl.menu.data[row] == NULL) + { + debugf("Failed to allocate %d bytes on heap for menu row data %d\n", sizeof(t_menuItem), row); + return; + } + idx = textLen; + if(textLen > 0) + { + // Scan the text for a hotkey, case insensitive. + for(idx=0; idx < textLen; idx++) + { + if(text[idx] == hotKey) + break; + } + strcpy(emuControl.menu.data[row]->text, text); + } + else + { + emuControl.menu.data[row]->text[0] = 0x00; + } + // Store hotkey if given and found. + if(hotKey != 0x00 && (idx < textLen || state == MENUSTATE_HIDDEN)) + { + // Store the hotkey case independent. + emuControl.menu.data[row]->hotKey = hotKey; + } else + { + emuControl.menu.data[row]->hotKey = 0x00; + } + emuControl.menu.data[row]->type = type; + emuControl.menu.data[row]->state = state; + emuControl.menu.data[row]->menuCallback = mcb; + emuControl.menu.data[row]->choiceCallback = ccb; + emuControl.menu.data[row]->viewCallback = vcb; + emuControl.menu.data[row]->cbAction = cbAction; + + if(active && state == MENUSTATE_ACTIVE) + { + emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = row; + } + return; +} + +// Method to get the boundaries of the current menu, ie. first item, last item and number of visible rows. +void EMZGetMenuBoundaries(int16_t *firstMenuRow, int16_t *lastMenuRow, int16_t *firstActiveRow, int16_t *lastActiveRow, int16_t *visibleRows) +{ + // Set defaults to indicate an invalid Menu structure. + *firstMenuRow = *lastMenuRow = *firstActiveRow = *lastActiveRow = -1; + *visibleRows = 0; + + // Npw scan the menu elements and work out first, last and number of visible rows. + for(int16_t idx=0; idx < MAX_MENU_ROWS; idx++) + { + if(emuControl.menu.data[idx] != NULL) + { + if(*firstMenuRow == -1) { *firstMenuRow = idx; } + *lastMenuRow = idx; + if(emuControl.menu.data[idx]->state != MENUSTATE_HIDDEN && emuControl.menu.data[idx]->state != MENUSTATE_INACTIVE) { *visibleRows += 1; } + if(emuControl.menu.data[idx]->state == MENUSTATE_ACTIVE && *firstActiveRow == -1) { *firstActiveRow = idx; } + if(emuControl.menu.data[idx]->state == MENUSTATE_ACTIVE) { *lastActiveRow = idx; } + } + } + return; +} + +// Method to update the framebuffer with current Menu contents and active line selection. +// The active row is used from the control structure when activeRow = -1 otherwise it is updated. +// +int16_t EMZDrawMenu(int16_t activeRow, uint8_t direction, enum MENUMODE mode) +{ + uint16_t xpad = 0; + uint16_t ypad = 1; + uint16_t rowPixelDepth = (emuControl.menu.rowFontptr->height + emuControl.menu.rowFontptr->spacing + emuControl.menu.padding + 2*ypad); + uint16_t maxCol = (uint16_t)OSDGet(ACTIVE_MAX_X); + uint16_t colPixelEnd = maxCol - emuControl.menu.colPixelsEnd; + uint16_t maxRow = ((uint16_t)OSDGet(ACTIVE_MAX_Y)/rowPixelDepth) + 1; + uint8_t textChrX = (emuControl.menu.colPixelStart / (emuControl.menu.rowFontptr->width + emuControl.menu.rowFontptr->spacing)); + char activeBuf[MENU_ROW_WIDTH]; + uint16_t attrBuf[MENU_ROW_WIDTH]; + int16_t firstMenuRow; + int16_t firstActiveMenuRow; + int16_t lastMenuRow; + int16_t lastActiveMenuRow; + int16_t visibleRows; + + // Get menu boundaries. + EMZGetMenuBoundaries(&firstMenuRow, &lastMenuRow, &firstActiveMenuRow, &lastActiveMenuRow, &visibleRows); + + // Sanity check. + if(firstMenuRow == -1 || lastMenuRow == -1 || firstActiveMenuRow == -1 || lastActiveMenuRow == -1 || visibleRows == 0) + return(activeRow); + + // Clear out old menu. + OSDClearArea(emuControl.menu.colPixelStart, emuControl.menu.rowPixelStart, colPixelEnd, ((uint16_t)OSDGet(ACTIVE_MAX_Y) - 2), emuControl.menu.inactiveBgColour); + + // Forward to the next active row if the one provided isnt active. + if(activeRow <= -1) + { + activeRow = (emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] < 0 || emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] >= MAX_MENU_ROWS ? 0 : emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx]); + } + + // Sanity check. + if(activeRow > MAX_MENU_ROWS-1) + activeRow = lastMenuRow; + + if(emuControl.menu.data[activeRow] == NULL || emuControl.menu.data[activeRow]->state != MENUSTATE_ACTIVE) + { + // Get the next or previous active menu item row. + uint16_t loopCheck = MAX_MENU_ROWS; + while((emuControl.menu.data[activeRow] == NULL || (emuControl.menu.data[activeRow] != NULL && emuControl.menu.data[activeRow]->state != MENUSTATE_ACTIVE)) && loopCheck > 0) + { + activeRow += (direction == 1 ? 1 : -1); + if(activeRow <= 0 && mode == MENU_NORMAL) activeRow = firstActiveMenuRow; + if(activeRow <= 0 && mode == MENU_WRAP) activeRow = lastActiveMenuRow; + if(activeRow >= MAX_MENU_ROWS && mode == MENU_NORMAL) activeRow = lastActiveMenuRow; + if(activeRow >= MAX_MENU_ROWS && mode == MENU_WRAP) activeRow = firstActiveMenuRow; + loopCheck--; + } + } + + // Loop through all the visible rows and output. + for(uint16_t dspRow=0, menuRow=activeRow < maxRow-1 ? 0 : activeRow - (maxRow-1); menuRow < MAX_MENU_ROWS; menuRow++) + { + // Skip inactive or hidden rows. + if(emuControl.menu.data[menuRow] == NULL) + continue; + if(emuControl.menu.data[menuRow]->state == MENUSTATE_HIDDEN || emuControl.menu.data[menuRow]->state == MENUSTATE_INACTIVE) + continue; + if(dspRow >= maxRow) + continue; + + // Skip rendering blank lines! + if(emuControl.menu.data[menuRow]->state != MENUSTATE_BLANK) + { + // Zero out attributes buffer. + memset(attrBuf, NOATTR, MENU_ROW_WIDTH*sizeof(uint16_t)); + + // For read only text, no choice or directory indicator required. + if(emuControl.menu.data[menuRow]->state == MENUSTATE_TEXT) + { + sprintf(activeBuf, " %-s", emuControl.menu.data[menuRow]->text); + } else + { + // Format the data into a single buffer for output according to type. The sprintf character limiter and left justify clash so manually cut the choice buffer to max length. + uint16_t selectionWidth = EMZGetMenuColumnWidth() - MENU_CHOICE_WIDTH - 2; + char *ptr; + sprintf(activeBuf, " %-*s", selectionWidth, emuControl.menu.data[menuRow]->text); + ptr=&activeBuf[strlen(activeBuf)]; + sprintf(ptr, "%-*s", MENU_CHOICE_WIDTH, (emuControl.menu.data[menuRow]->type & MENUTYPE_CHOICE && emuControl.menu.data[menuRow]->choiceCallback != NULL) ? emuControl.menu.data[menuRow]->choiceCallback() : ""); + sprintf(ptr+MENU_CHOICE_WIDTH, "%c", (emuControl.menu.data[menuRow]->type & MENUTYPE_SUBMENU) && !(emuControl.menu.data[menuRow]->type & MENUTYPE_ACTION) ? 0x10 : ' '); + + // Prepare any needed attributes. + for(uint16_t idx=0; idx < strlen(activeBuf); idx++) + { + // Highlight the Hot Key. + if(activeBuf[idx] == emuControl.menu.data[menuRow]->hotKey) + { + attrBuf[idx] = HILIGHT_FG_CYAN; + break; + } + } + } + + // Output the row according to type. + if(activeRow == menuRow) + { + OSDWriteString(textChrX, dspRow, 0, emuControl.menu.rowPixelStart, xpad, ypad, emuControl.menu.font, NORMAL, activeBuf, attrBuf, emuControl.menu.activeFgColour, emuControl.menu.activeBgColour); + if(activeRow != -1) + emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = activeRow; + } + else if(emuControl.menu.data[menuRow]->state == MENUSTATE_GREYED) + { + OSDWriteString(textChrX, dspRow, 0, emuControl.menu.rowPixelStart, xpad, ypad, emuControl.menu.font, NORMAL, activeBuf, attrBuf, emuControl.menu.greyedFgColour, emuControl.menu.greyedBgColour); + } + else if(emuControl.menu.data[menuRow]->state == MENUSTATE_TEXT) + { + OSDWriteString(textChrX, dspRow, 0, emuControl.menu.rowPixelStart, xpad, ypad, emuControl.menu.font, NORMAL, activeBuf, attrBuf, emuControl.menu.textFgColour, emuControl.menu.textBgColour); + } + else + { + OSDWriteString(textChrX, dspRow, 0, emuControl.menu.rowPixelStart, xpad, ypad, emuControl.menu.font, NORMAL, activeBuf, attrBuf, emuControl.menu.inactiveFgColour, emuControl.menu.inactiveBgColour); + } + // Once the menu entry has been rendered, render view data if callback given. + if(emuControl.menu.data[menuRow]->viewCallback != NULL) + { + emuControl.menu.data[menuRow]->viewCallback(); + } + } + dspRow++; + } + + // If this is a submenu, place a back arrow to indicate you can go back. + if(emuControl.activeMenu.menuIdx != 0) + { + OSDWriteString(textChrX+1, 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "\x1b back", NULL, CYAN, BLACK); + } + // Place scroll arrows if we span a page. + if(activeRow >= maxRow && visibleRows > maxRow) + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 71), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "scroll \x17", NULL, CYAN, BLACK); + } else + if(activeRow >= maxRow) + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 71), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "scroll \x18 ", NULL, CYAN, BLACK); + } + else if(visibleRows > maxRow) + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 71), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "scroll \x19", NULL, CYAN, BLACK); + } else + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 71), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, " ", NULL, CYAN, BLACK); + } + + return(activeRow); +} + +// Method to free up menu heap allocated memory. +// +void EMZReleaseMenuMemory(void) +{ + // Loop through menu, free previous memory and initialise pointers. + // + for(uint16_t idx=0; idx < MAX_MENU_ROWS; idx++) + { + if(emuControl.menu.data[idx] != NULL) + { + free(emuControl.menu.data[idx]); + emuControl.menu.data[idx] = NULL; + } + } +} + +// Method to setup the intial menu screen and prepare for menu creation. +// +void EMZSetupMenu(char *sideTitle, char *menuTitle, enum FONTS font) +{ + // Locals. + // + fontStruct *fontptr = OSDGetFont(font); + uint16_t fontWidth = fontptr->width+fontptr->spacing; + uint16_t menuStartX = (((((uint16_t)OSDGet(ACTIVE_MAX_X) / fontWidth) - (30/fontWidth)) / 2) - strlen(menuTitle)/2) + 2; + uint16_t menuTitleLineLeft = (menuStartX * fontWidth) - 3; + uint16_t menuTitleLineRight = ((menuStartX+strlen(menuTitle))*fontWidth) + 1; + + // Release previous memory as creation of a new menu will reallocate according to requirements. + EMZReleaseMenuMemory(); + + // Prepare screen background. + OSDClearScreen(WHITE); + OSDClearArea(30, -1, -1, -1, BLACK); + + // Side and Top menu titles. + OSDWriteString(0, 0, 2, 8, 0, 0, FONT_9X16, DEG270, sideTitle, NULL, BLACK, WHITE); + OSDWriteString(menuStartX, 0, 0, 0, 0, 0, font, NORMAL, menuTitle, NULL, WHITE, BLACK); + + // Top line indenting menu title. + OSDDrawLine( 0, 0, menuTitleLineLeft, 0, WHITE); + OSDDrawLine( menuTitleLineLeft, 0, menuTitleLineLeft, fontWidth, WHITE); + OSDDrawLine( menuTitleLineLeft, fontWidth, menuTitleLineRight, fontWidth, WHITE); + OSDDrawLine( menuTitleLineRight, 0, menuTitleLineRight, fontWidth, WHITE); + OSDDrawLine( menuTitleLineRight, 0, -1, 0, WHITE); + + // Right and bottom lines to complete menu outline. + OSDDrawLine( 0, -1, -1, -1, WHITE); + OSDDrawLine(-1, 0, -1, -1, WHITE); + +// emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + return; +} + + +// Method to setup the OSD for output of a list of paths or files ready for selection. +void EMZSetupDirList(char *sideTitle, char *menuTitle, enum FONTS font) +{ + // Locals. + // + fontStruct *fontptr = OSDGetFont(font); + uint16_t fontWidth = fontptr->width+fontptr->spacing; + uint16_t menuStartX = (((((uint16_t)OSDGet(ACTIVE_MAX_X) / fontWidth) - (30/fontWidth)) / 2) - strlen(menuTitle)/2) + 1; + uint16_t menuTitleWidth = ((uint16_t)OSDGet(ACTIVE_MAX_X) / fontWidth) - (30/fontWidth); + uint16_t menuTitleLineLeft = (menuStartX * fontWidth) - 5; + uint16_t menuTitleLineRight = ((menuStartX+strlen(menuTitle))*fontWidth) + 3; + + // Prepare screen background. + OSDClearScreen(WHITE); + OSDClearArea(30, -1, -1, -1, BLACK); + + // Side and Top menu titles. + OSDWriteString(0, 0, 8, 8, 0, 0, FONT_9X16, DEG270, sideTitle, NULL, BLUE, WHITE); + // Adjust the title start location when it is larger than the display area. + OSDWriteString(menuStartX, 0, 0, 0, 0, 0, font, NORMAL, (strlen(menuTitle) >= menuTitleWidth - 2) ? &menuTitle[menuTitleWidth - strlen(menuTitle) - 2] : menuTitle, NULL, WHITE, BLACK); + + // Top line indenting menu title. + OSDDrawLine( 0, 0, menuTitleLineLeft, 0, WHITE); + OSDDrawLine( menuTitleLineLeft, 0, menuTitleLineLeft, fontWidth, WHITE); + OSDDrawLine( menuTitleLineLeft, fontWidth, menuTitleLineRight, fontWidth, WHITE); + OSDDrawLine( menuTitleLineRight, 0, menuTitleLineRight, fontWidth, WHITE); + OSDDrawLine( menuTitleLineRight, 0, -1, 0, WHITE); + + // Right and bottom lines to complete menu outline. + OSDDrawLine( 0, -1, -1, -1, WHITE); + OSDDrawLine(-1, 0, -1, -1, WHITE); + return; +} + +// Method to process a key event which is intended for the on-screen menu. +// +void EMZProcessMenuKey(uint8_t data, uint8_t ctrl) +{ + // Locals. + uint16_t menuRow = MAX_MENU_ROWS; + int16_t activeRow = emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx]; + + // Does the key match a hotkey? If it does, modify active row and action. + for(menuRow=0; menuRow < MAX_MENU_ROWS; menuRow++) + { + // Skip inactive rows. + if(emuControl.menu.data[menuRow] == NULL) + continue; + if(emuControl.menu.data[menuRow]->state != MENUSTATE_ACTIVE && emuControl.menu.data[menuRow]->state != MENUSTATE_HIDDEN) + continue; + // Key match? + if(toupper(emuControl.menu.data[menuRow]->hotKey) == toupper(data)) + break; + } + + // If key matched with a hotkey then modify action according to row type. + if(menuRow != MAX_MENU_ROWS) + { + // If the row isnt hidden, update the active row to the matched hotkey row. + if(emuControl.menu.data[menuRow]->state != MENUSTATE_HIDDEN) + emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = menuRow; + + // Set the active row to the menu row, the active row is to invoke the callback functions. + activeRow = menuRow; + + // Update the action. + if(emuControl.menu.data[menuRow]->type & MENUTYPE_ACTION) + data = 0x0D; + else if(emuControl.menu.data[menuRow]->type & MENUTYPE_CHOICE) + data = ' '; + else if(emuControl.menu.data[menuRow]->type & MENUTYPE_SUBMENU) + data = 0xA3; + } + + // Process the data received. + switch(data) + { + // Up key. + case 0xA0: + if(emuControl.menu.data[emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx]] != NULL) + { + emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = EMZDrawMenu(--emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx], 0, MENU_WRAP); + OSDRefreshScreen(); + } + break; + + // Down key. + case 0xA1: + if(emuControl.menu.data[emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx]] != NULL) + { + emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = EMZDrawMenu(++emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx], 1, MENU_WRAP); + OSDRefreshScreen(); + } + break; + + // Left key. + case 0xA4: + if(emuControl.activeMenu.menuIdx != 0) + { + emuControl.activeMenu.menuIdx = emuControl.activeMenu.menuIdx-1; + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + break; + + // Toggle choice + case ' ': + if(emuControl.menu.data[activeRow] != NULL && emuControl.menu.data[activeRow]->type & MENUTYPE_CHOICE && emuControl.menu.data[activeRow]->menuCallback != NULL) + { + emuControl.menu.data[activeRow]->menuCallback(ACTION_TOGGLECHOICE); + if(emuControl.menu.data[activeRow]->cbAction == MENUCB_REFRESH) + { + EMZDrawMenu(emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx], 0, MENU_WRAP); + OSDRefreshScreen(); + } + } + break; + + // Carriage Return - action or select sub menu. + case 0x0D: + case 0xA3: + // Sub Menu select. + if(emuControl.menu.data[activeRow] != NULL && emuControl.menu.data[activeRow]->type & MENUTYPE_SUBMENU && emuControl.menu.data[activeRow]->menuCallback != NULL) + { + emuControl.activeMenu.menuIdx = emuControl.activeMenu.menuIdx >= MAX_MENU_DEPTH - 1 ? MAX_MENU_DEPTH - 1 : emuControl.activeMenu.menuIdx+1; + emuControl.menu.data[emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx-1]]->menuCallback(ACTION_SELECT); + } else + // Action select. + if(data == 0x0D && emuControl.menu.data[activeRow] != NULL) + { + if(emuControl.menu.data[activeRow]->menuCallback != NULL) + emuControl.menu.data[activeRow]->menuCallback(ACTION_SELECT); + if(emuControl.menu.data[activeRow]->cbAction == MENUCB_REFRESH) + { + EMZDrawMenu(emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx], 0, MENU_WRAP); + OSDRefreshScreen(); + } + } + break; + + default: + printf("%02x",data); + break; + } + return; +} + +// Method to release all memory used by the on screen file/path selection screen. +// +void EMZReleaseDirMemory(void) +{ + // Free up memory used by the directory name buffer from previous use prior to allocating new buffers. + // + for(uint16_t idx=0; idx < NUMELEM(emuControl.fileList.dirEntries); idx++) + { + if(emuControl.fileList.dirEntries[idx].name != NULL) + { + free(emuControl.fileList.dirEntries[idx].name); + emuControl.fileList.dirEntries[idx].name = NULL; + } + } +} + +// Method to cache a directory contents with suitable filters in order to present a perusable list to the user via the OSD. +// +uint8_t EMZReadDirectory(const char *path, const char *filter) +{ + // Locals. + uint16_t dirCnt = 0; + uint8_t result = 0; + static DIR dirFp; + static FILINFO fno; + + // Release any previous memory. + // + EMZReleaseDirMemory(); + + // Read and process the directory contents. + result = f_opendir(&dirFp, (char *)path); + if(result == FR_OK) + { + while(dirCnt < MAX_DIRENTRY) + { + result = f_readdir(&dirFp, &fno); + if(result != FR_OK || fno.fname[0] == 0) break; + + // Filter out none-files. + if(strlen(fno.fname) == 0) + continue; + + // Filter out all files if we are just selecting directories. + if(!(fno.fattrib & AM_DIR) && strcmp(fno.fname, ".") == 0) + continue; + + // Filter out files not relevant to the caller based on the provided filter. + const char *ext = strrchr(fno.fname, '.'); + const char *filterExt = strrchr(filter, '.'); +printf("ext=%s, filterExt=%s, filter=%s, file=%s\n", ext, filterExt, filter, fno.fname); +//FIX ME: Doesnt process wildcard filename +// ext=.dsk, filterExt=.*, filter=*.*, file=z80bcpm.dsk + // Is a File Is not a Wildcard Filter doesnt match the extension + if(!(fno.fattrib & AM_DIR) && !(filterExt != NULL && strcmp(filterExt, "\.\*") == 0) && (ext == NULL || strcasecmp(++ext, (filterExt == NULL ? filter : ++filterExt)) != 0)) + continue; + + // Filter out hidden directories. + if((fno.fattrib & AM_DIR) && fno.fname[0] == '.') + continue; + + // Allocate memory for the filename and fill in the required information. + if((emuControl.fileList.dirEntries[dirCnt].name = (char *)malloc(strlen(fno.fname)+1)) == NULL) + { + printf("Memory exhausted, aborting!\n"); + return(1); + } + strcpy(emuControl.fileList.dirEntries[dirCnt].name, fno.fname); + emuControl.fileList.dirEntries[dirCnt].isDir = fno.fattrib & AM_DIR ? 1 : 0; + dirCnt++; + } + + // Pre sort the list so it is alphabetic to speed up short key indexing. + // + uint16_t idx, idx2, idx3; + for(idx=0; idx < MAX_DIRENTRY; idx++) + { + if(emuControl.fileList.dirEntries[idx].name == NULL) + continue; + + for(idx2=0; idx2 < MAX_DIRENTRY; idx2++) + { + if(emuControl.fileList.dirEntries[idx2].name == NULL) + continue; + + // Locate the next slot just in case the list is fragmented. + for(idx3=idx2+1; idx3 < MAX_DIRENTRY && emuControl.fileList.dirEntries[idx3].name == NULL; idx3++); + if(idx3 == MAX_DIRENTRY) + break; + + // Compare the 2 closest strings and swap if needed. Priority to directories as they need to appear at the top of the list. + if( (!emuControl.fileList.dirEntries[idx2].isDir && emuControl.fileList.dirEntries[idx3].isDir) || + (((emuControl.fileList.dirEntries[idx2].isDir && emuControl.fileList.dirEntries[idx3].isDir) || (!emuControl.fileList.dirEntries[idx2].isDir && !emuControl.fileList.dirEntries[idx3].isDir)) && strcasecmp(emuControl.fileList.dirEntries[idx2].name, emuControl.fileList.dirEntries[idx3].name) > 0) ) + { + t_dirEntry temp = emuControl.fileList.dirEntries[idx2]; + emuControl.fileList.dirEntries[idx2].name = emuControl.fileList.dirEntries[idx3].name; + emuControl.fileList.dirEntries[idx2].isDir = emuControl.fileList.dirEntries[idx3].isDir; + emuControl.fileList.dirEntries[idx3].name = temp.name; + emuControl.fileList.dirEntries[idx3].isDir = temp.isDir; + } + } + } + } + if(dirCnt == 0 && result != FR_OK) + f_closedir(&dirFp); + return(result); +} + +// Method to get the boundaries of the displayed file list screen, ie. first item, last item and number of visible rows. +void EMZGetFileListBoundaries(int16_t *firstFileListRow, int16_t *lastFileListRow, int16_t *visibleRows) +{ + // Set defaults to indicate an invalid Menu structure. + *firstFileListRow = *lastFileListRow = -1; + *visibleRows = 0; + + // Npw scan the file list elements and work out first, last and number of visible rows. + for(uint16_t idx=0; idx < MAX_DIRENTRY; idx++) + { + if(emuControl.fileList.dirEntries[idx].name != NULL && *firstFileListRow == -1) { *firstFileListRow = idx; } + if(emuControl.fileList.dirEntries[idx].name != NULL) { *lastFileListRow = idx; } + if(emuControl.fileList.dirEntries[idx].name != NULL) { *visibleRows += 1; } + } + return; +} + +// Method to get the maximum number of columns available for a file row with the current selected font. +// +uint16_t EMZGetFileListColumnWidth(void) +{ + uint16_t maxPixels = OSDGet(ACTIVE_MAX_X); + return( (maxPixels - emuControl.fileList.colPixelStart - emuControl.fileList.colPixelsEnd) / (emuControl.fileList.rowFontptr->width + emuControl.fileList.rowFontptr->spacing) ); +} + + +// Method to take a cached list of directory entries and present them to the user via the OSD. The current row is highlighted and allows for scrolling up or down within the list. +// +int16_t EMZDrawFileList(int16_t activeRow, uint8_t direction) +{ + uint8_t xpad = 0; + uint8_t ypad = 1; + uint16_t rowPixelDepth = (emuControl.fileList.rowFontptr->height + emuControl.fileList.rowFontptr->spacing + emuControl.fileList.padding + 2*ypad); + uint16_t maxCol = (uint16_t)OSDGet(ACTIVE_MAX_X); + uint16_t colPixelEnd = maxCol - emuControl.fileList.colPixelsEnd; + uint16_t maxRow = ((uint16_t)OSDGet(ACTIVE_MAX_Y)/rowPixelDepth) + 1; + uint8_t textChrX = (emuControl.fileList.colPixelStart / (emuControl.fileList.rowFontptr->width + emuControl.fileList.rowFontptr->spacing)); + char activeBuf[MENU_ROW_WIDTH]; + int16_t firstFileListRow; + int16_t lastFileListRow; + int16_t visibleRows; + + // Get file list boundaries. + EMZGetFileListBoundaries(&firstFileListRow, &lastFileListRow, &visibleRows); + + // Clear out old file list. + OSDClearArea(emuControl.fileList.colPixelStart, emuControl.fileList.rowPixelStart, colPixelEnd, ((uint16_t)OSDGet(ACTIVE_MAX_Y) - 2), emuControl.fileList.inactiveBgColour); + + // If this is a sub directory, place a back arrow to indicate you can go back. + if(emuControl.activeDir.dirIdx != 0) + { + OSDWriteString(textChrX, 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "\x1b back", NULL, CYAN, BLACK); + } + // Place scroll arrows if we span a page. + if(activeRow >= maxRow && visibleRows > maxRow) + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 70), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "scroll \x17", NULL, CYAN, BLACK); + } else + if(activeRow >= maxRow) + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 70), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "scroll \x18 ",NULL, CYAN, BLACK); + } + else if(visibleRows > maxRow) + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 70), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, "scroll \x19", NULL, CYAN, BLACK); + } else + { + OSDWriteString(textChrX+(maxCol < 512 ? 38 : 70), 0, 0, 4, 0, 0, FONT_5X7, NORMAL, " ", NULL, CYAN, BLACK); + } + + // Sanity check, no files or parameters out of range just exit as though the directory is empty. + if(firstFileListRow == -1 || lastFileListRow == -1 || visibleRows == 0) + return(activeRow); + + // Forward to the next active row if the one provided isnt active. + if(activeRow <= -1) + { + activeRow = (emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] < 0 || emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] >= MAX_DIRENTRY ? 0 : emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx]); + } + // Sanity check. + if(activeRow > MAX_DIRENTRY-1) + activeRow = lastFileListRow; + if(emuControl.fileList.dirEntries[activeRow].name == NULL) + { + // Get the next or previous active file list row. + uint16_t loopCheck = MAX_DIRENTRY; + while(emuControl.fileList.dirEntries[activeRow].name == NULL && loopCheck > 0) + { + activeRow += (direction == 1 ? 1 : -1); + if(activeRow < 0) activeRow = 0; + if(activeRow >= MAX_DIRENTRY) activeRow = MAX_DIRENTRY-1; + loopCheck--; + } + if(activeRow == 0 || activeRow == MAX_DIRENTRY-1) activeRow = firstFileListRow; + if(activeRow == 0 || activeRow == MAX_DIRENTRY-1) activeRow = lastFileListRow; + } + + // Loop through all the visible rows and output. + for(uint16_t dspRow=0, fileRow=activeRow < maxRow-1 ? 0 : activeRow - (maxRow-1); fileRow < MAX_DIRENTRY; fileRow++) + { + // Skip inactive or hidden rows. + if(emuControl.fileList.dirEntries[fileRow].name == NULL) + continue; + if(dspRow >= maxRow) + continue; + + // Format the data into a single buffer for output. + uint16_t selectionWidth = EMZGetFileListColumnWidth() - 9; + uint16_t nameStart = strlen(emuControl.fileList.dirEntries[fileRow].name) > selectionWidth ? strlen(emuControl.fileList.dirEntries[fileRow].name) - selectionWidth : 0; + sprintf(activeBuf, " %-*s%-7s ", selectionWidth, &emuControl.fileList.dirEntries[fileRow].name[nameStart], (emuControl.fileList.dirEntries[fileRow].isDir == 1) ? "

\x10" : ""); + + // Finall output the row according to selection. + if(activeRow == fileRow) + { + OSDWriteString(textChrX, dspRow, 0, emuControl.fileList.rowPixelStart, xpad, ypad, emuControl.fileList.font, NORMAL, activeBuf, NULL, emuControl.fileList.activeFgColour, emuControl.fileList.activeBgColour); + if(activeRow != -1) + emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] = activeRow; + } + else + { + OSDWriteString(textChrX, dspRow, 0, emuControl.fileList.rowPixelStart, xpad, ypad, emuControl.fileList.font, NORMAL, activeBuf, NULL, emuControl.fileList.inactiveFgColour, emuControl.fileList.inactiveBgColour); + } + dspRow++; + } + + return(activeRow); +} + +void EMZGetFile(void) +{ + +} + +// Method to process a key event according to current state. If displaying a list of files for selection then this method is called to allow user interaction with the file +// list and ultimate selection. +// +void EMZProcessFileListKey(uint8_t data, uint8_t ctrl) +{ + // Locals. + // + char tmpbuf[MAX_FILENAME_LEN+1]; + uint16_t rowPixelDepth = (emuControl.fileList.rowFontptr->height + emuControl.fileList.rowFontptr->spacing + emuControl.fileList.padding + 2); + uint16_t maxRow = ((uint16_t)OSDGet(ACTIVE_MAX_Y)/rowPixelDepth) + 1; + + // data = ascii code/ctrl code for key pressed. + // ctrl = KEY_BREAK & KEY_CTRL & KEY_SHIFT & "000" & KEY_DOWN & KEY_UP + + // If the break key is pressed, this is equivalent to escape so exit file list selection. + if(ctrl & KEY_BREAK_BIT) + { + // Just switch back to active menu dont activate the callback for storing a selection. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } else + { + // Process according to pressed key. + // + switch(data) + { + // Short keys to index into the file list. + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + for(int16_t idx=0; idx < MAX_DIRENTRY; idx++) + { + if(emuControl.fileList.dirEntries[idx].name == NULL) + continue; + + // If the key pressed is the first letter of a filename then jump to it. + if((!emuControl.fileList.dirEntries[idx].isDir && emuControl.fileList.dirEntries[idx].name[0] == tolower(data)) || emuControl.fileList.dirEntries[idx].name[0] == toupper(data)) + { + emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] = idx; + EMZDrawFileList(emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx], 0); + OSDRefreshScreen(); + break; + } + } + break; + + // Up key. + case 0xA0: + // Shift UP pressed? + if(ctrl & KEY_SHIFT_BIT) + { + emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] = emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] - maxRow -1 > 0 ? emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] - maxRow -1 : 0; + } + emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] = EMZDrawFileList(--emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx], 0); + OSDRefreshScreen(); + break; + + // Down key. + case 0xA1: + // Shift Down pressed? + if(ctrl & KEY_SHIFT_BIT) + { + emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] = emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] + maxRow -1 > 0 ? emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] + maxRow -1 : MAX_DIRENTRY-1; + } + emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx] = EMZDrawFileList(++emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx], 1); + OSDRefreshScreen(); + break; + + // Left key. + case 0xA4: + if(emuControl.activeDir.dirIdx != 0) + { + emuControl.activeDir.dirIdx = emuControl.activeDir.dirIdx-1; + + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZDrawFileList(0, 1); + OSDRefreshScreen(); + } + break; + + // Carriage Return - action or select sub directory. + case 0x0D: + case 0xA3: // Right Key + if(emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx]].name != NULL) + { + // If selection is chosen by CR on a path, execute the return callback to process the path and return control to the menu system. + // + if(data == 0x0D && emuControl.fileList.selectDir && emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx]].isDir && emuControl.fileList.returnCallback != NULL) + { + sprintf(tmpbuf, "%s\%s", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx]].name); + emuControl.fileList.returnCallback(tmpbuf); + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + // If selection is on a directory, increase the menu depth, read the directory and refresh the file list. + // + else if(emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx]].isDir && emuControl.activeDir.dirIdx+1 < MAX_DIR_DEPTH) + { + emuControl.activeDir.dirIdx++; + if(emuControl.activeDir.dir[emuControl.activeDir.dirIdx] != NULL) + { + free(emuControl.activeDir.dir[emuControl.activeDir.dirIdx]); + emuControl.activeDir.dir[emuControl.activeDir.dirIdx] = NULL; + } + if(emuControl.activeDir.dirIdx == 1) + { + sprintf(tmpbuf, "0:\\%s", emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx-1]].name); + } + else + { + sprintf(tmpbuf, "%s\\%s", emuControl.activeDir.dir[emuControl.activeDir.dirIdx-1], emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx-1]].name); + } + if((emuControl.activeDir.dir[emuControl.activeDir.dirIdx] = (char *)malloc(strlen(tmpbuf)+1)) == NULL) + { + printf("Exhausted memory allocating file directory name buffer\n"); + return; + } + strcpy(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], tmpbuf); + // Bring up the OSD. + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + + // Check the directory, if it is valid and has contents then update the OSD otherwise wind back. + if(EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter) == 0) + { + EMZDrawFileList(0, 1); + OSDRefreshScreen(); + } else + { + free(emuControl.activeDir.dir[emuControl.activeDir.dirIdx]); + emuControl.activeDir.dir[emuControl.activeDir.dirIdx] = NULL; + emuControl.activeDir.dirIdx--; + } + } + // If the selection is on a file, execute the return callback to process the file and return control to the menu system. + else if(emuControl.fileList.returnCallback != NULL && !emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx]].isDir) + { + sprintf(tmpbuf, "%s\\%s", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.dirEntries[emuControl.activeDir.activeRow[emuControl.activeDir.dirIdx]].name); + emuControl.fileList.returnCallback(tmpbuf); + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + } + break; + + default: + printf("%02x",data); + break; + } + } + return; +} + +// Method to redraw/refresh the menu screen. +void EMZRefreshMenu(void) +{ + EMZDrawMenu(emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx], 0, MENU_WRAP); + OSDRefreshScreen(); +} + +// Method to redraw/refresh the file list. +void EMZRefreshFileList(void) +{ + EMZDrawFileList(emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx], 0); + OSDRefreshScreen(); +} + +// Initial method called to select a file which will be loaded directly into the emulation RAM. +// +void EMZLoadDirectToRAM(enum ACTIONMODE mode) +{ + // Locals. + // + + // Toggle choice? + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextLoadDirectFileFilter(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, EMZGetLoadDirectFileFilterChoice()); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + for(uint16_t idx=0; idx < MAX_DIRENTRY; idx++) + { + if(emuControl.fileList.dirEntries[idx].name != NULL) + { + printf("%-40s%s\n", emuControl.fileList.dirEntries[idx].name, emuControl.fileList.dirEntries[idx].isDir == 1 ? "" : ""); + } + } + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZLoadDirectToRAMSet; + } + return; +} + +// Method to print out the details of the last processed tape. +// +void EMZPrintTapeDetails(short errCode) +{ + // Locals. + // + uint8_t textChrX = (emuControl.menu.colPixelStart / (emuControl.menu.rowFontptr->width + emuControl.menu.rowFontptr->spacing)); + char strBuf[MENU_ROW_WIDTH]; + + // Use the menu framework to draw the borders and title but write the details directly. + // + if(errCode) + EMZSetupMenu(EMZGetMachineTitle(), "Tape Error", FONT_7X8); + else + EMZSetupMenu(EMZGetMachineTitle(), "Tape Details", FONT_7X8); + + sprintf(strBuf, "File Size: %04x", emuControl.tapeHeader.fileSize); + OSDWriteString(18, 4, 0, 2, 0, 0, FONT_7X8, NORMAL, strBuf, NULL, WHITE, BLACK); + sprintf(strBuf, "File Type: %s", EMZGetTapeType()); + OSDWriteString(18, 5, 0, 2, 0, 0, FONT_7X8, NORMAL, strBuf, NULL, WHITE, BLACK); + sprintf(strBuf, "File Name: %s", emuControl.tapeHeader.fileName); + OSDWriteString(18, 6, 0, 2, 0, 0, FONT_7X8, NORMAL, strBuf, NULL, WHITE, BLACK); + sprintf(strBuf, "Load Addr: %04x", emuControl.tapeHeader.loadAddress); + OSDWriteString(18, 7, 0, 2, 0, 0, FONT_7X8, NORMAL, strBuf, NULL, WHITE, BLACK); + sprintf(strBuf, "Exec Addr: %04x", emuControl.tapeHeader.execAddress); + OSDWriteString(18, 8, 0, 2, 0, 0, FONT_7X8, NORMAL, strBuf, NULL, WHITE, BLACK); + + if(errCode > 0 && errCode < 0x20) + { + sprintf(strBuf, "FAT FileSystem error code: %02x", errCode); + } + else if(errCode == 0x20) + { + sprintf(strBuf, "File header contains insufficient bytes."); + } + else if(errCode == 0x21) + { + sprintf(strBuf, "Tape Data Type is invalid: %02x", emuControl.tapeHeader.dataType); + } + else if(errCode == 0x22) + { + sprintf(strBuf, "Tape is not machine code, cannot load to RAM directly."); + } + else if(errCode == 0x23 || errCode == 0x24) + { + sprintf(strBuf, "File read error. directly."); + } + else + { + sprintf(strBuf, "Unknown error (%02x) processing tape file.", errCode); + } + if(errCode > 0) + { + OSDWriteString(((VC_MENU_MAX_X_PIXELS/7) - 4 - strlen(strBuf))/2, 12, 0, 2, 0, 0, FONT_7X8, NORMAL, strBuf, NULL, RED, BLACK); + } + EMZRefreshMenu(); + + return; +} + +// Secondary method called after a file has been chosen. +// +void EMZLoadDirectToRAMSet(char *fileName) +{ + // Locals. + // + short errCode; + + // Simply call tape load with the RAM option to complete request. + errCode = EMZLoadTapeToRAM(fileName, 0); + + // Print out the tape details. + EMZPrintTapeDetails(errCode); + + // Slight delay to give user chance to see the details. + delay(8000); + + return; +} + +// Method to push a tape filename onto the queue. +// +void EMZTapeQueuePushFile(char *fileName) +{ + // Locals. + char *ptr = (char *)malloc(strlen(fileName)+1); + + if(emuControl.tapeQueue.elements > MAX_TAPE_QUEUE) + { + free(ptr); + } else + { + // Copy filename into queue. + strcpy(ptr, fileName); + emuControl.tapeQueue.queue[emuControl.tapeQueue.elements] = ptr; + emuControl.tapeQueue.elements++; + } + + return; +} + +// Method to read the oldest tape filename entered and return it. +// +char *EMZTapeQueuePopFile(uint8_t popFile) +{ + // Pop the bottom most item and shift queue down. + // + emuControl.tapeQueue.fileName[0] = 0; + if(emuControl.tapeQueue.elements > 0) + { + strcpy(emuControl.tapeQueue.fileName, emuControl.tapeQueue.queue[0]); + + // Pop file off queue? + if(popFile) + { + free(emuControl.tapeQueue.queue[0]); + emuControl.tapeQueue.elements--; + for(int i= 1; i < MAX_TAPE_QUEUE; i++) + { + emuControl.tapeQueue.queue[i-1] = emuControl.tapeQueue.queue[i]; + } + emuControl.tapeQueue.queue[MAX_TAPE_QUEUE-1] = NULL; + } + } + + // Return filename. + return(emuControl.tapeQueue.fileName[0] == 0 ? 0 : emuControl.tapeQueue.fileName); +} + +// Method to virtualise a tape and shift the position up and down the queue according to actions given. +// direction: 0 = rotate left (Rew), 1 = rotate right (Fwd) +// update: 0 = dont update tape position, 1 = update tape position +// +char *EMZTapeQueueAPSSSearch(char direction, uint8_t update) +{ + emuControl.tapeQueue.fileName[0] = 0; + if(emuControl.tapeQueue.elements > 0) + { + if(direction == 0) + { +printf("tapePos REW enter:%d,Max:%d\n", emuControl.tapeQueue.tapePos, emuControl.tapeQueue.elements); + // Position is ahead of last, then shift down and return file. + // + if(emuControl.tapeQueue.tapePos > 0) + { + strcpy(emuControl.tapeQueue.fileName, emuControl.tapeQueue.queue[emuControl.tapeQueue.tapePos-1]); + if(update) emuControl.tapeQueue.tapePos--; +printf("tapePos REW exit:%d,Max:%d\n", emuControl.tapeQueue.tapePos, emuControl.tapeQueue.elements); + } + + } else + { +printf("tapePos FFWD enter:%d,Max:%d\n", emuControl.tapeQueue.tapePos, emuControl.tapeQueue.elements); + // Position is below max, then return current and forward. + // + if(emuControl.tapeQueue.tapePos < MAX_TAPE_QUEUE && emuControl.tapeQueue.tapePos < emuControl.tapeQueue.elements) + { + strcpy(emuControl.tapeQueue.fileName, emuControl.tapeQueue.queue[emuControl.tapeQueue.tapePos]); + if(update) emuControl.tapeQueue.tapePos++; +printf("tapePos FFWD exit:%d,Max:%d\n", emuControl.tapeQueue.tapePos, emuControl.tapeQueue.elements); + } + } + } + + // Return filename. + return(emuControl.tapeQueue.fileName[0] == 0 ? 0 : emuControl.tapeQueue.fileName); +} + + +// Method to iterate through the list of filenames. +// +char *EMZNextTapeQueueFilename(char reset) +{ + static unsigned short pos = 0; + + // Reset is active, start at beginning of list. + // + if(reset) + { + pos = 0; + } + emuControl.tapeQueue.fileName[0] = 0x00; + + // If we reach the queue limit or the max elements stored, cycle the pointer + // round to the beginning. + // + if(pos >= MAX_TAPE_QUEUE || pos >= emuControl.tapeQueue.elements) + { + pos = 0; + } else + + // Get the next element in the queue, if available. + // + if(emuControl.tapeQueue.elements > 0) + { + if(pos < MAX_TAPE_QUEUE && pos < emuControl.tapeQueue.elements) + { + strcpy(emuControl.tapeQueue.fileName, emuControl.tapeQueue.queue[pos++]); + } + } + // Return filename if available. + // + return(emuControl.tapeQueue.fileName[0] == 0x00 ? NULL : &emuControl.tapeQueue.fileName); +} + +// Method to clear the queued tape list. +// +uint16_t EMZClearTapeQueue(void) +{ + // Locals. + uint16_t entries = emuControl.tapeQueue.elements; + + // Clear the queue through iteration, freeing allocated memory per entry. + if(emuControl.tapeQueue.elements > 0) + { + for(int i=0; i < MAX_TAPE_QUEUE; i++) + { + if(emuControl.tapeQueue.queue[i] != NULL) + { + free(emuControl.tapeQueue.queue[i]); + } + emuControl.tapeQueue.queue[i] = NULL; + } + } + // Reset control variables. + emuControl.tapeQueue.elements = 0; + emuControl.tapeQueue.tapePos = 0; + emuControl.tapeQueue.fileName[0] = 0; + + // Finally, return number of entries in the queue which have been cleared out. + return(entries); +} + +// Method to select a tape file which is added to the tape queue. The tape queue is a virtual tape where +// each file is presented to the CMT hardware sequentially or in a loop for the more advanced APSS decks +// of the MZ80B/2000. +void EMZQueueTape(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextQueueTapeFileFilter(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, EMZGetQueueTapeFileFilterChoice()); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZQueueTapeSet; + } +} + +// Method to store the selected file into the tape queue. +void EMZQueueTapeSet(char *param) +{ + EMZTapeQueuePushFile(param); +} + +// Method to set the active queue entry to the next entry. +void EMZQueueNext(enum ACTIONMODE mode) +{ + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + // Fast forward to next tape entry. + EMZTapeQueueAPSSSearch(1, 1); + + // Need to redraw the menu as read only text has changed. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } +} + +// Method to set the active queue entry to the previous entry. +void EMZQueuePrev(enum ACTIONMODE mode) +{ + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + // Fast forward to next tape entry. + EMZTapeQueueAPSSSearch(0, 1); + + // Need to redraw the menu as read only text has changed. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } +} + +// Simple wrapper method to clear the tape queue. +void EMZQueueClear(enum ACTIONMODE mode) +{ + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + uint16_t deletedEntries = EMZClearTapeQueue(); + + // Update the active row to account for number of entries deleted. + if(emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] - deletedEntries > 0) + emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] -= deletedEntries; + + // Need to redraw the menu as read only text has changed. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } +} + +// Method to select a path where tape images will be saved. +void EMZTapeSave(enum ACTIONMODE mode) +{ + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + // Prompt to enter the path in which incoming files will be saved. + EMZSetupDirList("Select Path", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "."); + emuControl.fileList.selectDir = 1; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZTapeSaveSet; + } +} + +// Method to save the entered directory path in the configuration. +void EMZTapeSaveSet(char *param) +{ + if(strlen(param) < MAX_FILENAME_LEN) + strcpy(emuConfig.params[emuConfig.machineModel].tapeSavePath, param); + emuControl.fileList.selectDir = 0; +} + +// Method to reset the emulation logic by issuing an MCTRL command. +void EMZReset(void) +{ + // Initiate a machine RESET via the control registers. + // + emuConfig.emuRegisters[MZ_EMU_REG_CTRL] |= 0x01; + + // Write the control register to initiate RESET command. + writeZ80Array(MZ_EMU_ADDR_REG_MODEL+emuConfig.emuRegisters[MZ_EMU_REG_CTRL], &emuConfig.emuRegisters[emuConfig.emuRegisters[MZ_EMU_REG_CTRL]], 1, FPGA); + + // Clear any reset command from the internal register copy so the command isnt accidentally sent again. + emuConfig.emuRegisters[MZ_EMU_REG_CTRL] &= 0xFE; + +} + +// Method to reset the machine. This can be done via the hardware, load up the configuration and include the machine definition and the hardware will force a reset. +// +void EMZResetMachine(enum ACTIONMODE mode) +{ + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + // Force a reload of the machine ROM's and reset. + EMZSwitchToMachine(emuConfig.machineModel, 1); + + // Refresh menu to update cursor. + EMZRefreshMenu(); + } +} + +// Method to read in the header of an MZF file and populate the tapeHeader structure. +// +short EMZReadTapeDetails(const char *tapeFile) +{ + // Locals. + // + char loadName[MAX_FILENAME_LEN+1]; + uint32_t actualReadSize; + FIL fileDesc; + FRESULT result; + + // If a relative path has been given we need to expand it into an absolute path. + if(tapeFile[0] != '/' && tapeFile[0] != '\\' && (tapeFile[0] < 0x30 || tapeFile[0] > 0x32)) + { + sprintf(loadName, "%s\%s", TOPLEVEL_DIR, tapeFile); + } else + { + strcpy(loadName, tapeFile); + } + + // Attempt to open the file for reading. + result = f_open(&fileDesc, loadName, FA_OPEN_EXISTING | FA_READ); + if(result) + { + debugf("EMZReadTapeDetails(open) File:%s, error: %d.\n", loadName, fileDesc); + return(result); + } + + // Read in the tape header, this indicates crucial data such as data type, size, exec address, load address etc. + // + result = f_read(&fileDesc, &emuControl.tapeHeader, MZF_HEADER_SIZE, &actualReadSize); + if(actualReadSize != 128) + { + debugf("Only read:%d bytes of header, aborting.\n", actualReadSize); + f_close(&fileDesc); + return(0x20); + } + + // Close the open file to complete, details stored in the tapeHeader structure. + f_close(&fileDesc); + + return result; +} + +// Method to load a tape (MZF) file directly into RAM. +// This involves reading the tape header, extracting the size and destination and loading +// the header and program into emulator ram. +// +short EMZLoadTapeToRAM(const char *tapeFile, unsigned char dstCMT) +{ + // Locals. + // + FIL fileDesc; + FRESULT result; + uint32_t loadAddress; + uint32_t actualReadSize; + uint32_t time = *ms; + uint32_t readSize; + char loadName[MAX_FILENAME_LEN+1]; + char sectorBuffer[512]; + #if defined __EMUMZ_DEBUG__ + char fileName[17]; + + debugf("Sending tape file:%s to emulator ram", tapeFile); + #endif + + // If a relative path has been given we need to expand it into an absolute path. + if(tapeFile[0] != '/' && tapeFile[0] != '\\' && (tapeFile[0] < 0x30 || tapeFile[0] > 0x32)) + { + sprintf(loadName, "%s\%s", TOPLEVEL_DIR, tapeFile); + } else + { + strcpy(loadName, tapeFile); + } + + // Attempt to open the file for reading. + result = f_open(&fileDesc, loadName, FA_OPEN_EXISTING | FA_READ); + if(result) + { + debugf("EMZLoadTapeToRAM(open) File:%s, error: %d.\n", loadName, fileDesc); + return(result); + } + + // Read in the tape header, this indicates crucial data such as data type, size, exec address, load address etc. + // + result = f_read(&fileDesc, &emuControl.tapeHeader, MZF_HEADER_SIZE, &actualReadSize); + if(actualReadSize != 128) + { + debugf("Only read:%d bytes of header, aborting.\n", actualReadSize); + f_close(&fileDesc); + return(0x20); + } + + // Some sanity checks. + // + if(emuControl.tapeHeader.dataType == 0 || emuControl.tapeHeader.dataType > 5) return(0x21); + #if defined __EMUMZ_DEBUG__ + for(int i=0; i < 17; i++) + { + fileName[i] = emuControl.tapeHeader.fileName[i] == 0x0d ? 0x00 : emuControl.tapeHeader.fileName[i]; + } + + // Debug output to indicate the file loaded and information about the tape image. + // + switch(emuControl.tapeHeader.dataType) + { + case 0x01: + debugf("Binary File(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", emuControl.tapeHeader.loadAddress, emuControl.tapeHeader.fileSize, emuControl.tapeHeader.execAddress, fileName); + break; + + case 0x02: + debugf("MZ-80 Basic Program(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", emuControl.tapeHeader.loadAddress, emuControl.tapeHeader.fileSize, emuControl.tapeHeader.execAddress, fileName); + break; + + case 0x03: + debugf("MZ-80 Data File(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", emuControl.tapeHeader.loadAddress, emuControl.tapeHeader.fileSize, emuControl.tapeHeader.execAddress, fileName); + break; + + case 0x04: + debugf("MZ-700 Data File(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", emuControl.tapeHeader.loadAddress, emuControl.tapeHeader.fileSize, emuControl.tapeHeader.execAddress, fileName); + break; + + case 0x05: + debugf("MZ-700 Basic Program(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", emuControl.tapeHeader.loadAddress, emuControl.tapeHeader.fileSize, emuControl.tapeHeader.execAddress, fileName); + break; + + default: + debugf("Unknown tape type(Type=%02x, Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", emuControl.tapeHeader.dataType, emuControl.tapeHeader.loadAddress, emuControl.tapeHeader.fileSize, emuControl.tapeHeader.execAddress, fileName); + break; + } + #endif + + // Check the data type, only load machine code directly to RAM. + // + if(dstCMT == 0 && emuControl.tapeHeader.dataType != CMT_TYPE_OBJCD) + { + f_close(&fileDesc); + return(0x22); + } + + // Reset Emulator if loading direct to RAM. This clears out memory, resets monitor and places it in a known state. + // + if(dstCMT == 0) + EMZReset(); + + // Load the data from tape to RAM. + // + if(dstCMT == 0) // Load to emulators RAM + { + loadAddress = MZ_EMU_RAM_ADDR + emuControl.tapeHeader.loadAddress; + } else + { + loadAddress = MZ_EMU_CMT_DATA_ADDR; + } + for (unsigned short i = 0; i < emuControl.tapeHeader.fileSize && actualReadSize > 0; i += actualReadSize) + { + result = f_read(&fileDesc, sectorBuffer, 512, &actualReadSize); + if(result) + { + debugf("Failed to read data from file:%s @ addr:%08lx, aborting.\n", loadName, loadAddress); + f_close(&fileDesc); + return(0x23); + } + + debugf("Bytes to read, actual:%d, index:%d, sizeHeader:%d, load:%08lx", actualReadSize, i, emuControl.tapeHeader.fileSize, loadAddress); + + if(actualReadSize > 0) + { + // Write the sector (or part) to the fpga memory. + writeZ80Array(loadAddress, sectorBuffer, actualReadSize, FPGA); + loadAddress += actualReadSize; + } else + { + debugf("Bad tape or corruption, should never be 0, actual:%d, index:%d, sizeHeader:%d", actualReadSize, i, emuControl.tapeHeader.fileSize); + return(0x24); + } + } + + // Now load header - this is done last because the emulator monitor wipes the stack area on reset. + // + writeZ80Array(MZ_EMU_CMT_HDR_ADDR, &emuControl.tapeHeader, MZF_HEADER_SIZE, FPGA); + +#ifdef __EMUMZ_DEBUG__ + time = *ms - time; + debugf("Uploaded in %lu ms", time >> 20); +#endif + + // Tidy up. + f_close(&fileDesc); + + // Remove the LF from the header filename, not needed. + // + for(int i=0; i < 17; i++) + { + if(emuControl.tapeHeader.fileName[i] == 0x0d) emuControl.tapeHeader.fileName[i] = 0x00; + } + + // Success. + // + return(0); +} + +// Method to save the contents of the CMT buffer onto a disk based MZF file. +// The method reads the header, writes it and then reads the data (upto size specified in header) and write it. +// +short EMZSaveTapeFromCMT(const char *tapeFile) +{ + short dataSize = 0; + uint32_t readAddress; + uint32_t actualWriteSize; + uint32_t writeSize = 0; + char fileName[MAX_FILENAME_LEN+1]; + char saveName[MAX_FILENAME_LEN+1]; + FIL fileDesc; + FRESULT result; + uint32_t time = *ms; + char sectorBuffer[512]; + + // Read the header, then data, but limit data size to the 'file size' stored in the header. + // + for(unsigned int mb=0; mb <= 1; mb++) + { + // Setup according to block being read, header or data. + // + if(mb == 0) + { + dataSize = MZF_HEADER_SIZE; + readAddress = MZ_EMU_CMT_HDR_ADDR; + } else + { + dataSize = emuControl.tapeHeader.fileSize; + readAddress = MZ_EMU_CMT_DATA_ADDR; + debugf("mb=%d, tapesize=%04x\n", mb, emuControl.tapeHeader.fileSize); + } + for (; dataSize > 0; dataSize -= actualWriteSize) + { + if(mb == 0) + { + writeSize = MZF_HEADER_SIZE; + } else + { + writeSize = dataSize > 512 ? 512 : dataSize; + } + debugf("mb=%d, dataSize=%04x, writeSize=%04x\n", mb, dataSize, writeSize); + + // Read the next chunk from the CMT RAM. + readZ80Array(readAddress, §orBuffer, writeSize, FPGA); + + if(mb == 0) + { + memcpy(&emuControl.tapeHeader, §orBuffer, MZF_HEADER_SIZE); + + // Now open the file for writing. If no name provided, use the one stored in the header. + // + if(tapeFile == 0) + { + for(int i=0; i < 17; i++) + { + fileName[i] = emuControl.tapeHeader.fileName[i] == 0x0d ? 0x00 : emuControl.tapeHeader.fileName[i]; + } + strcat(fileName, ".mzf"); + debugf("File from tape:%s (%02x,%04x,%04x,%04x)\n", fileName, emuControl.tapeHeader.dataType, emuControl.tapeHeader.fileSize, emuControl.tapeHeader.loadAddress, emuControl.tapeHeader.execAddress); + } else + { + strcpy(fileName, tapeFile); + debugf("File provided:%s\n", fileName); + } + + // If a relative path has been given we need to expand it into an absolute path. + if(fileName[0] != '/' && fileName[0] != '\\' && (fileName[0] < 0x30 || fileName[0] > 0x32)) + { + sprintf(saveName, "%s\\%s", emuConfig.params[emuConfig.machineModel].tapeSavePath, fileName); + } else + { + strcpy(saveName, fileName); + } + debugf("File to write:%s\n", saveName); + + // Attempt to open the file for writing. + result = f_open(&fileDesc, saveName, FA_CREATE_ALWAYS | FA_WRITE); + if(result) + { + debugf("EMZSaveFromCMT(open) File:%s, error: %d.\n", saveName, fileDesc); + return(3); + } + } + result = f_write(&fileDesc, sectorBuffer, writeSize, &actualWriteSize); + readAddress += actualWriteSize; + if(result) + { + debugf("EMZSaveFromCMT(write) File:%s, error: %d.\n", saveName, result); + f_close(&fileDesc); + return(4); + } + } + } + + // Close file to complete dump. + f_close(&fileDesc); + + return(0); +} + + +// Method to enable/disable the 40char monitor ROM and select the image to be used in the ROM. +// +void EMZMonitorROM40(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextMonitorROM40(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "*.*"); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZMonitorROM40Set; + } +} + +// Method to store the selected file name. +void EMZMonitorROM40Set(char *param) +{ + if(strlen(param) < MAX_FILENAME_LEN) + { + strcpy(emuConfig.params[emuConfig.machineModel].romMonitor40.romFileName, param); + emuConfig.params[emuConfig.machineModel].romMonitor40.romEnabled = 1; + } +} + +// Method to enable/disable the 80char monitor ROM and select the image to be used in the ROM. +// +void EMZMonitorROM80(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextMonitorROM80(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "*.*"); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZMonitorROM80Set; + } +} + +// Method to store the selected file name. +void EMZMonitorROM80Set(char *param) +{ + if(strlen(param) < MAX_FILENAME_LEN) + { + strcpy(emuConfig.params[emuConfig.machineModel].romMonitor80.romFileName, param); + emuConfig.params[emuConfig.machineModel].romMonitor80.romEnabled = 1; + } +} + +// Method to enable/disable the Character Generator ROM and select the image to be used in the ROM. +// +void EMZCGROM(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextCGROM(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "*.*"); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZCGROMSet; + } +} + +// Method to store the selected file name. +void EMZCGROMSet(char *param) +{ + if(strlen(param) < MAX_FILENAME_LEN) + { + strcpy(emuConfig.params[emuConfig.machineModel].romCG.romFileName, param); + emuConfig.params[emuConfig.machineModel].romCG.romEnabled = 1; + } +} + +// Method to enable/disable the Keyboard Mapping ROM and select the image to be used in the ROM. +// +void EMZKeyMappingROM(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextKeyMappingROM(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "*.*"); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZKeyMappingROMSet; + } +} + +// Method to store the selected file name. +void EMZKeyMappingROMSet(char *param) +{ + if(strlen(param) < MAX_FILENAME_LEN) + { + strcpy(emuConfig.params[emuConfig.machineModel].romKeyMap.romFileName, param); + emuConfig.params[emuConfig.machineModel].romKeyMap.romEnabled = 1; + } +} + +// Method to enable/disable the User Option ROM and select the image to be used in the ROM. +// +void EMZUserROM(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextUserROM(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "*.*"); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZUserROMSet; + } +} + +// Method to store the selected file name. +void EMZUserROMSet(char *param) +{ + if(strlen(param) < MAX_FILENAME_LEN) + { + strcpy(emuConfig.params[emuConfig.machineModel].romUser.romFileName, param); + emuConfig.params[emuConfig.machineModel].romUser.romEnabled = 1; + } +} + +// Method to enable/disable the FDC Option ROM and select the image to be used in the ROM. +// +void EMZFloppyDiskROM(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextFloppyDiskROM(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "*.*"); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZFloppyDiskROMSet; + } +} + +// Method to store the selected file name. +void EMZFloppyDiskROMSet(char *param) +{ + if(strlen(param) < MAX_FILENAME_LEN) + { + strcpy(emuConfig.params[emuConfig.machineModel].romFDC.romFileName, param); + emuConfig.params[emuConfig.machineModel].romFDC.romEnabled = 1; + } +} + +// Method to enable/disable the cold boot application load and select the image to be loaded. +// +void EMZLoadApplication(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + EMZNextLoadApplication(mode); + EMZRefreshMenu(); + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + EMZSetupDirList("Select File", emuControl.activeDir.dir[emuControl.activeDir.dirIdx], FONT_7X8); + strcpy(emuControl.fileList.fileFilter, "*.MZF"); + emuControl.fileList.selectDir = 0; + EMZReadDirectory(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], emuControl.fileList.fileFilter); + EMZRefreshFileList(); + + // Switch to the File List Dialog mode setting the return Callback which will be activated after a file has been chosen. + // + emuControl.activeDialog = DIALOG_FILELIST; + emuControl.fileList.returnCallback = EMZLoadApplicationSet; + } +} + +// Method to store the selected file name. +void EMZLoadApplicationSet(char *param) +{ + // Locals. + uint8_t tmpbuf[20]; + + // If a file has been selected, store it within the config and also extract the load address from the file to be used as the + // default Post load key injection sequence. + if(strlen(param) < MAX_FILENAME_LEN) + { + strcpy(emuConfig.params[emuConfig.machineModel].loadApp.appFileName, param); + emuConfig.params[emuConfig.machineModel].loadApp.appEnabled = 1; + + // Try read the tape, set tapeHeader structure to tape details. + if(EMZReadTapeDetails(emuConfig.params[emuConfig.machineModel].loadApp.appFileName) == 0) + { + // Clear out existing key entries. + for(uint16_t idx=0; idx < MAX_KEY_INS_BUFFER; idx++) { if(emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx].i == 0) { emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx].i = 0xffffffff; } } + + // First key injection is a pause, a delay needed for the monitor BIOS to startup and present the entry prompt. + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[0].b[0] = 0x00; + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[0].b[1] = 0x00; + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[0].b[2] = 0x7f; + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[0].b[3] = 0x82; + + // Default command based on target machine. + // + switch(emuConfig.machineModel) + { + case MZ80K: + case MZ80C: + sprintf(tmpbuf, "GOTO$%04x\r", emuControl.tapeHeader.execAddress); + break; + + default: + sprintf(tmpbuf, "J%04x\r", emuControl.tapeHeader.execAddress); + break; + } + + for(uint16_t idx=0; idx < strlen(tmpbuf); idx++) + { + // Map the ASCII key and insert into the key injection buffer. + t_numCnv map = EMZMapToScanCode(emuControl.hostMachine, tmpbuf[idx]); + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+1].b[0] = map.b[0]; + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+1].b[1] = map.b[1]; + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+1].b[2] = 0x7f; + emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+1].b[3] = 0x7f; + + } + } + } +} + +void EMZMainMenu(void) +{ + // Locals. + // + uint8_t row = 0; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_MAIN; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "Main Menu", FONT_7X8); + EMZAddToMenu(row++, 0, "Tape Storage", 'T', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZTapeStorageMenu, MENUCB_REFRESH, NULL, NULL ); + + // The MZ80K/MZ80C Floppy Disk drives use a different controller to the rest of the models, so not yet implemented. + if(emuConfig.machineModel == MZ80K || emuConfig.machineModel == MZ80C) + { + EMZAddToMenu(row++, 0, "Floppy Storage", 'F', MENUTYPE_SUBMENU, MENUSTATE_GREYED, EMZFloppyStorageMenu, MENUCB_REFRESH, NULL, NULL ); + } else + { + EMZAddToMenu(row++, 0, "Floppy Storage", 'F', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZFloppyStorageMenu, MENUCB_REFRESH, NULL, NULL ); + } + + EMZAddToMenu(row++, 0, "Machine", 'M', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZMachineMenu, MENUCB_REFRESH, NULL, NULL ); + EMZAddToMenu(row++, 0, "Display", 'D', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZDisplayMenu, MENUCB_REFRESH, NULL, NULL ); + EMZAddToMenu(row++, 0, "Audio", 'A', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZAudioMenu, MENUCB_REFRESH, NULL, NULL ); + EMZAddToMenu(row++, 0, "System", 'S', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZSystemMenu, MENUCB_REFRESH, NULL, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "Reset Machine", 'R', MENUTYPE_ACTION, MENUSTATE_ACTIVE, EMZResetMachine, MENUCB_DONOTHING, NULL, NULL ); + EMZRefreshMenu(); +} + +void EMZTapeStorageMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + char *fileName; + char lineBuf[MENU_ROW_WIDTH+1]; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_TAPE_STORAGE; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "Tape Storage Menu", FONT_7X8); + EMZAddToMenu(row++, 0, "CMT Hardware", 'C', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZChangeCMTMode, MENUCB_REFRESH, EMZGetCMTModeChoice, NULL ); + EMZAddToMenu(row++, 0, "Load tape to RAM", 'L', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZLoadDirectToRAM, MENUCB_DONOTHING, EMZGetLoadDirectFileFilterChoice, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "Queue Tape", 'Q', MENUTYPE_ACTION | MENUTYPE_CHOICE, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZQueueTape, MENUCB_DONOTHING, EMZGetQueueTapeFileFilterChoice, NULL ); + + // List out the current tape queue. + if(!emuConfig.params[emuConfig.machineModel].cmtMode) + { + uint16_t fileCount = 0; + while((fileName = EMZNextTapeQueueFilename(0)) != NULL) + { + // Place an indicator on the active queue file. + if((EMZGetMachineGroup() == GROUP_MZ80B && emuControl.tapeQueue.tapePos == fileCount) || + (EMZGetMachineGroup() != GROUP_MZ80B && fileCount == 0)) + sprintf(lineBuf, " >%d %.50s", fileCount++, fileName); + else + sprintf(lineBuf, " %d %.50s", fileCount++, fileName); + EMZAddToMenu(row++, 0, lineBuf, 0x00, MENUTYPE_TEXT, MENUSTATE_TEXT, NULL, MENUCB_DONOTHING, NULL, NULL ); + } + } + + // Hidden function entries to set the active queue position. + EMZAddToMenu(row++, 0, "", '+', MENUTYPE_ACTION, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_HIDDEN : MENUSTATE_INACTIVE, EMZQueueNext, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "", '-', MENUTYPE_ACTION, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_HIDDEN : MENUSTATE_INACTIVE, EMZQueuePrev, MENUCB_DONOTHING, NULL, NULL ); + // + EMZAddToMenu(row++, 0, "Clear Queue", 'e', MENUTYPE_ACTION, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZQueueClear, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "File Name Map Ascii", 'F', MENUTYPE_ACTION | MENUTYPE_CHOICE, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextCMTAsciiMapping, MENUCB_REFRESH, EMZGetCMTAsciiMappingChoice, NULL ); + EMZAddToMenu(row++, 0, "Save Tape Directory", 'T', MENUTYPE_ACTION | MENUTYPE_CHOICE, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZTapeSave, MENUCB_DONOTHING, EMZGetTapeSaveFilePathChoice, NULL ); + EMZAddToMenu(row++, 0, "Fast Tape Load", 'd', MENUTYPE_CHOICE, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFastTapeLoad, MENUCB_REFRESH, EMZGetFastTapeLoadChoice, NULL ); + if(EMZGetMachineGroup() != GROUP_MZ80B) + { + // EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK, NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "Tape Buttons", 'B', MENUTYPE_CHOICE, !emuConfig.params[emuConfig.machineModel].cmtMode ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextTapeButtons, MENUCB_REFRESH, EMZGetTapeButtonsChoice, NULL ); + } + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZFloppyStorageMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + char *fileName; + char lineBuf[MENU_ROW_WIDTH+1]; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_FLOPPY_STORAGE; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "Floppy Storage Menu", FONT_7X8); + EMZAddToMenu(row++, 0, "FDD Hardware", 'F', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZChangeFDDMode, MENUCB_REFRESH, EMZGetFDDModeChoice, NULL ); + EMZAddToMenu(row++, 0, "File Selection Filter", 'S', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextDriveImageFilter, MENUCB_REFRESH, EMZGetFDDDriveFileFilterChoice, NULL ); + + EMZAddToMenu(row++, 0, "Disk 0", '0', MENUTYPE_ACTION | MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZFDDSetDriveImage0, MENUCB_DONOTHING, EMZGetFDDDrive0FileChoice, NULL ); + EMZAddToMenu(row++, 0, " Type", 'T', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDDriveType0, MENUCB_REFRESH, EMZGetFDDDriveType0Choice, NULL ); + EMZAddToMenu(row++, 0, " Image Polarity", 'P', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDImagePolarity0, MENUCB_REFRESH, EMZGetFDDImagePolarity0Choice, NULL ); + EMZAddToMenu(row++, 0, " Update Mode", 'U', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDUpdateMode0, MENUCB_REFRESH, EMZGetFDDUpdateMode0Choice, NULL ); + EMZAddToMenu(row++, 0, " Mount/Eject", 'E', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextMountDrive0, MENUCB_REFRESH, EMZGetFDDMount0Choice, NULL ); + + EMZAddToMenu(row++, 0, "Disk 1", '1', MENUTYPE_ACTION | MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZFDDSetDriveImage1, MENUCB_DONOTHING, EMZGetFDDDrive1FileChoice, NULL ); + EMZAddToMenu(row++, 0, " Type", 'y', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDDriveType1, MENUCB_REFRESH, EMZGetFDDDriveType1Choice, NULL ); + EMZAddToMenu(row++, 0, " Image Polarity", 'i', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDImagePolarity1, MENUCB_REFRESH, EMZGetFDDImagePolarity1Choice, NULL ); + EMZAddToMenu(row++, 0, " Update Mode", 'd', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDUpdateMode1, MENUCB_REFRESH, EMZGetFDDUpdateMode1Choice, NULL ); + EMZAddToMenu(row++, 0, " Mount/Eject", 'j', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextMountDrive1, MENUCB_REFRESH, EMZGetFDDMount1Choice, NULL ); + + EMZAddToMenu(row++, 0, "Disk 2", '2', MENUTYPE_ACTION | MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZFDDSetDriveImage2, MENUCB_DONOTHING, EMZGetFDDDrive2FileChoice, NULL ); + EMZAddToMenu(row++, 0, " Type", 'p', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDDriveType2, MENUCB_REFRESH, EMZGetFDDDriveType2Choice, NULL ); + EMZAddToMenu(row++, 0, " Image Polarity", 'l', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDImagePolarity2, MENUCB_REFRESH, EMZGetFDDImagePolarity2Choice, NULL ); + EMZAddToMenu(row++, 0, " Update Mode", 'M', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDUpdateMode2, MENUCB_REFRESH, EMZGetFDDUpdateMode2Choice, NULL ); + EMZAddToMenu(row++, 0, " Mount/Eject", 'c', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextMountDrive2, MENUCB_REFRESH, EMZGetFDDMount2Choice, NULL ); + + EMZAddToMenu(row++, 0, "Disk 3", '3', MENUTYPE_ACTION | MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZFDDSetDriveImage3, MENUCB_DONOTHING, EMZGetFDDDrive3FileChoice, NULL ); + EMZAddToMenu(row++, 0, " Type", 'e', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDDriveType3, MENUCB_REFRESH, EMZGetFDDDriveType3Choice, NULL ); + EMZAddToMenu(row++, 0, " Image Polarity", 'a', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDImagePolarity3, MENUCB_REFRESH, EMZGetFDDImagePolarity3Choice, NULL ); + EMZAddToMenu(row++, 0, " Update Mode", 'o', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextFDDUpdateMode3, MENUCB_REFRESH, EMZGetFDDUpdateMode3Choice, NULL ); + EMZAddToMenu(row++, 0, " Mount/Eject", 't', MENUTYPE_CHOICE, emuConfig.params[emuConfig.machineModel].fddEnabled ? MENUSTATE_ACTIVE : MENUSTATE_INACTIVE, EMZNextMountDrive3, MENUCB_REFRESH, EMZGetFDDMount3Choice, NULL ); + + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZMachineMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_MACHINE; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "Machine Menu", FONT_7X8); + EMZAddToMenu(row++, 0, "Machine Model", 'M', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextMachineModel, MENUCB_REFRESH, EMZGetMachineModelChoice, NULL ); + EMZAddToMenu(row++, 0, "CPU Speed", 'C', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextCPUSpeed, MENUCB_REFRESH, EMZGetCPUSpeedChoice, NULL ); + EMZAddToMenu(row++, 0, "Memory Size", 'S', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextMemSize, MENUCB_REFRESH, EMZGetMemSizeChoice, NULL ); + if(emuConfig.machineModel == MZ800) + { + EMZAddToMenu(row++, 0, "Mode", 'o', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextMZ800Mode, MENUCB_REFRESH, EMZGetMZ800ModeChoice, NULL ); + EMZAddToMenu(row++, 0, "Printer", 'P', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextMZ800Printer, MENUCB_REFRESH, EMZGetMZ800PrinterChoice, NULL ); + EMZAddToMenu(row++, 0, "Tape In", 'T', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextMZ800TapeIn, MENUCB_REFRESH, EMZGetMZ800TapeInChoice, NULL ); + } + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "Rom Management", 'R', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZRomManagementMenu, MENUCB_REFRESH, NULL, NULL ); + EMZAddToMenu(row++, 0, "AutoStart Application", 'A', MENUTYPE_SUBMENU, MENUSTATE_ACTIVE, EMZAutoStartApplicationMenu, MENUCB_REFRESH, NULL, NULL ); + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZDisplayMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_DISPLAY; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "Display Menu", FONT_7X8); + switch(emuConfig.machineModel) + { + case MZ80K: + case MZ80C: + case MZ1200: + case MZ80A: + case MZ700: + case MZ1500: + EMZAddToMenu(row++, 0, "Display Type", 'T', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextDisplayType, MENUCB_REFRESH, EMZGetDisplayTypeChoice, NULL ); + break; + + default: + break; + } + switch(emuConfig.machineModel) + { + case MZ80A: + case MZ700: + case MZ800: + case MZ1500: + case MZ80B: + case MZ2000: + case MZ2200: + case MZ2500: + EMZAddToMenu(row++, 0, "Display Option", 'D', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextDisplayOption, MENUCB_REFRESH, EMZGetDisplayOptionChoice, NULL ); + break; + + default: + break; + } + EMZAddToMenu(row++, 0, "Display Output", 'O', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextDisplayOutput, MENUCB_REFRESH, EMZGetDisplayOutputChoice, NULL ); + EMZAddToMenu(row++, 0, "Video", 'V', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextVRAMMode, MENUCB_REFRESH, EMZGetVRAMModeChoice, NULL ); + switch(emuConfig.machineModel) + { + case MZ800: + case MZ80B: + case MZ2000: + case MZ2200: + case MZ2500: + EMZAddToMenu(row++, 0, "Graphics", 'G', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextGRAMMode, MENUCB_REFRESH, EMZGetGRAMModeChoice, NULL ); + break; + + default: + break; + } + if(emuConfig.machineModel == MZ80A) + { + EMZAddToMenu(row++, 0, "VRAM CPU Wait", 'W', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextVRAMWaitMode, MENUCB_REFRESH, EMZGetVRAMWaitModeChoice, NULL ); + } + if(strcmp(EMZGetDisplayOptionChoice(), "PCG") == 0) + { + EMZAddToMenu(row++, 0, "PCG Mode", 'P', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextPCGMode, MENUCB_REFRESH, EMZGetPCGModeChoice, NULL ); + } + //EMZAddToMenu(row++, 0, "Aspect Ratio", 'R', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextAspectRatio, MENUCB_REFRESH, EMZGetAspectRatioChoice, NULL ); + //EMZAddToMenu(row++, 0, "Scandoubler", 'S', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextScanDoublerFX, MENUCB_REFRESH, EMZGetScanDoublerFXChoice, NULL ); + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZAudioMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_AUDIO; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "Audio Menu", FONT_7X8); + EMZAddToMenu(row++, 0, "Source", 'S', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextAudioSource, MENUCB_REFRESH, EMZGetAudioSourceChoice, NULL ); + EMZAddToMenu(row++, 0, "Hardware", 'H', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextAudioHardware, MENUCB_REFRESH, EMZGetAudioHardwareChoice, NULL ); + // If FPGA sound hardware is selected show configuration options. + if(emuConfig.params[emuConfig.machineModel].audioHardware != 0) + { + EMZAddToMenu(row++, 0, "Volume", 'V', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextAudioVolume, MENUCB_REFRESH, EMZGetAudioVolumeChoice, NULL ); + EMZAddToMenu(row++, 0, "Mute", 'M', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextAudioMute, MENUCB_REFRESH, EMZGetAudioMuteChoice, NULL ); + EMZAddToMenu(row++, 0, "Channel Mix", 'C', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZNextAudioMix, MENUCB_REFRESH, EMZGetAudioMixChoice, NULL ); + } + + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZSystemMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_SYSTEM; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "System Menu", FONT_7X8); + EMZAddToMenu(row++, 0, "Reload config", 'R', MENUTYPE_ACTION, MENUSTATE_ACTIVE, EMZReadConfig, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "Save config", 'S', MENUTYPE_ACTION, MENUSTATE_ACTIVE, EMZWriteConfig, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "Reset config", 'e', MENUTYPE_ACTION, MENUSTATE_ACTIVE, EMZResetConfig, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "About", 'A', MENUTYPE_SUBMENU | MENUTYPE_ACTION, MENUSTATE_ACTIVE, EMZAbout, MENUCB_REFRESH, NULL, NULL ); + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZAbout(enum ACTIONMODE mode) +{ + // Locals. + // + uint16_t maxX = OSDGet(ACTIVE_MAX_X); + uint8_t textChrX = (emuControl.menu.colPixelStart / (emuControl.menu.rowFontptr->width + emuControl.menu.rowFontptr->spacing)); + + // Use the menu framework to draw the borders and title but write a bitmap and text directly. + // + EMZSetupMenu(EMZGetMachineTitle(), "About", FONT_7X8); + OSDWriteBitmap(48, 15, BITMAP_ARGO_MEDIUM, RED, BLACK); + OSDWriteString(22, 9, 0, 2, 0, 0, (maxX < 512 ? FONT_5X7 : FONT_7X8), NORMAL, "Sharp MZ Series v2.01", NULL, CYAN, BLACK); + OSDWriteString(19, 10, 0, 2, 0, 0, (maxX < 512 ? FONT_5X7 : FONT_7X8), NORMAL, "(C) Philip Smart, 2018-2021", NULL, CYAN, BLACK); + OSDWriteString(21, 11, 0, 2, 0, 0, (maxX < 512 ? FONT_5X7 : FONT_7X8), NORMAL, "MZ-700 Embedded Version", NULL, CYAN, BLACK); + OSDWriteString(textChrX+1, 0, 0, 4, 0, 0, (maxX < 512 ? FONT_5X7 : FONT_5X7), NORMAL, "\x1b back", NULL, CYAN, BLACK); + EMZRefreshMenu(); +} + +void EMZRomManagementMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_ROMMANAGEMENT; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "Rom Management Menu", FONT_7X8); + + EMZAddToMenu(row++, 0, "Monitor ROM (40x25)", '4', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZMonitorROM40, MENUCB_DONOTHING, EMZGetMonitorROM40Choice, NULL ); + EMZAddToMenu(row++, 0, "Monitor ROM (80x25)", '8', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZMonitorROM80, MENUCB_DONOTHING, EMZGetMonitorROM80Choice, NULL ); + EMZAddToMenu(row++, 0, "Char Generator ROM", 'G', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZCGROM, MENUCB_DONOTHING, EMZGetCGROMChoice, NULL ); + EMZAddToMenu(row++, 0, "Key Mapping ROM", 'K', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZKeyMappingROM, MENUCB_DONOTHING, EMZGetKeyMappingROMChoice, NULL ); + EMZAddToMenu(row++, 0, "User ROM", 'U', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZUserROM, MENUCB_DONOTHING, EMZGetUserROMChoice, NULL ); + EMZAddToMenu(row++, 0, "Floppy Disk ROM", 'F', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZFloppyDiskROM, MENUCB_DONOTHING, EMZGetFloppyDiskROMChoice, NULL ); + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZAutoStartApplicationMenu(enum ACTIONMODE mode) +{ + // Locals. + // + uint8_t row = 0; + char lineBuf[MENU_ROW_WIDTH+1]; + + // Set active menu for the case when this method is invoked as a menu callback. + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_AUTOSTART; + emuControl.activeDialog = DIALOG_MENU; + + EMZSetupMenu(EMZGetMachineTitle(), "AutoStart Menu", FONT_7X8); + + EMZAddToMenu(row++, 0, "Enable AutoStart", 'E', MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZChangeAutoStart, MENUCB_DONOTHING, EMZGetAutoStartChoice, NULL ); + if(emuConfig.params[emuConfig.machineModel].autoStart) + { + EMZAddToMenu(row++, 0, "Application to Load", 'A', MENUTYPE_ACTION | MENUTYPE_CHOICE, MENUSTATE_ACTIVE, EMZLoadApplication, MENUCB_DONOTHING, EMZGetLoadApplicationChoice, NULL ); + EMZAddToMenu(row++, 0, "Pre-load key injection", 'r', MENUTYPE_ACTION, MENUSTATE_ACTIVE, EMZPreKeyEntry, MENUCB_DONOTHING, NULL, EMZRenderPreKeyViewTop ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + + EMZAddToMenu(row++, 0, "Post-load key injection",'o', MENUTYPE_ACTION, MENUSTATE_ACTIVE, EMZPostKeyEntry, MENUCB_DONOTHING, NULL, EMZRenderPostKeyViewTop ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + EMZAddToMenu(row++, 0, "", 0x00, MENUTYPE_BLANK, MENUSTATE_BLANK , NULL, MENUCB_DONOTHING, NULL, NULL ); + } + + // When called as a select callback then the menus are moving forward so start the active row at the top. + if(mode == ACTION_SELECT) emuControl.activeMenu.activeRow[emuControl.activeMenu.menuIdx] = 0; + EMZRefreshMenu(); +} + +void EMZRenderPreKeyViewTop(void) +{ + EMZRenderPreKeyView(0); +} +void EMZRenderPreKeyView(uint16_t startpos) +{ + // Locals. + // + uint16_t maxX = OSDGet(ACTIVE_MAX_X); + char lineBuf[MAX_INJEDIT_COLS*KEY_INJEDIT_NIBBLES+MENU_ROW_WIDTH+1]; + + // Adjust start position to get 4 view rows. + startpos=startpos > KEY_INJEDIT_ROWS-MAX_INJEDIT_ROWS ? (KEY_INJEDIT_ROWS-MAX_INJEDIT_ROWS)*MAX_INJEDIT_COLS : startpos*MAX_INJEDIT_COLS; + + // Now add in the key entry text in a smaller font. + for(uint16_t idx=startpos; idx < startpos+(MAX_INJEDIT_ROWS*MAX_INJEDIT_COLS); idx+=MAX_INJEDIT_COLS) + { + lineBuf[0] = 0x00; + for(uint16_t idx2=0; idx2 < MAX_INJEDIT_COLS && idx+idx2 < MAX_KEY_INS_BUFFER; idx2++) + { + sprintf(&lineBuf[strlen(lineBuf)], "%s%02x%02x%02x%02x", idx2 == 0 ? "" : " ", + (emuConfig.params[emuConfig.machineModel].loadApp.preKeyInsertion[idx+idx2].b[0]), + (emuConfig.params[emuConfig.machineModel].loadApp.preKeyInsertion[idx+idx2].b[1]), + (emuConfig.params[emuConfig.machineModel].loadApp.preKeyInsertion[idx+idx2].b[2]), + (emuConfig.params[emuConfig.machineModel].loadApp.preKeyInsertion[idx+idx2].b[3])); + } + OSDWriteString(10 - (maxX < 512 ? 2 : 0), 6+((idx-startpos)/MAX_INJEDIT_COLS)+(maxX < 512 ? 1 : 0), 0, 0, 0, 0, maxX < 512 ? FONT_3X6 : FONT_5X7, NORMAL, lineBuf, NULL, PURPLE, BLACK); + } +} +void EMZRenderPostKeyViewTop(void) +{ + EMZRenderPostKeyView(0); +} +void EMZRenderPostKeyView(uint16_t startpos) +{ + // Locals. + // + uint16_t maxX = OSDGet(ACTIVE_MAX_X); + char lineBuf[MAX_INJEDIT_COLS*KEY_INJEDIT_NIBBLES+MENU_ROW_WIDTH+1]; + + // Adjust start position to get 4 view rows. + startpos=startpos > KEY_INJEDIT_ROWS-MAX_INJEDIT_ROWS ? (KEY_INJEDIT_ROWS-MAX_INJEDIT_ROWS)*MAX_INJEDIT_COLS : startpos*MAX_INJEDIT_COLS; + + for(uint16_t idx=startpos; idx < startpos+(MAX_INJEDIT_ROWS*MAX_INJEDIT_COLS); idx+=MAX_INJEDIT_COLS) + { + lineBuf[0] = 0x00; + for(uint16_t idx2=0; idx2 < MAX_INJEDIT_COLS && idx+idx2 < MAX_KEY_INS_BUFFER; idx2++) + { + sprintf(&lineBuf[strlen(lineBuf)], "%s%02x%02x%02x%02x", idx2 == 0 ? "" : " ", + (emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+idx2].b[0]), + (emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+idx2].b[1]), + (emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+idx2].b[2]), + (emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[idx+idx2].b[3])); + } + OSDWriteString(10 - (maxX < 512 ? 2 : 0), 11+((idx-startpos)/MAX_INJEDIT_COLS)+(maxX < 512 ? 1 : 0), 0, 4, 0, 0, maxX < 512 ? FONT_3X6 : FONT_5X7, NORMAL, lineBuf, NULL, GREEN, BLACK); + } +} + +void EMZPreKeyEntry(void) +{ + // Locals. + uint16_t maxX = OSDGet(ACTIVE_MAX_X); + uint8_t lineBuf[10]; + + // Setup the control structure to be used by the key call back eventhandler to correctly update the displayed and stored buffer. + emuControl.keyInjEdit.bufptr = emuConfig.params[emuConfig.machineModel].loadApp.preKeyInsertion; + emuControl.keyInjEdit.editptr = 0; + emuControl.keyInjEdit.cursorAttr = HILIGHT_BG_WHITE; + emuControl.keyInjEdit.fg = PURPLE; + emuControl.keyInjEdit.bg = BLACK; + emuControl.keyInjEdit.font = maxX < 512 ? FONT_3X6 : FONT_5X7; + emuControl.keyInjEdit.startRow = 6+(maxX < 512 ? 1 : 0); + emuControl.keyInjEdit.startCol = 10 - (maxX < 512 ? 2 : 0); + emuControl.keyInjEdit.offsetRow = 0; + emuControl.keyInjEdit.offsetCol = 0; + emuControl.keyInjEdit.cursorFlashRate = 250; + emuControl.keyInjEdit.curView = 0; + emuControl.keyInjEdit.render = EMZRenderPreKeyView; + + // Setup cursor on the first nibble in buffer. + sprintf(lineBuf, "%01x", (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0]>>4)); + OSDSetCursorFlash(emuControl.keyInjEdit.startCol, emuControl.keyInjEdit.startRow, emuControl.keyInjEdit.offsetCol, emuControl.keyInjEdit.offsetRow, emuControl.keyInjEdit.font, lineBuf[0], emuControl.keyInjEdit.fg, emuControl.keyInjEdit.bg, emuControl.keyInjEdit.cursorAttr, emuControl.keyInjEdit.cursorFlashRate); + + // Setup to use the key injection editor event handler so all keys entered are redirected into this handler. + emuControl.activeDialog = DIALOG_KEYENTRY; + + return; +} + +void EMZPostKeyEntry(void) +{ + // Locals. + uint16_t maxX = OSDGet(ACTIVE_MAX_X); + uint8_t lineBuf[10]; + + // Setup the control structure to be used by the key call back eventhandler to correctly update the displayed and stored buffer. + emuControl.keyInjEdit.bufptr = emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion; + emuControl.keyInjEdit.editptr = 0; + emuControl.keyInjEdit.cursorAttr = HILIGHT_BG_WHITE; + emuControl.keyInjEdit.fg = GREEN; + emuControl.keyInjEdit.bg = BLACK; + emuControl.keyInjEdit.font = maxX < 512 ? FONT_3X6 : FONT_5X7; + emuControl.keyInjEdit.startRow = 11 +(maxX < 512 ? 1 : 0); + emuControl.keyInjEdit.startCol = 10 - (maxX < 512 ? 2 : 0); + emuControl.keyInjEdit.offsetRow = 4; + emuControl.keyInjEdit.offsetCol = 0; + emuControl.keyInjEdit.cursorFlashRate = 250; + emuControl.keyInjEdit.curView = 0; + emuControl.keyInjEdit.render = EMZRenderPostKeyView; + + // Setup cursor on the first nibble in buffer. + sprintf(lineBuf, "%01x", (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0]>>4)); + OSDSetCursorFlash(emuControl.keyInjEdit.startCol, emuControl.keyInjEdit.startRow, emuControl.keyInjEdit.offsetCol, emuControl.keyInjEdit.offsetRow, emuControl.keyInjEdit.font, lineBuf[0], emuControl.keyInjEdit.fg, emuControl.keyInjEdit.bg, emuControl.keyInjEdit.cursorAttr, emuControl.keyInjEdit.cursorFlashRate); + + // Setup to use the key injection editor event handler so all keys entered are redirected into this handler. + emuControl.activeDialog = DIALOG_KEYENTRY; + + return; +} +void EMZKeyInjectionEdit(uint8_t data, uint8_t ctrl) +{ + // Locals. + // + uint8_t lineBuf[10]; + uint8_t row; + uint8_t col; + uint8_t key; + + char tmpbuf[MAX_FILENAME_LEN+1]; + uint16_t rowPixelDepth = (emuControl.fileList.rowFontptr->height + emuControl.fileList.rowFontptr->spacing + emuControl.fileList.padding + 2); + uint16_t maxRow = ((uint16_t)OSDGet(ACTIVE_MAX_Y)/rowPixelDepth) + 1; + + // data = ascii code/ctrl code for key pressed. + // ctrl = KEY_BREAK & KEY_CTRL & KEY_SHIFT & "000" & KEY_DOWN & KEY_UP + + // If the break key is pressed, this is equivalent to escape, ie. exit edit mode. + if(ctrl & KEY_BREAK_BIT) + { + // Disable the cursor. + OSDClearCursorFlash(); + + // Just switch back to active menu dont activate the callback for storing a selection. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } else + { + // Process according to pressed key. + // + switch(data) + { + // Up key. + case 0xA0: + if(emuControl.keyInjEdit.editptr >= KEY_INJEDIT_NIBBLES_PER_ROW) + { + emuControl.keyInjEdit.editptr-=KEY_INJEDIT_NIBBLES_PER_ROW; + } + break; + + // Down key. + case 0xA1: + if(emuControl.keyInjEdit.editptr < ((MAX_KEY_INS_BUFFER*KEY_INJEDIT_NIBBLES)-KEY_INJEDIT_NIBBLES_PER_ROW)) + { + emuControl.keyInjEdit.editptr+=KEY_INJEDIT_NIBBLES_PER_ROW; + } + break; + + // Left key. + case 0xA4: + if(ctrl & KEY_SHIFT_BIT) + { + if(emuControl.keyInjEdit.editptr > 1) + { + emuControl.keyInjEdit.editptr = emuControl.keyInjEdit.editptr >= KEY_INJEDIT_NIBBLES ? ((emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES)-1)*KEY_INJEDIT_NIBBLES : 0; + } + } + else + { + if(emuControl.keyInjEdit.editptr > 0) + { + emuControl.keyInjEdit.editptr--; + } + } + break; + + case 0xA3: // Right Key + if(ctrl & KEY_SHIFT_BIT) + { + if(emuControl.keyInjEdit.editptr < ((MAX_KEY_INS_BUFFER*KEY_INJEDIT_NIBBLES)-KEY_INJEDIT_NIBBLES)) + { + emuControl.keyInjEdit.editptr = ((emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES)+1)*KEY_INJEDIT_NIBBLES; + } + } + else + { + if(emuControl.keyInjEdit.editptr < (MAX_KEY_INS_BUFFER*KEY_INJEDIT_NIBBLES)-1) + { + emuControl.keyInjEdit.editptr++; + } + } + break; + + // All other keys are processed as data. + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + default: + // When CTRL is pressed we insert a nibble based on the key pressed. + if(ctrl & KEY_CTRL_BIT) + { + key = toupper(data); + if(key >= '0' && key <= '9') + key -= '0'; + else if(key >= 'A' && key <= 'F') + key = key - 'A' + 10; + else + // Npt a hex value. + break; + + // Update the nibble according to edit pointer. + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 0) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0] & 0x0f | key << 4; + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 1) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0] & 0xf0 | key; + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 2) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1] & 0x0f | key << 4; + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 3) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1] & 0xf0 | key; + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 4) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2] & 0x0f | key << 4; + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 5) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2] & 0xf0 | key; + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 6) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3] & 0x0f | key << 4; + if(emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 7) emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3] = emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3] & 0xf0 | key; + + // Move onto next nibble. + emuControl.keyInjEdit.editptr += emuControl.keyInjEdit.editptr < (MAX_KEY_INS_BUFFER*KEY_INJEDIT_NIBBLES)-1 ? 1 : 0; + } else + { + emuControl.keyInjEdit.editptr = (emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES) * KEY_INJEDIT_NIBBLES; + t_numCnv map = EMZMapToScanCode(emuControl.hostMachine, data); + + // If a modifier key is required, it must be inserted first with 0 delay in the down/up pause timers. To avoid clash with other valid combinations, + // the delay is set to 0ms down key, 0S up key (stipulated with bit 7 set for seconds). + if(map.b[2] != 0xff && map.b[3] != 0xff) + { + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0] = map.b[2]; + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1] = map.b[3]; + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2] = 0x00; + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3] = 0x80; + emuControl.keyInjEdit.editptr += emuControl.keyInjEdit.editptr < (MAX_KEY_INS_BUFFER-1)*KEY_INJEDIT_NIBBLES ? KEY_INJEDIT_NIBBLES : 0; + } + // Now insert the actual key. If the buffer is full we overwrite the last position. + if(map.b[0] != 0xff && map.b[1] != 0xff) + { + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0] = map.b[0]; + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1] = map.b[1]; + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2] = 0x7f; + emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3] = 0x7f; + emuControl.keyInjEdit.editptr += emuControl.keyInjEdit.editptr < (MAX_KEY_INS_BUFFER-1)*KEY_INJEDIT_NIBBLES ? KEY_INJEDIT_NIBBLES : 0; + } + } + break; + } + + // Set the view portal (to allow for bigger key buffers than the display allows) according to position of edit pointer. + emuControl.keyInjEdit.curView = (emuControl.keyInjEdit.editptr / KEY_INJEDIT_NIBBLES_PER_ROW) > MAX_INJEDIT_ROWS-1 ? (emuControl.keyInjEdit.editptr / KEY_INJEDIT_NIBBLES_PER_ROW) - MAX_INJEDIT_ROWS +1 : 0; + + col = ((emuControl.keyInjEdit.editptr) % KEY_INJEDIT_NIBBLES_PER_ROW) + ((emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES) % MAX_INJEDIT_COLS); + row = emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES_PER_ROW > MAX_INJEDIT_ROWS-1 ? MAX_INJEDIT_ROWS-1 : emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES_PER_ROW; + + sprintf(lineBuf, "%01x", emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 0 ? (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0]&0xf0)>>4 : + emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 1 ? (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[0]&0x0f) : + emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 2 ? (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1]&0xf0)>>4 : + emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 3 ? (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[1]&0x0f) : + emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 4 ? (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2]&0xf0)>>4 : + emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 5 ? (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[2]&0x0f) : + emuControl.keyInjEdit.editptr % KEY_INJEDIT_NIBBLES == 6 ? (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3]&0xf0)>>4 : (emuControl.keyInjEdit.bufptr[emuControl.keyInjEdit.editptr/KEY_INJEDIT_NIBBLES].b[3]&0x0f) ); + + OSDSetCursorFlash(emuControl.keyInjEdit.startCol+col, emuControl.keyInjEdit.startRow+row, emuControl.keyInjEdit.offsetCol, emuControl.keyInjEdit.offsetRow, emuControl.keyInjEdit.font, lineBuf[0], emuControl.keyInjEdit.fg, emuControl.keyInjEdit.bg, emuControl.keyInjEdit.cursorAttr, emuControl.keyInjEdit.cursorFlashRate); + if(emuControl.keyInjEdit.render != NULL) + emuControl.keyInjEdit.render(emuControl.keyInjEdit.curView); + } + return; +} + +// Method to switch to a menu given its integer Id. +// +void EMZSwitchToMenu(int8_t menu) +{ + switch(menu) + { + case MENU_MAIN: + EMZMainMenu(); + break; + + case MENU_TAPE_STORAGE: + EMZTapeStorageMenu(ACTION_DEFAULT); + break; + + case MENU_FLOPPY_STORAGE: + EMZFloppyStorageMenu(ACTION_DEFAULT); + break; + + case MENU_MACHINE: + EMZMachineMenu(ACTION_DEFAULT); + break; + + case MENU_DISPLAY: + EMZDisplayMenu(ACTION_DEFAULT); + break; + + case MENU_AUDIO: + EMZAudioMenu(ACTION_DEFAULT); + break; + + case MENU_SYSTEM: + EMZSystemMenu(ACTION_DEFAULT); + break; + + case MENU_ROMMANAGEMENT: + EMZRomManagementMenu(ACTION_DEFAULT); + break; + + case MENU_AUTOSTART: + EMZAutoStartApplicationMenu(ACTION_DEFAULT); + break; + + default: + break; + } + return; +} + + +// Method to write out a complete file with the given name and data. +// +int EMZFileSave(const char *fileName, void *data, int size) +{ + // Locals. + // + FIL fileDesc; + FRESULT result; + unsigned int writeSize; + char saveName[MAX_FILENAME_LEN+1]; + + // If a relative path has been given we need to expand it into an absolute path. + if(fileName[0] != '/' && fileName[0] != '\\' && (fileName[0] < 0x30 || fileName[0] > 0x32)) + { + sprintf(saveName, "%s\%s", TOPLEVEL_DIR, fileName); + } else + { + strcpy(saveName, fileName); + } +printf("Save to File:%s,%s\n", saveName, fileName); + + // Attempt to open the file for writing. + result = f_open(&fileDesc, saveName, FA_CREATE_ALWAYS | FA_WRITE); + if(result) + { + debugf("EMZFileSave(open) File:%s, error: %d.\n", saveName, fileDesc); + } else + { + // Write out the complete buffer. + result = f_write(&fileDesc, data, size, &writeSize); +printf("Written:%d, result:%d\n", writeSize, result); + f_close(&fileDesc); + if(result) + { + debugf("FileSave(write) File:%s, error: %d.\n", saveName, result); + } + } + return(result); +} + +// Method to read into memory a complete file with the given name and data. +// +int EMZFileLoad(const char *fileName, void *data, int size) +{ + // Locals. + // + FIL fileDesc; + FRESULT result; + unsigned int readSize; + char loadName[MAX_FILENAME_LEN+1]; + + // If a relative path has been given we need to expand it into an absolute path. + if(fileName[0] != '/' && fileName[0] != '\\' && (fileName[0] < 0x30 || fileName[0] > 0x32)) + { + sprintf(loadName, "%s\%s", TOPLEVEL_DIR, fileName); + } else + { + strcpy(loadName, fileName); + } + + // Attempt to open the file for reading. + result = f_open(&fileDesc, loadName, FA_OPEN_EXISTING | FA_READ); + if(result) + { + debugf("EMZFileLoad(open) File:%s, error: %d.\n", loadName, fileDesc); + } else + { + // Read in the complete buffer. + result = f_read(&fileDesc, data, size, &readSize); + f_close(&fileDesc); + if(result) + { + debugf("FileLoad(read) File:%s, error: %d.\n", loadName, result); + } + } + + return(result); +} + +// Method to load the stored configuration, update the hardware and refresh the menu to reflect changes. +// +void EMZReadConfig(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + // Load config, if the load fails then we remain with the current config. + // + EMZLoadConfig(); + + // Setup the hardware with the config values. + EMZSwitchToMachine(emuConfig.machineModel, 0); + + // Recreate the menu with the new config values. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + return; +} + +// Method to write the stored configuration onto the SD card persistence. +// +void EMZWriteConfig(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + // Load config, if the load fails then we remain with the current config. + // + EMZSaveConfig(); + + // Refresh menu to update cursor. + EMZRefreshMenu(); + } + return; +} + +// Method to reset the configuration. This is achieved by copying the power on values into the working set. +// +void EMZResetConfig(enum ACTIONMODE mode) +{ + if(mode == ACTION_TOGGLECHOICE) + { + } else + if(mode == ACTION_DEFAULT || mode == ACTION_SELECT) + { + // Restore the reset parameters into the working set. + memcpy(emuConfig.params, emuControl.hostMachine == HW_MZ2000 ? emuConfigDefault_MZ2000.params : emuControl.hostMachine == HW_MZ80A ? emuConfigDefault_MZ80A.params : emuConfigDefault_MZ700.params, sizeof(emuConfig.params)); + for(uint16_t idx=0; idx < MAX_MZMACHINES; idx++) { for(uint16_t idx2=0; idx2 < MAX_KEY_INS_BUFFER; idx2++) { if(emuConfig.params[idx].loadApp.preKeyInsertion[idx2].i == 0) { emuConfig.params[idx].loadApp.preKeyInsertion[idx2].i = 0xffffffff; } } } + for(uint16_t idx=0; idx < MAX_MZMACHINES; idx++) { for(uint16_t idx2=0; idx2 < MAX_KEY_INS_BUFFER; idx2++) { if(emuConfig.params[idx].loadApp.postKeyInsertion[idx2].i == 0) { emuConfig.params[idx].loadApp.postKeyInsertion[idx2].i = 0xffffffff; } } } + + // Setup the hardware with the config values. + EMZSwitchToMachine(emuConfig.machineModel, 0); + + // Recreate the menu with the new config values. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + return; +} + + +// Method to read in from disk the persisted configuration. +// +void EMZLoadConfig(void) +{ + if(EMZFileLoad(CONFIG_FILENAME, &emuConfig.params, sizeof(emuConfig.params))) + { + debugf("EMZLoadConfig error reading: %s.\n", CONFIG_FILENAME); + } +} + +// Method to save the current configuration onto disk for persistence. +// +void EMZSaveConfig(void) +{ + if(EMZFileSave(CONFIG_FILENAME, emuConfig.params, sizeof(emuConfig.params))) + { + debugf("EMZSaveConfig error writing: %s.\n", CONFIG_FILENAME); + } +} + +// Method to switch and configure the emulator according to the values input by the user OSD interaction. +// +void EMZSwitchToMachine(uint8_t machineModel, uint8_t forceROMLoad) +{ + // Locals. + // + uint8_t result = 0; + + // Suspend the T80. + writeZ80IO(IO_TZ_CPUCFG, CPUMODE_SET_EMU_MZ, TRANZPUTER); + +printf("Machine model:%d, old:%d, change:%d, force:%d, memory:%d\n", machineModel, emuConfig.machineModel, emuConfig.machineChanged, forceROMLoad, emuConfig.params[machineModel].memSize); + // Go through the active configuration, convert to register values and upload the register values into the FPGA. + // + //emuConfig.emuRegisters[MZ_EMU_REG_MODEL] = (emuConfig.emuRegisters[MZ_EMU_REG_MODEL] & 0xF0) | emuConfig.params[machineModel].vramMode << 4 | machineModel; + emuConfig.emuRegisters[MZ_EMU_REG_MODEL] = (EMZGetMemSizeValue() << 4) | (machineModel&0x0f); + +printf("DisplayType:%02x, VRAM:%d, GRAM:%d, WAIT:%d, PCG:%d\n", emuConfig.params[machineModel].displayType, emuConfig.params[machineModel].vramMode, emuConfig.params[machineModel].gramMode, emuConfig.params[machineModel].vramWaitMode, emuConfig.params[machineModel].pcgMode); + emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY] = emuConfig.params[machineModel].pcgMode << 7 | + emuConfig.params[machineModel].vramWaitMode << 6 | + emuConfig.params[machineModel].gramMode << 5 | + emuConfig.params[machineModel].vramMode << 4 | + (emuConfig.params[machineModel].displayType & 0x0f); + + // Display register 2 configures the output type and run time options such as Wait states. +printf("DisplayOutput:%02x,%02x\n", emuConfig.params[machineModel].displayOutput, emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY2]); + emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY2] = (emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY2] & 0xF0) | emuConfig.params[machineModel].displayOutput; + + // Display register 3 configures the graphics options such as GRAM/PCG. + emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY3] = EMZGetDisplayOptionValue(); + + emuConfig.emuRegisters[MZ_EMU_REG_CPU] = (emuConfig.emuRegisters[MZ_EMU_REG_CPU] & 0xF8) | emuConfig.params[machineModel].cpuSpeed; + emuConfig.emuRegisters[MZ_EMU_REG_AUDIO] = emuConfig.params[machineModel].audioHardware << 7 | emuConfig.params[machineModel].audioMix << 5 | (emuConfig.params[machineModel].audioMute == 1 ? 0 : emuConfig.params[machineModel].audioVolume << 1) | emuConfig.params[machineModel].audioSource; + emuConfig.emuRegisters[MZ_EMU_REG_CMT] = emuConfig.params[machineModel].cmtMode << 7 | + (emuConfig.params[machineModel].cmtAsciiMapping & 0x03) << 5 | + emuConfig.params[machineModel].tapeButtons << 3 | + (emuConfig.params[machineModel].fastTapeLoad & 0x07); + // No current changes to CMT register 2, just a place holder until needed. + emuConfig.emuRegisters[MZ_EMU_REG_CMT2] = emuConfig.emuRegisters[MZ_EMU_REG_CMT2]; + + // Floppy disk configuration register. + emuConfig.emuRegisters[MZ_EMU_REG_FDD] = emuConfig.params[machineModel].fdd[3].mounted << 7 | emuConfig.params[machineModel].fdd[2].mounted << 6 | emuConfig.params[machineModel].fdd[1].mounted << 5 | emuConfig.params[machineModel].fdd[0].mounted << 4 | emuConfig.params[machineModel].fddEnabled; + emuConfig.emuRegisters[MZ_EMU_REG_FDD2] = emuConfig.params[machineModel].fdd[3].updateMode << 7 | emuConfig.params[machineModel].fdd[3].polarity << 6 | + emuConfig.params[machineModel].fdd[2].updateMode << 5 | emuConfig.params[machineModel].fdd[2].polarity << 4 | + emuConfig.params[machineModel].fdd[1].updateMode << 3 | emuConfig.params[machineModel].fdd[1].polarity << 2 | + emuConfig.params[machineModel].fdd[0].updateMode << 1 | emuConfig.params[machineModel].fdd[0].polarity; + + // Option ROMS configuration register. + emuConfig.emuRegisters[MZ_EMU_REG_ROMS] = emuConfig.params[machineModel].romFDC.romEnabled << 1 | emuConfig.params[machineModel].romUser.romEnabled; + + // Setup the hardware switches. + if(machineModel == MZ800) + { + emuConfig.emuRegisters[MZ_EMU_REG_SWITCHES] = 0x0 << 4 | emuConfig.params[machineModel].mz800TapeIn << 3 | emuConfig.params[machineModel].mz800Printer << 2 | emuConfig.params[machineModel].mz800Printer << 1 | emuConfig.params[machineModel].mz800Mode; + } else + { + emuConfig.emuRegisters[MZ_EMU_REG_SWITCHES] = 0x00; + } + + // Set the model and group according to parameter provided. + emuConfig.machineModel = machineModel; + emuConfig.machineGroup = EMZGetMachineGroup(); + + // Based on the machine, upload the required ROMS into the emulator. Although the logic is the same for many machines it is seperated below in case specific implementations/configurations are needed. + // + if(emuConfig.machineChanged || forceROMLoad) + { +printf("%s load\n", MZMACHINES[machineModel]); + if(emuConfig.params[machineModel].romMonitor40.romEnabled == 1 && strlen((char *)emuConfig.params[machineModel].romMonitor40.romFileName) > 0 && (emuConfig.params[machineModel].displayType == MZ_EMU_DISPLAY_MONO || emuConfig.params[machineModel].displayType == MZ_EMU_DISPLAY_COLOUR)) + result |= loadZ80Memory((char *)emuConfig.params[machineModel].romMonitor40.romFileName, 0, emuConfig.params[machineModel].romMonitor40.loadAddr, emuConfig.params[machineModel].romMonitor40.loadSize, 0, FPGA, 1); + if(emuConfig.params[machineModel].romMonitor80.romEnabled == 1 && strlen((char *)emuConfig.params[machineModel].romMonitor80.romFileName) > 0 && (emuConfig.params[machineModel].displayType == MZ_EMU_DISPLAY_MONO80 || emuConfig.params[machineModel].displayType == MZ_EMU_DISPLAY_COLOUR80)) + result |= loadZ80Memory((char *)emuConfig.params[machineModel].romMonitor80.romFileName, 0, emuConfig.params[machineModel].romMonitor80.loadAddr, emuConfig.params[machineModel].romMonitor80.loadSize, 0, FPGA, 1); + if(emuConfig.params[machineModel].romCG.romEnabled == 1 && strlen((char *)emuConfig.params[machineModel].romCG.romFileName) > 0) + result |= loadZ80Memory((char *)emuConfig.params[machineModel].romCG.romFileName, 0, emuConfig.params[machineModel].romCG.loadAddr, emuConfig.params[machineModel].romCG.loadSize, 0, FPGA, 1); + if(emuConfig.params[machineModel].romKeyMap.romEnabled == 1 && strlen((char *)emuConfig.params[machineModel].romKeyMap.romFileName) > 0) + result |= loadZ80Memory((char *)emuConfig.params[machineModel].romKeyMap.romFileName, 0, emuConfig.params[machineModel].romKeyMap.loadAddr, emuConfig.params[machineModel].romKeyMap.loadSize, 0, FPGA, 1); + if(machineModel == MZ80A && emuConfig.params[machineModel].romUser.romEnabled == 1 && strlen((char *)emuConfig.params[machineModel].romUser.romFileName) > 0) + result |= loadZ80Memory((char *)emuConfig.params[machineModel].romUser.romFileName, 0, emuConfig.params[machineModel].romUser.loadAddr, emuConfig.params[machineModel].romUser.loadSize, 0, FPGA, 1); + if(emuConfig.params[machineModel].romFDC.romEnabled == 1 && strlen((char *)emuConfig.params[machineModel].romFDC.romFileName) > 0) + result |= loadZ80Memory((char *)emuConfig.params[machineModel].romFDC.romFileName, 0, emuConfig.params[machineModel].romFDC.loadAddr, emuConfig.params[machineModel].romFDC.loadSize, 0, FPGA, 1); + if(result) + { + printf("Error: Failed to load a ROM into the Sharp MZ Series Emulation ROM memory.\n"); + } + + // As the machine changed force a machine reset. + // + emuConfig.emuRegisters[MZ_EMU_REG_CTRL] |= 0x01; + + // Next invocation we wont load the roms unless machine changes again. + emuConfig.machineChanged = 0; + + // Write the updated registers into the emulator to configure it. + writeZ80Array(MZ_EMU_ADDR_REG_MODEL, emuConfig.emuRegisters, MZ_EMU_MAX_REGISTERS, FPGA); + + // Clear any reset command in the internal register copy, the hardware automatically clears the reset flag. + emuConfig.emuRegisters[MZ_EMU_REG_CTRL] &= 0xFE; + + // Clear the graphics and text screen - this is needed if switching between machines as some machines are not graphic aware and others clear text areas + // differently. This ensures a machine switch will be properly initialised. + fillZ80Memory(MZ_EMU_RED_FB_ADDR, MAX_FB_LEN, 0x00, FPGA); + fillZ80Memory(MZ_EMU_BLUE_FB_ADDR, MAX_FB_LEN, 0x00, FPGA); + fillZ80Memory(MZ_EMU_GREEN_FB_ADDR, MAX_FB_LEN, 0x00, FPGA); + fillZ80Memory(MZ_EMU_TEXT_VRAM_ADDR, MAX_TEXT_VRAM_LEN, 0x00, FPGA); + fillZ80Memory(MZ_EMU_ATTR_VRAM_ADDR, MAX_ATTR_VRAM_LEN, 0x71, FPGA); + } else + { + // Write the updated registers into the emulator to configure it. + writeZ80Array(MZ_EMU_ADDR_REG_MODEL, emuConfig.emuRegisters, MZ_EMU_MAX_REGISTERS, FPGA); + } + + printf("WriteReg: "); for(uint8_t idx=0; idx < 16; idx++) { printf("%02x,", emuConfig.emuRegisters[idx]); } printf("\n"); + readZ80Array(MZ_EMU_ADDR_REG_MODEL, emuConfig.emuRegisters, MZ_EMU_MAX_REGISTERS, FPGA); + printf("ReadReg: "); for(uint8_t idx=0; idx < 16; idx++) { printf("%02x,", emuConfig.emuRegisters[idx]); } printf("\n"); + + // If the floppy module is enabled, set READY so that the disk controller can process commands. + // + if(emuConfig.params[machineModel].fddEnabled) + { + emuControl.fdd.ctrlReg |= FDD_CTRL_READY; + writeZ80Array(MZ_EMU_FDD_CTRL_ADDR+MZ_EMU_FDD_CTRL_REG, &emuControl.fdd.ctrlReg, 1, FPGA); + + // Update disk loaded state (ie. close or flush any ejected disks). + EMZProcessFDDRequest(0, 0, 0, 0, 0, 0); + } + + // Reenable the T80 as it needs to be running prior to reset. + writeZ80IO(IO_TZ_CPUCFG, CPUMODE_CLK_EN | CPUMODE_SET_EMU_MZ, TRANZPUTER); + return; +} + +// Method to handle and process the tape unit, loading files as requested from the queue, saving recorded files to disk and implementing the APSS +// tape drive mechanisms. +// This method is called by interrupt (cmt status change) and periodically (process tape queue). +// +void EMZProcessTapeQueue(uint8_t force) +{ + // Locals. + static unsigned long time = 0; + uint32_t timeElapsed; + char *fileName; + + // Get elapsed time since last service poll. + timeElapsed = *ms - time; + + // If the elapsed time is too short, ie. because a manual call and a scheduled call occur almost simultaneously, exit as it is not needed. + if(timeElapsed < 1000 && !force) + return; + + // MZ80B APSS functionality. + // + if(emuConfig.machineGroup == GROUP_MZ80B) + { + // If Eject set, clear queue then issue CMT Register Clear. + // + if(emuConfig.emuRegisters[MZ_EMU_REG_CMT2] & MZ_EMU_CMT2_EJECT ) + { + debugf("APSS Eject Cassette (%02x:%02x).", emuConfig.emuRegisters[MZ_EMU_REG_CMT2], MZ_EMU_CMT2_EJECT); + EMZClearTapeQueue(); + } else + + // If APSS set, rotate queue forward (DIRECTION = 1) or backward (DIRECTION = 0). + // + if(emuConfig.emuRegisters[MZ_EMU_REG_CMT2] & MZ_EMU_CMT2_APSS ) + { + debugf("APSS Search %s (%02x:%02x).", emuConfig.emuRegisters[MZ_EMU_REG_CMT2] & MZ_EMU_CMT2_DIRECTION ? "Forward" : "Reverse", emuConfig.emuRegisters[MZ_EMU_REG_CMT2], MZ_EMU_CMT2_APSS ); + EMZTapeQueueAPSSSearch(emuConfig.emuRegisters[MZ_EMU_REG_CMT2] & MZ_EMU_CMT2_DIRECTION ? 1 : 0, 1); + } + + // If Play is active, the cache is empty and we are not recording, load into cache the next tape image. + // + if((emuConfig.emuRegisters[MZ_EMU_REG_CMT2] & MZ_EMU_CMT2_PLAY) && !(emuConfig.emuRegisters[MZ_EMU_REG_CMT3] & MZ_EMU_CMT_PLAY_READY) && !(emuConfig.emuRegisters[MZ_EMU_REG_CMT3] & MZ_EMU_CMT_RECORDING) ) + { + // Check the tape queue, if items available, read oldest,upload and rotate. + // + if(emuControl.tapeQueue.elements > 0) + { + // Get the filename from the queue. On the MZ2000 we dont adjust the tape position as the logic will issue an APSS as required. + if(emuConfig.machineModel == MZ80B) + { + fileName = EMZTapeQueueAPSSSearch(1, 1); + } else + { + fileName = EMZTapeQueueAPSSSearch(1, 0); + } + + // If a file was found, upload it into the CMT buffer. + if(fileName != 0) + { + debugf("APSS Play, loading tape: %s\n", fileName); + EMZLoadTapeToRAM(fileName, 1); + + // Need to redraw the menu as the tape queue may have changed. + if(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] == MENU_TAPE_STORAGE) + { + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + } + } + } + } else + + // Standard Tape Deck functionality. + { + // Check to see if the Tape READY signal is inactive, if it is and we have items in the queue, load up the next + // tape file and send it. + // + if((emuConfig.emuRegisters[MZ_EMU_REG_CMT3] & MZ_EMU_CMT_SENSE) && !(emuConfig.emuRegisters[MZ_EMU_REG_CMT3] & MZ_EMU_CMT_PLAY_READY)) + { + // Check the tape queue, if items available, pop oldest and upload. + // + if(emuControl.tapeQueue.elements > 0) + { + // Get the filename from the queue. + fileName = EMZTapeQueuePopFile(1); + + // If a file was found, upload it into the CMT buffer. + if(fileName != 0) + { + debugf("Loading tape: %s\n", fileName); + EMZLoadTapeToRAM(fileName, 1); + + // Need to redraw the menu as the tape queue has changed. + EMZSwitchToMenu(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx]); + } + } + } + } + + // Check to see if the RECORD_READY flag is set. + if( (emuConfig.emuRegisters[MZ_EMU_REG_CMT3] & MZ_EMU_CMT_RECORD_READY) ) + { + EMZSaveTapeFromCMT((const char *)0); + } + + // Reset the timer. + time = *ms; +} + +// Method to check a given file image to ensure it is valid. This method is normally called when the file is +// selected to save time during IRQ processing. +// +short EMZCheckFDDImage(char *fileName) +{ + // Locals. + FIL fileDesc; + FRESULT result; + uint8_t imgType; + uint8_t tmpBuf[35]; + uint32_t actualReadSize; + + // Check extension. + // + const char *ext = strrchr(fileName, '.'); + + // Extended CPC Disk Image. + if(strcasecmp(++ext, "DSK") == 0) + { + imgType = IMAGETYPE_EDSK; + } else + // Raw unformatted binary image. Sector and track defined by user are used to locate FDC requested sector. + if(strcasecmp(ext, "IMG") == 0) + { + imgType = IMAGETYPE_IMG; + } else + { + debugf("Image:%s has no handler.", fileName); + return(-1); + } + + // Open the image. + result = f_open(&fileDesc, fileName, FA_OPEN_EXISTING | FA_READ); + if(result) + { + debugf("Image cannot be opened:%d,%s", imgType, fileName); + return(-1); + } + + // Check to see if this is a valid EDSK image. + if(imgType == IMAGETYPE_EDSK) + { + result = f_read(&fileDesc, tmpBuf, 34, &actualReadSize); + if(result) + { + debugf("Cannot read image description block:%d,%s", imgType, fileName); + f_close(&fileDesc); + return(-1); + } + + // Terminate description block header and compare to validate it is an Extended CPC Disk. + tmpBuf[34] = 0x00; + if(strcmp((char*)tmpBuf, "EXTENDED CPC DSK File\r\nDisk-Info\r\n") != 0) + { + debugf("Disk image (%s) is not a valid EDSK file.", fileName); + f_close(&fileDesc); + return(-1); + } + } else + // Not much you can check on a binary image! + if(imgType == IMAGETYPE_IMG) + { + ; + } else + { + f_close(&fileDesc); + return(-1); + } + + // Close image to exit. + f_close(&fileDesc); + + // All checks out, return image type as success flag to caller. + // + return(imgType); +} + +// A method to retrieve the floppy definitions from the image and if valid, store in the configuration. +short EMZSetFDDImageParams(char *fileName, uint8_t driveNo, enum IMAGETYPES imgType) +{ + // Locals. + // + FIL fileDesc; + FRESULT result; + uint8_t tmpBuf[35]; + uint8_t idx; + uint8_t noTracks; + uint8_t noSides; + uint8_t noSectors; + uint16_t sectorSize; + uint32_t offset; + uint32_t actualReadSize; + + // Open the image. + result = f_open(&fileDesc, fileName, FA_OPEN_EXISTING | FA_READ); + if(result) + { + debugf("Image cannot be opened:%d,%s", imgType, fileName); + return(-1); + } + + // Check to see if this is a valid EDSK image. + if(imgType == IMAGETYPE_EDSK) + { + // result = f_read(&fileDesc, tmpBuf, 34, &actualReadSize); + // if(result) + // { + // debugf("Cannot read image description block:%d,%s", fileName); + // f_close(&fileDesc); + // return(-1); + // } + + result = f_lseek(&fileDesc, 0x30); + if(!result) + result = f_read(&fileDesc, &tmpBuf, 8, &actualReadSize); + if(result) + { + debugf("Failed to obtain Track/Side info:%s", fileName); + f_close(&fileDesc); + return(-1); + + } + noTracks = tmpBuf[0]; + noSides = tmpBuf[1]; + + offset = 0x100; // Disk Information Block size. + // Go to track 1 and retrieve the sector size. + // The offset is built up by the track entries x 100H added together. The size of tracks can vary hence having to calculate in a loop. + for(idx = 0; idx < (1 * noSides); idx++) + { + result = f_lseek(&fileDesc, offset + 0x14); + if(!result) + result = f_read(&fileDesc, &tmpBuf[10], 2, &actualReadSize); + if(actualReadSize != 2) + { + debugf("Failed to traverse track structure:%s", fileName); + f_close(&fileDesc); + return(-1); + } + if(tmpBuf == 0x00) + { + debugf("Track doesnt exist (%d), bad image:%s", idx/noSides, fileName); + f_close(&fileDesc); + return(-1); + } + + // Get sector size. + sectorSize = tmpBuf[10] == 0x00 ? 128 : tmpBuf[10] == 0x01 ? 256 : tmpBuf[10] == 0x02 ? 512 : 1024; +printf("Sector Size:%d,%02x:%02x\n", sectorSize, tmpBuf[10], tmpBuf[4+idx]); + // Bug on HD images, track reports 0x25 sectors but this is only applicable for the first track. + if(idx > 0 && tmpBuf[4+idx] == 0x25) tmpBuf[4+idx] = 0x11; + + // Position 0 = number of tracks x sides -> Should be constant but it isnt! + offset += tmpBuf[4+idx] * 0x100; //sectorSize; +printf("Loop Offset:%08lx\n", offset); + } +printf("Offset:%08lx\n", offset); + // Go to the first sector information list on track 1 to retrieve sector size. + result = f_lseek(&fileDesc, offset+0x10); + if(!result) + result = f_read(&fileDesc, &tmpBuf, 6, &actualReadSize); + if(result) + { + debugf("Failed to read track 1 info:%s", fileName); + f_close(&fileDesc); + return(-1); + } + noSectors = tmpBuf[5] == 0x25 ? 0x11 : tmpBuf[5]; + sectorSize = tmpBuf[4] == 0x00 ? 128 : tmpBuf[4] == 0x01 ? 256 : tmpBuf[4] == 0x02 ? 512 : 1024; +printf("%dT %dH %dS\n", noTracks, noSides, noSectors); +printf("SectorSize:%08lx,%02x\n", sectorSize, tmpBuf[4]); + + // A lot of conversions on the internet are not accurate, for example a 40T 2H 16S 256B image can be encapsulated in a DSK image with 41T 2H 17S 256B, so need to cater for these images here, mapping + // to a known image definition. This list needs to be updated as more unusual images are encountered or alternatively use samDisk or HxC 2000 to convert them correctly. + if(noTracks > 40 && noTracks < 42) noTracks = 0x28; + if(noTracks > 80 && noTracks < 82) noTracks = 0x50; + if(noSectors > 16 && noSectors <= 18 && sectorSize == 256) noSectors = 16; + + // Now match what we have read with known disks. + debugf("EDISK File(%s) has format:%dT, %dH, %dS, %dB", fileName, noTracks, noSides, noSectors, sectorSize); + for(idx= 0; idx < NUMELEM(FLOPPY_DEFINITIONS); idx++) + { + if(FLOPPY_DEFINITIONS[idx].tracks == noTracks && FLOPPY_DEFINITIONS[idx].heads == noSides && FLOPPY_DEFINITIONS[idx].sectors == noSectors && FLOPPY_DEFINITIONS[idx].sectorSize == sectorSize) + break; + } + if(idx == NUMELEM(FLOPPY_DEFINITIONS)) + { + debugf("Couldnt match image definition to known floppy definition: %dT %dH %dS %dB:%s", noTracks, noSides, noSectors, sectorSize, fileName); + f_close(&fileDesc); + return(-1); + } + + // Save type to configuration list. + emuConfig.params[emuConfig.machineModel].fdd[driveNo].diskType = idx; + } else + // Not much you can set on a binary image - apart from filename, the disk type must be set manually as it cannot be determined. + if(imgType == IMAGETYPE_IMG) + { + ; + } else + { + f_close(&fileDesc); + return(-1); + } + + // Set parameters on success. + strcpy(emuConfig.params[emuConfig.machineModel].fdd[driveNo].fileName, fileName); + + // Close image to exit. + f_close(&fileDesc); + + // Success. + return(0); +} + +// Method to process an interrupt request from the Floppy Disk Drive unit, generally raised by the WD1793 controller. +// +enum FLOPPYERRORCODES EMZProcessFDDRequest(uint8_t ctrlReg, uint8_t trackNo, uint8_t sectorNo, uint8_t fdcReg, uint16_t *sectorSize, uint16_t *rotationalSpeed) +{ + // Locals. + // + static FIL fileDesc[MZ_EMU_FDD_MAX_DISKS]; + static uint8_t opened; + static uint8_t dirty; + static uint8_t lastTrack[MZ_EMU_FDD_MAX_DISKS] = {0xff, 0xff, 0xff, 0xff}; + static uint8_t lastSide[MZ_EMU_FDD_MAX_DISKS] = {0xff, 0xff, 0xff, 0xff}; + static uint32_t trackOffset[MZ_EMU_FDD_MAX_DISKS] = {0, 0, 0, 0}; + static uint32_t trackLen[MZ_EMU_FDD_MAX_DISKS] = {0, 0, 0, 0}; + static uint8_t sectorCount[MZ_EMU_FDD_MAX_DISKS]; + uint8_t driveNo = ((ctrlReg & FDD_IOP_DISK_SELECT_NO) >> 5) & 0x03; + uint8_t noSides = FLOPPY_DEFINITIONS[emuConfig.params[emuConfig.machineModel].fdd[driveNo].diskType].heads; + uint8_t side = ctrlReg & FDD_IOP_SIDE ? 1 : 0; + uint8_t sectorsPerTrack = FLOPPY_DEFINITIONS[emuConfig.params[emuConfig.machineModel].fdd[driveNo].diskType].sectors; + uint8_t cmd = (ctrlReg & FDD_IOP_SERVICE_REQ) == 0 ? FDD_IOP_REQ_NOP : ctrlReg & FDD_IOP_REQ_MODE; + uint8_t sectorBuffer[1024]; + uint8_t idx; + // uint16_t sectorSize = FLOPPY_DEFINITIONS[emuConfig.params[emuConfig.machineModel].fdd[driveNo].diskType].sectorSize; + uint32_t actualReadSize; + uint32_t sectorOffset; + uint32_t thisSectorSize = FLOPPY_DEFINITIONS[emuConfig.params[emuConfig.machineModel].fdd[driveNo].diskType].sectorSize; + FRESULT result; +printf("Drive No:%d, %02x, %d\n", driveNo, ctrlReg & FDD_IOP_SERVICE_REQ, emuConfig.params[emuConfig.machineModel].fdd[driveNo].mounted); + // If this is a valid service request, process. + // + if(cmd != FDD_IOP_REQ_NOP) + { + // Is the disk mounted? Cannot process if no disk! + // + if(emuConfig.params[emuConfig.machineModel].fdd[driveNo].mounted) + { + // If the disk hasnt yet been opened, open to save time on next sector requests. + if(!((opened >> driveNo) & 0x01)) + { +printf("Opening disk:%s,%d\n", emuConfig.params[emuConfig.machineModel].fdd[driveNo].fileName, driveNo); + result = f_open(&fileDesc[driveNo], emuConfig.params[emuConfig.machineModel].fdd[driveNo].fileName, FA_OPEN_EXISTING | FA_READ); + if(result) + { + debugf("[open] File:%s, error: %d.\n", emuConfig.params[emuConfig.machineModel].fdd[driveNo].fileName, fileDesc[driveNo]); + return(FLPYERR_DISK_ERROR); + } + // Mark drive as being opened. + opened |= 1 << driveNo; + } + + // Locate sector according to image type. + if(emuConfig.params[emuConfig.machineModel].fdd[driveNo].imgType == IMAGETYPE_EDSK) + { + // If track or side has changed (or first call as lastTrack = 255), calculate the track offset position. + // This is necessary as the track length can vary from track to track, side to side. + if(trackNo != lastTrack[driveNo] || side != lastSide[driveNo]) + { + // Seek to the track information block, get initial sector count to calculate the track length, then back to the start to iterate tracks. + result = f_lseek(&fileDesc[driveNo], 0x34); + // if(!result) result = f_read(&fileDesc, §orBuffer, 2, &actualReadSize); + // if(!result) result = f_lseek(&fileDesc[driveNo], 0x34); + if(result) + { + debugf("Failed to seek to start of TIB:%d, sector:%d, drive:%d", trackNo, sectorNo, driveNo); + return(FLPYERR_TRACK_NOT_FOUND); + } + + // Go to track and retrieve the sector information. + trackLen[driveNo] = 0x0100; + trackOffset[driveNo] = 0x00000000; + for(idx = 0; idx == 0 || idx <= (trackNo * noSides)+side; idx++) + { + result = f_read(&fileDesc[driveNo], §orBuffer, 1, &actualReadSize); + if(actualReadSize != 1) + { + debugf("Failed to traverse track structure:%d", trackNo); + return(FLPYERR_TRACK_NOT_FOUND); + } + + // Bug on HD images, track reports 0x25 sectors but this is only applicable for the first track. + if(idx > 0 && sectorBuffer[0] == 0x25) sectorBuffer[0] = 0x11; + + // if(idx == 0) + // { + // // Disk Information Block size. + // trackOffset[driveNo] = 0x100; + // } else + // { + // trackOffset[driveNo] += sectorBuffer[0] * 0x100; + // } + trackOffset[driveNo] += trackLen[driveNo]; + trackLen[driveNo] = (uint32_t)sectorBuffer[0] * 0x100; +//printf("idx=%d,%08lx:%02x:%08lx,\n", idx,trackOffset[driveNo], sectorBuffer[0], trackLen[driveNo]); + } + // Get next track length for Side 1 offset addition. + // result = f_read(&fileDesc[driveNo], §orBuffer, 1, &actualReadSize); + // if(result) + // { + // debugf("Failed to seek to start of TIB:%d, sector:%d, drive:%d", trackNo, sectorNo, driveNo); + // return(FLPYERR_TRACK_NOT_FOUND); + // } + // trackLen[driveNo] = (uint32_t)sectorBuffer[0] * 0x100; + // Check to see if the track exists, some EDSK files have missing tracks and a request for one is an error. + // + if(sectorBuffer[0] == 0x00) + { + debugf("Track doesnt exist (%d,%d), bad image:%s", side, trackNo, emuConfig.params[emuConfig.machineModel].fdd[driveNo].fileName); + return(FLPYERR_TRACK_NOT_FOUND); + } + uint8_t sectorCountFromTIB = sectorBuffer[0]; + + // Read the sector count for this track, + result = f_lseek(&fileDesc[driveNo], trackOffset[driveNo] + 0x14); + if(!result) + result = f_read(&fileDesc[driveNo], §orBuffer, 2, &actualReadSize); + if(result) + { + debugf("Failed to seek to sector count in TIB:%d, sector:%d, trackOffset:%04x, drive:%d", trackNo, sectorNo, trackOffset[driveNo], driveNo); + return(FLPYERR_TRACK_NOT_FOUND); + } + // There are some wierd formats available, some report one set of sectors in the TIB which differes from the SIB and the TIB is right yet others + // report sectors in the TIB which differs from the SIB and the SIB is right! Work around, choose the maximum sector as the loop below will either find it or error out. + sectorCount[driveNo] = (uint8_t)sectorCountFromTIB > (uint8_t)sectorBuffer[1] ? (uint8_t)sectorCountFromTIB : (uint8_t)sectorBuffer[1]; +printf("trackLen=%08lx, trackOffset=%08lx, sectorCount=%d, %02x,%02x\n", trackLen[driveNo], trackOffset[driveNo], sectorCount[driveNo], sectorBuffer[0], sectorBuffer[1]); + thisSectorSize = sectorBuffer[0] == 0x00 ? 128 : sectorBuffer[0] == 0x01 ? 256 : sectorBuffer[0] == 0x02 ? 512 : 1024; +printf("%02x,%02x trackOffset:%08lx, side:%d, thisSectorSize:%d\n", sectorBuffer[0], sectorBuffer[1], trackOffset[driveNo], side, thisSectorSize); +printf("trackLen:%08lx\n", trackLen[driveNo]); + + // Store track and side so this section is skipped if we remain on a track and side. + lastTrack[driveNo] = trackNo; + lastSide[driveNo] = side; + } + + // // Check sector requested against sector available, error if request is greater. + // // + // if(sectorNo > sectorCount) + // { + // debugf("Requested sector (%d) greater than track max sector (%d), drive:%d", sectorNo, sectorCount, driveNo); + // return(FLPYERR_SECTOR_NOT_FOUND); + // } + + // Traverse the sector list to find the required sector. + sectorOffset = trackOffset[driveNo]; // + (side ? trackLen[driveNo] : 0); + uint32_t offsetLimit = sectorOffset + trackLen[driveNo]; + + // Seek to the Sector Information List for this track. + result = f_lseek(&fileDesc[driveNo], sectorOffset + 0x18); //trackOffset[driveNo] + (side ? trackLen[driveNo] : 0) + 0x18); + + // Loop through the SIL looking for a sector/head match. Constrain the search to a maximum track length of data. This check is in place + // because of the loose usage by host software of the 'sector' number and sector count which differs in the TIB/SIB. + //sectorOffset += 0x0100; + for(idx = 1; idx <= sectorCount[driveNo] && sectorOffset < offsetLimit; idx++) + { + if(!result) result = f_read(&fileDesc[driveNo], §orBuffer, 8, &actualReadSize); + if(result) + { + debugf("Failed to seek and read the Sector Information List for track:%d, sector:%d", trackNo, sectorNo); + return(FLPYERR_SECTOR_NOT_FOUND); + } +//printf("%d:%d:%d,%02x,%02x,%08lx\n", idx, sectorNo, side, sectorBuffer[2], sectorBuffer[1], sectorOffset); + + thisSectorSize = sectorBuffer[3] == 0x00 ? 128 : sectorBuffer[3] == 0x01 ? 256 : sectorBuffer[3] == 0x02 ? 512 : 1024; + if(sectorBuffer[2] == sectorNo) + { + // // Check the sector size for mismatch - if any found then this code needs to be updated to change the controller size parameter dynamically. + // if(thisSectorSize != sectorSize) + // { + // debugf("Sector size mismatch, Track:%d, Sector:%d, DefSize:%d, ReadSize:%d", trackNo, sectorNo, sectorSize, thisSectorSize); + // return(FLPYERR_SECTOR_NOT_FOUND); + // } + // sectorOffset += 0x100; //thisSectorSize; + break; + } + sectorOffset += thisSectorSize; + } + // Add in the offset to track data, which is 256 bytes from the Track Information Block. + sectorOffset += 0x100; +printf("Offset End:%08lx,idx=%d,sectorCount=%d\n", sectorOffset, idx, sectorCount[driveNo]+1); + // Not found? + if(idx == sectorCount[driveNo]+1 || sectorOffset >= offsetLimit) + { + debugf("Sector not found, Track:%d, Sector:%d", trackNo, sectorNo); + return(FLPYERR_SECTOR_NOT_FOUND); + } + } else + + if(emuConfig.params[emuConfig.machineModel].fdd[driveNo].imgType == IMAGETYPE_IMG) + { + // Calculate block based on disk configuration. + // + sectorOffset = (sectorsPerTrack * (sectorNo-1) + (trackNo * sectorsPerTrack) + (ctrlReg & FDD_IOP_SIDE ? sectorsPerTrack : 0)) * thisSectorSize; + printf("SectorsPerTrack=%d, Offset=%d\n", sectorsPerTrack, sectorOffset); + } else + { + debugf("Unrecognised disk image type:%d", emuConfig.params[emuConfig.machineModel].fdd[driveNo].imgType); + return(FLPYERR_DISK_ERROR); + } + + // Sector details determined, sectorOffset points to the exact disk location where the required sector is stored. Complete the command according to request, ie. read/write or info. + // + switch(cmd) + { + case FDD_IOP_REQ_READ: + // Get the sector. + result = f_lseek(&fileDesc[driveNo], sectorOffset); + if(!result) + result = f_read(&fileDesc[driveNo], §orBuffer, thisSectorSize, &actualReadSize); + if(result) + { + debugf("Failed to read the required sector, Track:%d, Sector:%d, offset:%04x", trackNo, sectorNo, sectorOffset); + return(FLPYERR_SECTOR_NOT_FOUND); + } +printf("sectorOffset=%08lx, thisSectorSize=%08lx, actualReadSize=%08lx\n", sectorOffset,thisSectorSize, actualReadSize); + // Write the sector to the sector cache memory. + writeZ80Array(MZ_EMU_FDD_CACHE_ADDR, sectorBuffer, thisSectorSize, FPGA); + break; + + case FDD_IOP_REQ_WRITE: + // Fetch the sector from FDC cache. + // + // Seek to the correct disk location. + // + // Write out the cache. + break; + + case FDD_IOP_REQ_INFO: + // Nothing else needs to be done, the sector has been located and the parameters obtained, these will now be returned to the FDC. + break; + + default: + debugf("Unrecognised service command:%d", cmd); + return(FLPYERR_DISK_ERROR); + break; + } + + + // Update the callers variable to indicate actual disk type parameters, ie. sector size - this is necessary as some disks have various sector sizes per track. The rotational speed is taken from + // the parameters and shouldnt change but kept here just in case! +printf("Check1:%08lx, %08lx, %08lx, %08lx, %d, %d\n", sectorSize, *sectorSize, rotationalSpeed, *rotationalSpeed, thisSectorSize, FLOPPY_DEFINITIONS[emuConfig.params[emuConfig.machineModel].fdd[driveNo].diskType].rpm ); + if(sectorSize != NULL) + (*sectorSize) = thisSectorSize; + if(rotationalSpeed != NULL) + (*rotationalSpeed) = FLOPPY_DEFINITIONS[emuConfig.params[emuConfig.machineModel].fdd[driveNo].diskType].rpm; +printf("Check2:%08lx, %08lx, %08lx, %08lx\n", sectorSize, *sectorSize, rotationalSpeed, *rotationalSpeed); + } + } else + { + // Update the mounted status of all the disks. + // + for(uint8_t idx=0; idx < MZ_EMU_FDD_MAX_DISKS; idx++) + { + // If a disk is mounted, unmount (close) it. + if(opened >> idx) + { +printf("Closing disk:%d\n", idx); + f_close(&fileDesc[idx]); + opened &= ~(1 << idx); + dirty &= ~(1 << idx); + lastTrack[idx] = 0xff; + } + } + } + + return(FLPYERR_NOERROR); +} + + +// Method to invoke necessary services for the Sharp MZ Series emulations, ie. User OSD Menu, Tape Queuing, Floppy Disk loading etc. +// When interrupt = 1 then an interrupt from the FPGA has occurred, when 0 it is a scheduling call. +// +void EMZservice(uint8_t interrupt) +{ + // Locals. + static uint32_t entryScreenTimer = 0xFFFFFFFF; + uint8_t result; + uint8_t emuISRReason[MZ_EMU_INTR_MAX_REGISTERS]; + uint8_t emuInData[256]; + uint8_t emuOutData[256]; + uint8_t cmdType; + uint8_t cmdSvc; + const char * cmdStr; + unsigned long time = 0; + uint32_t timeElapsed; + + // Get elapsed time since last service poll. + time = *ms; + + // Has an interrupt been generated by the emulator support hardware? + if(interrupt) + { + // Take out a Lock on the Z80 bus - this is needed as a number of small operations are performed on the Z80/FPGA during an interrupt cycle and it adds overhead to wait for a Z80 request approval on each occasion. + // + if(lockZ80() == 0) + { + // Read the reason code register. + // + result=readZ80Array(MZ_EMU_REG_INTR_ADDR, emuISRReason, MZ_EMU_INTR_MAX_REGISTERS, FPGA); + printf("IntrReg:"); for(uint16_t idx=0; idx < MZ_EMU_INTR_MAX_REGISTERS; idx++) { printf("%02x ", emuISRReason[idx]); } printf("\n"); + if(!result) + { + // Keyboard interrupt? + if(emuISRReason[MZ_EMU_INTR_REG_ISR] & MZ_EMU_INTR_SRC_KEYB) + { + // Read the key pressed. + result=readZ80Array(MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_CTRL_REG, &emuInData[MZ_EMU_KEYB_CTRL_REG], MZ_EMU_KEYB_MAX_REGISTERS, FPGA); + printf("KeyReg:"); for(uint16_t idx=MZ_EMU_KEYB_CTRL_REG; idx < MZ_EMU_KEYB_CTRL_REG+MZ_EMU_KEYB_MAX_REGISTERS; idx++) { printf("%02x ", emuInData[idx]); } printf("\n"); + if(!result) + { + printf("Received key:%02x, %02x, %d, %d (%d,%d)\n", emuInData[MZ_EMU_KEYB_KEYD_REG], emuInData[MZ_EMU_KEYB_KEYC_REG], emuInData[MZ_EMU_KEYB_KEY_POS_REG], emuInData[MZ_EMU_KEYB_KEY_POS_LAST_REG], emuInData[MZ_EMU_KEYB_FIFO_WR_ADDR], emuInData[MZ_EMU_KEYB_FIFO_RD_ADDR]); + + // Not interested in up key events or control events except for CTRL+BREAK, discard. + if(emuInData[MZ_EMU_KEYB_KEYC_REG] & KEY_DOWN_BIT) + { + // First time the menu key has been pressed, pop up the menu and redirect all key input to the I/O processor. + if(emuControl.activeMenu.menu[0] == MENU_DISABLED && emuInData[MZ_EMU_KEYB_KEYD_REG] == 0xFE) + { + // Recheck OSD parameters - the running machine may change resolution which in turn changes the OSD settings. + OSDUpdateScreenSize(); + + // Setup Menu Font according to available X pixels. Original screen formats only have 320 pixel width. + if((uint16_t)OSDGet(ACTIVE_MAX_X) < 512) + { + EMZSetMenuFont(FONT_5X7); + } else + { + EMZSetMenuFont(FONT_7X8); + } + + // Disable keyboard scan being sent to emulation, enable interrupts on each key press. + emuOutData[MZ_EMU_KEYB_CTRL_REG] = MZ_EMU_KEYB_DISABLE_EMU | MZ_EMU_KEYB_ENABLE_INTR; + writeZ80Array(MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_CTRL_REG, &emuOutData[MZ_EMU_KEYB_CTRL_REG], 1, FPGA); + emuControl.activeMenu.menuIdx = 0; + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_MAIN; + + // Draw the initial main menu. + EMZMainMenu(); + OSDRefreshScreen(); + + // Enable the OSD. + emuOutData[0] = 0x40 | emuConfig.params[emuConfig.machineModel].displayOutput; + emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY2] = emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY2] | 0x40; + writeZ80Array(MZ_EMU_ADDR_REG_DISPLAY2, emuOutData, 1, FPGA); + + // Menu active and user has requested to return to emulation? + } + else if(emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] != MENU_DISABLED && emuInData[MZ_EMU_KEYB_KEYD_REG] == 0xFE) + { + // Enable keyboard scan being sent to emulation, disable interrupts on each key press. + emuOutData[MZ_EMU_KEYB_CTRL_REG] = 0; + writeZ80Array(MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_CTRL_REG, &emuOutData[MZ_EMU_KEYB_CTRL_REG], 1, FPGA); + emuControl.activeMenu.menuIdx = 0; + emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] = MENU_DISABLED; + + // Release any allocated menu or file list memory as next invocation restarts at the main menu. + EMZReleaseDirMemory(); + EMZReleaseMenuMemory(); + + // Disable the OSD cursor. + OSDClearCursorFlash(); + + // Disable the OSD, this is done by updating the local register copy as the final act is to upload the latest configuration, OSD mode included. + emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY2] = emuConfig.emuRegisters[MZ_EMU_REG_DISPLAY2] & 0xbf; + //emuOutData[0] = 0x0; + //writeZ80Array(MZ_EMU_ADDR_REG_DISPLAY3, emuOutData, 1, FPGA); + + // Switch to the requested machine if changed, update the configuration in the FPGA. + // + if(emuConfig.machineChanged) + { + EMZRun(); + } else + { + EMZSwitchToMachine(emuConfig.machineModel, 0); + } + + // Direct key intercept, process according to state. + } else + { + // Event driven key processing + switch(emuControl.activeDialog) + { + case DIALOG_FILELIST: + EMZProcessFileListKey(emuInData[MZ_EMU_KEYB_KEYD_REG], emuInData[MZ_EMU_KEYB_KEYC_REG]); + break; + + case DIALOG_KEYENTRY: + EMZKeyInjectionEdit(emuInData[MZ_EMU_KEYB_KEYD_REG], emuInData[MZ_EMU_KEYB_KEYC_REG]); + break; + + case DIALOG_MENU: + default: + EMZProcessMenuKey(emuInData[MZ_EMU_KEYB_KEYD_REG], emuInData[MZ_EMU_KEYB_KEYC_REG]); + //EMZProcessMenuKeyDebug(emuInData[MZ_EMU_KEYB_KEYD_REG], emuInData[MZ_EMU_KEYB_KEYC_REG]); + break; + } + } + } + } else + { + printf("Key retrieval error.\n"); + } + } + + // CMT generated an interrupt? + if(emuISRReason[MZ_EMU_INTR_REG_ISR] & MZ_EMU_INTR_SRC_CMT) + { + // Read the cmt status directly. This data can also be ready from the Master Control register of the emulator. + result=readZ80Array(MZ_EMU_CMT_REG_ADDR, emuInData, MZ_EMU_CMT_MAX_REGISTERS, FPGA); + + // Update the local copy. + emuConfig.emuRegisters[MZ_EMU_REG_CMT3] = emuInData[MZ_EMU_CMT_STATUS_INTR_REG]; + emuConfig.emuRegisters[MZ_EMU_REG_CMT2] = emuInData[MZ_EMU_CMT_STATUS2_INTR_REG]; + + // When debugging output register change as text message. + debugf("CMT/CMT2 (%02x,%02x,%s%s%s%s%s%s:%s%s%s%s%s).", + emuInData[MZ_EMU_CMT_STATUS_REG], emuInData[MZ_EMU_CMT_STATUS2_REG], + emuInData[MZ_EMU_CMT_STATUS_REG] & MZ_EMU_CMT_PLAY_READY ? "PLAY_READY," : "", + emuInData[MZ_EMU_CMT_STATUS_REG] & MZ_EMU_CMT_PLAYING ? "PLAYING," : "", + emuInData[MZ_EMU_CMT_STATUS_REG] & MZ_EMU_CMT_RECORD_READY ? "RECORD_READY,": "", + emuInData[MZ_EMU_CMT_STATUS_REG] & MZ_EMU_CMT_RECORDING ? "RECORDING," : "", + emuInData[MZ_EMU_CMT_STATUS_REG] & MZ_EMU_CMT_ACTIVE ? "ACTIVE," : "", + emuInData[MZ_EMU_CMT_STATUS_REG] & MZ_EMU_CMT_SENSE ? "SENSE," : "", + emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_APSS ? "APSS," : "", + emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_APSS ? emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_DIRECTION ? "FFWD," : "REW," : "", + emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_EJECT ? "EJECT," : "", + emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_PLAY ? "PLAY," : "", + emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_STOP ? "STOP," : "", + emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_AUTOREW ? "AUTOREW," : "", + emuInData[MZ_EMU_CMT_STATUS2_REG] & MZ_EMU_CMT2_AUTOPLAY ? "AUTOPLAY" : ""); + + debugf("CMT/CMT2i(%02x,%02x,%s%s%s%s%s%s:%s%s%s%s%s).", + emuInData[MZ_EMU_CMT_STATUS_INTR_REG], emuInData[MZ_EMU_CMT_STATUS2_INTR_REG], + emuInData[MZ_EMU_CMT_STATUS_INTR_REG] & MZ_EMU_CMT_PLAY_READY ? "PLAY_READY," : "", + emuInData[MZ_EMU_CMT_STATUS_INTR_REG] & MZ_EMU_CMT_PLAYING ? "PLAYING," : "", + emuInData[MZ_EMU_CMT_STATUS_INTR_REG] & MZ_EMU_CMT_RECORD_READY ? "RECORD_READY,": "", + emuInData[MZ_EMU_CMT_STATUS_INTR_REG] & MZ_EMU_CMT_RECORDING ? "RECORDING," : "", + emuInData[MZ_EMU_CMT_STATUS_INTR_REG] & MZ_EMU_CMT_ACTIVE ? "ACTIVE," : "", + emuInData[MZ_EMU_CMT_STATUS_INTR_REG] & MZ_EMU_CMT_SENSE ? "SENSE," : "", + emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_APSS ? "APSS," : "", + emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_APSS ? emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_DIRECTION ? "FFWD," : "REW," : "", + emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_EJECT ? "EJECT," : "", + emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_PLAY ? "PLAY," : "", + emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_STOP ? "STOP," : "", + emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_AUTOREW ? "AUTOREW," : "", + emuInData[MZ_EMU_CMT_STATUS2_INTR_REG] & MZ_EMU_CMT2_AUTOPLAY ? "AUTOPLAY" : ""); + + // Process the tape queue according to signals received from the hardware. + EMZProcessTapeQueue(1); + } + + // Floppy Disk Drive unit interrupt? + if(emuISRReason[MZ_EMU_INTR_REG_ISR] & MZ_EMU_INTR_SRC_FDD) + { + + // Read the control data to allow a decision as to what is required. + result=readZ80Array(MZ_EMU_FDD_CTRL_ADDR, emuInData, MZ_EMU_FDD_MAX_REGISTERS, FPGA); + + // Debug information to evaluate interrupt. + result=readZ80Array(MZ_EMU_FDC_CTRL_ADDR, &emuInData[MZ_EMU_FDD_MAX_REGISTERS], 32, FPGA); //MZ_EMU_FDC_MAX_REGISTERS, FPGA); + debugf("FDD: (%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x)", + emuInData[MZ_EMU_FDD_CTRL_REG], + emuInData[MZ_EMU_FDD_SECTOR_REG], + emuInData[MZ_EMU_FDD_TRACK_REG], + emuInData[MZ_EMU_FDD_CST_REG], + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG], + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_TRACK_REG], + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_SECTOR_REG], + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_DATA_REG], + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_LCMD_REG]); + + debugf("FDD IOP: Drive No:%d, Head:%s, Request:%s, Command: %s, Sector:%d, Track:%d", + ((emuInData[MZ_EMU_FDD_CTRL_REG] & FDD_IOP_DISK_SELECT_NO) >> 5) & 0x03, + emuInData[MZ_EMU_FDD_CTRL_REG] & FDD_IOP_SIDE ? "1" : "0", + emuInData[MZ_EMU_FDD_CTRL_REG] & FDD_IOP_SERVICE_REQ ? "YES " : "NO", + (emuInData[MZ_EMU_FDD_CTRL_REG] & FDD_IOP_REQ_MODE) == 0 ? "NOP" : (emuInData[MZ_EMU_FDD_CTRL_REG] & FDD_IOP_REQ_MODE) == 1 ? "READ" : (emuInData[MZ_EMU_FDD_CTRL_REG] & FDD_IOP_REQ_MODE) == 2 ? "WRITE" : "INFO", + emuInData[MZ_EMU_FDD_SECTOR_REG], + emuInData[MZ_EMU_FDD_TRACK_REG]); + debugf(" FDD Signals:(%s%s%s%s) Raw Drive Select:(%d)", + emuInData[MZ_EMU_FDD_CST_REG] & FDD_DISK_BUSY ? "BUSY," : "", + emuInData[MZ_EMU_FDD_CST_REG] & FDD_DISK_DRQ ? "DRQ," : "", + emuInData[MZ_EMU_FDD_CST_REG] & FDD_DISK_DDEN ? "" : "DDEN,", + emuInData[MZ_EMU_FDD_CST_REG] & FDD_DISK_MOTORON ? "" : "MOTOR", + emuInData[MZ_EMU_FDD_CST_REG] & FDD_DISK_SELECT_NO); + + cmdSvc = 0; + switch(emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_LCMD_REG] & 0xF0) + { + case FDC_CMD_RESTORE: + cmdStr = "RESTORE"; + cmdType = 1; + break; + case FDC_CMD_SEEK: + cmdStr = "SEEK"; + cmdType = 1; + break; + case FDC_CMD_STEP: + cmdStr = "STEP"; + cmdType = 1; + break; + case FDC_CMD_STEP_TU: + cmdStr = "STEP TU"; + cmdType = 1; + break; + case FDC_CMD_STEP_IN: + cmdStr = "STEPIN"; + cmdType = 1; + break; + case FDC_CMD_STEPIN_TU: + cmdStr = "STEPIN TU"; + cmdType = 1; + break; + case FDC_CMD_STEPOUT: + cmdStr = "STEPOUT"; + cmdType = 1; + break; + case FDC_CMD_STEPOUT_TU: + cmdStr = "STEPOUT TU"; + cmdType = 1; + break; + case FDC_CMD_READSEC: + cmdStr = "READSEC"; + cmdSvc = 1; + cmdType = 2; + break; + case FDC_CMD_READSEC_MULT: + cmdStr = "READSEC MULT"; + cmdSvc = 1; + cmdType = 2; + break; + case FDC_CMD_WRITESEC: + cmdStr = "WRITESEC"; + cmdSvc = 1; + cmdType = 2; + break; + case FDC_CMD_WRITESEC_MULT: + cmdStr = "WRITESEC MULT"; + cmdSvc = 1; + cmdType = 2; + break; + case FDC_CMD_READADDR: + cmdStr = "READADDR"; + cmdSvc = 1; + cmdType = 3; + break; + case FDC_CMD_READTRACK: + cmdStr = "READTRACK"; + cmdSvc = 1; + cmdType = 3; + break; + case FDC_CMD_WRITETRACK: + cmdStr = "WRITETRACK"; + cmdSvc = 1; + cmdType = 3; + break; + case FDC_CMD_FORCEINT: + cmdStr = "FORCEINT"; + cmdType = 4; + break; + } + debugf(" WD1793 Signals:(%s%s%s%s%s%s%s%s%s[%02x,%d])", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_NOTRDY ? "NOTRDY," : "", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_PROTECTED ? "PROTECTED," : "", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_HEADLOADED ? cmdType != 1 ? "RTYPE/WFAULT," : "HEADLOADED," : "", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_SEEKERROR ? cmdType != 1 ? "RNF," : "SEEKERROR," : "", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_CRCERROR ? "CRCERROR," : "", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_TRACK0 ? cmdType != 1 ? "LOSTDATA," : "TRACK0," : "", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_INDEX ? cmdType != 1 ? "DRQ," : "INDEX," : "", + emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_CTRL_REG] & FDC_STI_BUSY ? "BUSY," : "", + cmdStr, emuInData[MZ_EMU_FDD_MAX_REGISTERS+MZ_EMU_FDC_LCMD_REG], cmdType); + if(cmdType == 3) + debugf("READADDR:%02x,%02x,%02x,%02x,%02x,%02x", emuInData[MZ_EMU_FDD_MAX_REGISTERS+16], emuInData[MZ_EMU_FDD_MAX_REGISTERS+17],emuInData[MZ_EMU_FDD_MAX_REGISTERS+18],emuInData[MZ_EMU_FDD_MAX_REGISTERS+19],emuInData[MZ_EMU_FDD_MAX_REGISTERS+20],emuInData[MZ_EMU_FDD_MAX_REGISTERS+21]); + + for(uint8_t tst=0; tst < 32; tst ++) + { + printf("%02x,", emuInData[MZ_EMU_FDD_MAX_REGISTERS+tst]); + } + // Clear the READY flag. This also clears the interrupt, basically an acknowledgement. + emuControl.fdd.ctrlReg &= ((~FDD_CTRL_READY) & 0x1f); + printf("CTRLREG ENTER:%02x\n", emuControl.fdd.ctrlReg ); + writeZ80Array(MZ_EMU_FDD_CTRL_ADDR+MZ_EMU_FDD_CTRL_REG, &emuControl.fdd.ctrlReg, 1, FPGA); + + // Process the request if it requires servicing. + enum FLOPPYERRORCODES floppyError = FLPYERR_NOERROR; + uint16_t thisSectorSize = 0; + uint16_t thisRotationalSpeed = 0; + if(cmdSvc) floppyError = (uint8_t)EMZProcessFDDRequest(emuInData[MZ_EMU_FDD_CTRL_REG], emuInData[MZ_EMU_FDD_TRACK_REG], emuInData[MZ_EMU_FDD_SECTOR_REG], emuInData[MZ_EMU_FDD_CST_REG], &thisSectorSize, &thisRotationalSpeed); + printf("Error Code:%d, Sector Size:%d, Rotational Speed:%d\n", floppyError, thisSectorSize, thisRotationalSpeed); + + // Processing complete, set the READY flag along with current sector size, rotational speed and error code. 7:5 = error code, 4 = rotational speed, 3:1 = sector size code, 0 = Ready flag. + if(floppyError != FLPYERR_NOERROR) + { + emuControl.fdd.ctrlReg = (floppyError << 5 | FDD_CTRL_READY); + } else + { + emuControl.fdd.ctrlReg = thisRotationalSpeed == 360 ? 0x10 : 0x00 | ((uint8_t)((thisSectorSize&0xff00) >> 7) & 0x0E) | FDD_CTRL_READY; + } + printf("CTRLREG EXIT:%02x\n", emuControl.fdd.ctrlReg ); + writeZ80Array(MZ_EMU_FDD_CTRL_ADDR+MZ_EMU_FDD_CTRL_REG, &emuControl.fdd.ctrlReg, 1, FPGA); + } + } else + { + printf("Interrupt reason retrieval error.\n"); + } + + // Release the lock on the Z80 bus before exitting. + releaseLockZ80(); + + // Indicate processing time. + debugf("Int time:%ld", *ms - time); + } else + { + // Raise an error, couldnt service the interrupt because the Z80 bus couldnt be locked. + // + debugf("Failed to lock the Z80 bus, cannot service interrupt!"); + } + } + + // Scheduling block, called periodically by the zOS scheduling. + else + { + // On the first hot-key menu selection, draw the Argo logo and branding. + if(entryScreenTimer == 0xFFFFFFFF && emuControl.activeMenu.menu[emuControl.activeMenu.menuIdx] == MENU_DISABLED) + { + OSDClearScreen(BLACK); + OSDWriteBitmap(128,0,BITMAP_ARGO, RED, BLACK); + OSDWriteString(31, 6, 0, 10, 0, 0, FONT_9X16, NORMAL, "Sharp MZ Series", NULL, BLUE, BLACK); + OSDRefreshScreen(); + entryScreenTimer = 0x00FFFFF; + + // Enable the OSD. + emuOutData[0] = 0x40 | emuConfig.params[emuConfig.machineModel].displayOutput; + writeZ80Array(MZ_EMU_ADDR_REG_DISPLAY2, emuOutData, 1, FPGA); + } + else if(entryScreenTimer != 0xFFFFFFFF && entryScreenTimer > 0) + { + switch(--entryScreenTimer) + { + // Quick wording change... + case 0x40000: + OSDClearScreen(BLACK); + OSDWriteBitmap(128,0,BITMAP_ARGO, RED, BLACK); + OSDWriteString(31, 6, 0, 10, 0, 0, FONT_9X16, NORMAL, "Argo Inside", NULL, BLUE, BLACK); + OSDRefreshScreen(); + break; + + // Near expiry, clear the OSD and disable it. + case 0x00100: + // Set initial menu on-screen for user to interact with. + OSDClearScreen(BLACK); + + // Disable the OSD. + // + emuOutData[0] = 0x00 | emuConfig.params[emuConfig.machineModel].displayOutput; + writeZ80Array(MZ_EMU_ADDR_REG_DISPLAY2, emuOutData, 1, FPGA); + break; + + default: + break; + } + } + + // When the startup banner has been displayed, normal operations can commence as this module has been initialised. + else if(entryScreenTimer == 0x00000000) + { + // Call the OSD service scheduling routine to allow any OSD updates to take place. + OSDService(); + + // Process the tape queue periodically, this is generally only needed for uploading a new file from the queue, all other actions are serviced + // via interrupt. + EMZProcessTapeQueue(0); + } + } + + return; +} + + +// Initialise the EmuMZ subsystem. This method is called at startup to intialise control structures and hardware settings. +// +uint8_t EMZInit(enum MACHINE_HW_TYPES hostMachine, uint8_t machineModel) +{ + // Locals. + // + uint8_t result = 0; + + // On initialisation, copy the default configuration into the working data structures and if an SD configuration is found, overwrite as necessary. + memcpy(&emuControl, &emuControlDefault, sizeof(t_emuControl)); + memcpy(&emuConfig, hostMachine == HW_MZ2000 ? &emuConfigDefault_MZ2000 : hostMachine == HW_MZ80A ? &emuConfigDefault_MZ80A : &emuConfigDefault_MZ700, sizeof(t_emuConfig)); + for(uint16_t idx=0; idx < MAX_MZMACHINES; idx++) { for(uint16_t idx2=0; idx2 < MAX_KEY_INS_BUFFER; idx2++) { if(emuConfig.params[idx].loadApp.preKeyInsertion[idx2].i == 0) { emuConfig.params[idx].loadApp.preKeyInsertion[idx2].i = 0xffffffff; } } } + for(uint16_t idx=0; idx < MAX_MZMACHINES; idx++) { for(uint16_t idx2=0; idx2 < MAX_KEY_INS_BUFFER; idx2++) { if(emuConfig.params[idx].loadApp.postKeyInsertion[idx2].i == 0) { emuConfig.params[idx].loadApp.postKeyInsertion[idx2].i = 0xffffffff; } } } + + // Initialise the on screen display. + if(!(result=OSDInit(MENU))) + { + // Store the host machine under which the emulation is running, needed for features such as keyboard scan code mapping. + emuControl.hostMachine = hostMachine; + + // Allocate first file list top level directory buffer. + emuControl.activeDir.dirIdx = 0; + + if((emuControl.activeDir.dir[emuControl.activeDir.dirIdx] = (char *)malloc(strlen(TOPLEVEL_DIR)+1)) == NULL) + { + printf("Memory exhausted during init of directory cache, aborting!\n"); + } else + { + strcpy(emuControl.activeDir.dir[emuControl.activeDir.dirIdx], TOPLEVEL_DIR); + } + + // Initialise tape queue. + // + for(int i=0; i < MAX_TAPE_QUEUE; i++) + { + emuControl.tapeQueue.queue[i] = NULL; + } + emuControl.tapeQueue.tapePos = 0; + emuControl.tapeQueue.elements = 0; + emuControl.tapeQueue.fileName[0] = 0; + + // Read in the persisted configuration. + // + EMZLoadConfig(); + + // Setup the local copy of the emulator register contents. + if(readZ80Array(MZ_EMU_ADDR_REG_MODEL, emuConfig.emuRegisters, MZ_EMU_MAX_REGISTERS, FPGA)) + { + printf("Failed to read initial emulator register configuration.\n"); + } + + // Initialise Floppy control variables. + emuControl.fdd.ctrlReg = 0; + + // Finally set the initial machine and group. + emuConfig.machineModel = machineModel; + emuConfig.machineGroup = EMZGetMachineGroup(); + } + + return(result); +} + +// Once the tranZPUter has been engaged and configured, start the emulation with the required machine. +// +void EMZRun(void) +{ + // Locals. + uint8_t errCode; + uint8_t keyCnt; + + // Ensure the tape queue is cleared, dont persist between emulations. + EMZClearTapeQueue(); + + // Switch to the requested machine and upload the configuration to the FPGA. + // + EMZSwitchToMachine(emuConfig.machineModel, 1); + + // If startup application is enabled and pre key insertion keys given, send them to the emulation keyboard module. This functionality works without + // an actual application for load being specified and these keys can also be used to interact with the I/O processor menu, ie. to set a CPU speed. + if(emuConfig.params[emuConfig.machineModel].loadApp.appEnabled && emuConfig.params[emuConfig.machineModel].loadApp.preKeyInsertion[0].i != 0xffffffff) + { + // Count the number of keys for insertion. + for(keyCnt=0; keyCnt < MAX_KEY_INS_BUFFER; keyCnt++) { if(emuConfig.params[emuConfig.machineModel].loadApp.preKeyInsertion[keyCnt].i == 0xffffffff) break; } + } + + // If a startup application is specified, load it direct to RAM. + // + if(emuConfig.params[emuConfig.machineModel].loadApp.appEnabled && strlen(emuConfig.params[emuConfig.machineModel].loadApp.appFileName) > 0) + { + // Use the tape method for loading direct to memory. Currently startup applications limited to tape but this will change as the project progresses. + errCode = EMZLoadTapeToRAM(emuConfig.params[emuConfig.machineModel].loadApp.appFileName, 0); + if(errCode != 0) + { + debugf("Failed to load startup application:%s to memory.", emuConfig.params[emuConfig.machineModel].loadApp.appFileName); + } + } + + // If a startup application is enabled and there are post keys to be injected into the keyboard module, send them. + // + if(emuConfig.params[emuConfig.machineModel].loadApp.appEnabled && emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[0].i != 0xffffffff) + { + // Count the number of keys for insertion. + for(keyCnt=0; keyCnt < MAX_KEY_INS_BUFFER; keyCnt++) { if(emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion[keyCnt].i == 0xffffffff) break; } +printf("KeyCnt:%d, addr=%08lx, mem=%08lx\n", keyCnt, MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_FIFO_ADDR, (uint8_t *)&emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion); + writeZ80Array(MZ_EMU_REG_KEYB_ADDR+MZ_EMU_KEYB_FIFO_ADDR, (uint8_t *)&emuConfig.params[emuConfig.machineModel].loadApp.postKeyInsertion, keyCnt*4, FPGA); + } +} diff --git a/software/FusionX/src/z80drv/MZ80A/k64fcpu.c b/software/FusionX/src/z80drv/src/k64fcpu.c similarity index 99% rename from software/FusionX/src/z80drv/MZ80A/k64fcpu.c rename to software/FusionX/src/z80drv/src/k64fcpu.c index 65ca232f3..07eb94e87 100644 --- a/software/FusionX/src/z80drv/MZ80A/k64fcpu.c +++ b/software/FusionX/src/z80drv/src/k64fcpu.c @@ -183,16 +183,10 @@ int getch(uint8_t wait) return 0; } -void delay(int number_of_seconds) +// Millisecond delay routine. +void delay(int ms_delay) { - // Converting time into milli_seconds - int milli_seconds = 1000 * number_of_seconds; - - // Storing start time - clock_t start_time = clock(); - - // looping till required time is not achieved - while (clock() < start_time + milli_seconds); + usleep(1000 * ms_delay); } // Function to dump out a given section of memory via the UART. @@ -2799,6 +2793,7 @@ void z80ResetRequest(int signalNo) svcCacheDir(TZSVC_DEFAULT_MZF_DIR, MZF, 1); // Start the Z80 after initialisation of the memory. + delay(500); startZ80(TZMM_BOOT); return; } @@ -2925,6 +2920,18 @@ int main(int argc, char *argv[]) printf("Failed to open the Z80 Driver, exiting...\n"); exit(1); } + + // Setup host type, at the moment this is static but as the FusionX progresses and it can be hosted in multiple hosts without firmware change, then we can pull the + // host type from the detected hardware. + #if (TARGET_HOST_MZ80A == 1) + z80Control.hostType = HW_MZ80A; + #elif (TARGET_HOST_MZ700 == 1) + z80Control.hostType = HW_MZ700; + #elif (TARGET_HOST_MZ2000 == 1) + z80Control.hostType = HW_MZ2000; + #else + #error "Unknown host, update code to accommodate." + #endif // Register the service request handler. signal(SIGIO, z80ServiceRequest); diff --git a/software/FusionX/src/z80drv/MZ2000/optparse.h b/software/FusionX/src/z80drv/src/optparse.h similarity index 100% rename from software/FusionX/src/z80drv/MZ2000/optparse.h rename to software/FusionX/src/z80drv/src/optparse.h diff --git a/software/FusionX/src/z80drv/MZ80A/sharpbiter.c b/software/FusionX/src/z80drv/src/sharpbiter.c similarity index 84% rename from software/FusionX/src/z80drv/MZ80A/sharpbiter.c rename to software/FusionX/src/z80drv/src/sharpbiter.c index 40781575d..7236371fc 100644 --- a/software/FusionX/src/z80drv/MZ80A/sharpbiter.c +++ b/software/FusionX/src/z80drv/src/sharpbiter.c @@ -101,9 +101,15 @@ static t_Z80Ctrl *Z80Ctrl = NULL; static uint8_t *Z80RAM = NULL; static uint8_t *Z80ROM = NULL; +// Millisecond delay routine. +void delay(int ms_delay) +{ + usleep(1000 * ms_delay); +} + // Method to reset the Z80 CPU. // -void reqResetZ80(uint8_t memoryMode) +void reqResetZ80(void) { // Locals. // @@ -116,7 +122,7 @@ void reqResetZ80(uint8_t memoryMode) // Method to start the Z80 CPU. // -void startZ80(uint8_t memoryMode) +void startZ80(void) { // Locals. // @@ -129,7 +135,7 @@ void startZ80(uint8_t memoryMode) // Method to stop the Z80 CPU. // -void stopZ80(uint8_t memoryMode) +void stopZ80(void) { // Locals. // @@ -323,24 +329,42 @@ int main(int argc, char *argv[]) ioctl(arbCtrl.fdTTY, IOCTL_CMD_SUSPEND_IO, &result); // Stop the Z80. - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + stopZ80(); // Remove drivers. Remove all because an external event could have changed last configured driver. // ioctlCmd.cmd = IOCTL_CMD_DEL_DEVICE; + #if(TARGET_HOST_MZ80A == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ80A; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ2000 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ2000; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ700 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ700; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #endif ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS80; ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS40; ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); ioctlCmd.vdev.device = VIRTUAL_DEVICE_TZPU; ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + + // Add in the required driver. + ioctlCmd.cmd = IOCTL_CMD_ADD_DEVICE; + #if(TARGET_HOST_MZ80A == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ80A; + #elif(TARGET_HOST_MZ2000 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ2000; + #elif(TARGET_HOST_MZ700 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ700; + #endif + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); // Reset and start the Z80. - ioctlCmd.cmd = IOCTL_CMD_Z80_RESET; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - ioctlCmd.cmd = IOCTL_CMD_Z80_START; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + delay(500); + reqResetZ80(); break; case HOTKEY_RFS80: @@ -349,12 +373,21 @@ int main(int argc, char *argv[]) ioctl(arbCtrl.fdTTY, IOCTL_CMD_SUSPEND_IO, &result); // Stop the Z80. - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + stopZ80(); // Remove drivers. Remove all because an external event could have changed last configured driver. // ioctlCmd.cmd = IOCTL_CMD_DEL_DEVICE; + #if(TARGET_HOST_MZ80A == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ80A; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ2000 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ2000; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ700 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ700; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #endif ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS80; ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS40; @@ -368,10 +401,8 @@ int main(int argc, char *argv[]) ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); // Reset and start the Z80. - ioctlCmd.cmd = IOCTL_CMD_Z80_RESET; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - ioctlCmd.cmd = IOCTL_CMD_Z80_START; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + delay(500); + reqResetZ80(); break; case HOTKEY_TZFS: @@ -379,12 +410,21 @@ int main(int argc, char *argv[]) ioctl(arbCtrl.fdTTY, IOCTL_CMD_SUSPEND_IO, &result); // Stop the Z80. - ioctlCmd.cmd = IOCTL_CMD_Z80_STOP; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + stopZ80(); // Remove drivers. Remove all because an external event could have changed last configured driver. // ioctlCmd.cmd = IOCTL_CMD_DEL_DEVICE; + #if(TARGET_HOST_MZ80A == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ80A; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ2000 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ2000; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ700 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ700; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #endif ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS80; ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS40; @@ -396,12 +436,6 @@ int main(int argc, char *argv[]) ioctlCmd.cmd = IOCTL_CMD_ADD_DEVICE; ioctlCmd.vdev.device = VIRTUAL_DEVICE_TZPU; ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - - // Reset and start the Z80. - ioctlCmd.cmd = IOCTL_CMD_Z80_RESET; - ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); - // ioctlCmd.cmd = IOCTL_CMD_Z80_START; - // ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); break; case HOTKEY_LINUX: @@ -412,6 +446,16 @@ int main(int argc, char *argv[]) // Remove drivers. Remove all because an external event could have changed last configured driver. // ioctlCmd.cmd = IOCTL_CMD_DEL_DEVICE; + #if(TARGET_HOST_MZ80A == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ80A; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ2000 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ2000; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #elif(TARGET_HOST_MZ700 == 1) + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ700; + ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); + #endif ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS80; ioctl(arbCtrl.fdZ80, IOCTL_CMD_SEND, &ioctlCmd); ioctlCmd.vdev.device = VIRTUAL_DEVICE_RFS40; diff --git a/software/FusionX/src/z80drv/MZ80A/sharpmz.c b/software/FusionX/src/z80drv/src/sharpmz.c similarity index 100% rename from software/FusionX/src/z80drv/MZ80A/sharpmz.c rename to software/FusionX/src/z80drv/src/sharpmz.c diff --git a/software/FusionX/src/z80drv/MZ80A/tzpu.h b/software/FusionX/src/z80drv/src/tzpu.h similarity index 99% rename from software/FusionX/src/z80drv/MZ80A/tzpu.h rename to software/FusionX/src/z80drv/src/tzpu.h index 1a11874b7..e0d9992f9 100755 --- a/software/FusionX/src/z80drv/MZ80A/tzpu.h +++ b/software/FusionX/src/z80drv/src/tzpu.h @@ -46,7 +46,13 @@ #define REFRESH_BYTE_COUNT 8 // This constant controls the number of bytes read/written to the z80 bus before a refresh cycle is needed. #define RFSH_BYTE_CNT 256 // Number of bytes we can write before needing a full refresh for the DRAM. #define HOST_MON_TEST_VECTOR 0x4 // Address in the host monitor to test to identify host type. -#define OS_BASE_DIR "/apps/FusionX/host/MZ-80A/" // Linux base directory where all the files are stored. On a real tranZPUter this would be the SD card root dir. +#if (TARGET_HOST_MZ80A == 1) + #define OS_BASE_DIR "/apps/FusionX/host/MZ-80A/" // Linux base directory where all the files are stored. On a real tranZPUter this would be the SD card root dir. +#elif (TARGET_HOST_MZ700 == 1) + #define OS_BASE_DIR "/apps/FusionX/host/MZ-700/" +#elif (TARGET_HOST_MZ2000 == 1) + #define OS_BASE_DIR "/apps/FusionX/host/MZ-2000/" +#endif #define TZFS_AUTOBOOT_FLAG OS_BASE_DIR "/TZFSBOOT.FLG" // Filename used as a flag, if this file exists in the base directory then TZFS is booted automatically. #define TZ_MAX_Z80_MEM 0x100000 // Maximum Z80 memory available on the tranZPUter board. diff --git a/software/FusionX/src/z80drv/MZ80A/z80ctrl.c b/software/FusionX/src/z80drv/src/z80ctrl.c similarity index 96% rename from software/FusionX/src/z80drv/MZ80A/z80ctrl.c rename to software/FusionX/src/z80drv/src/z80ctrl.c index 6dde0c153..648869834 100644 --- a/software/FusionX/src/z80drv/MZ80A/z80ctrl.c +++ b/software/FusionX/src/z80drv/src/z80ctrl.c @@ -593,6 +593,23 @@ int ctrlCmd(int fdZ80, enum CTRL_COMMANDS cmd, long param1, long param2, long pa { ioctlCmd.vdev.device = VIRTUAL_DEVICE_TZPU; } + else if(strcasecmp((char *)param1, "MZ80A") == 0) + { + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ80A; + } + else if(strcasecmp((char *)param1, "MZ700") == 0) + { + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ700; + } + else if(strcasecmp((char *)param1, "MZ2000") == 0) + { + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ2000; + } + else if(strcasecmp((char *)param1, "PCW") == 0) + { + printf("Add device:%s\n", (char *)param1); + ioctlCmd.vdev.device = VIRTUAL_DEVICE_PCW; + } if(ioctlCmd.vdev.device != VIRTUAL_DEVICE_NONE) { ioctlCmd.cmd = IOCTL_CMD_ADD_DEVICE; @@ -613,6 +630,22 @@ int ctrlCmd(int fdZ80, enum CTRL_COMMANDS cmd, long param1, long param2, long pa { ioctlCmd.vdev.device = VIRTUAL_DEVICE_TZPU; } + else if(strcasecmp((char *)param1, "MZ80A") == 0) + { + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ80A; + } + else if(strcasecmp((char *)param1, "MZ700") == 0) + { + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ700; + } + else if(strcasecmp((char *)param1, "MZ2000") == 0) + { + ioctlCmd.vdev.device = VIRTUAL_DEVICE_MZ2000; + } + else if(strcasecmp((char *)param1, "PCW") == 0) + { + ioctlCmd.vdev.device = VIRTUAL_DEVICE_PCW; + } if(ioctlCmd.vdev.device != VIRTUAL_DEVICE_NONE) { ioctlCmd.cmd = IOCTL_CMD_DEL_DEVICE; @@ -749,7 +782,7 @@ void showArgs(char *progName, struct optparse *options) printf(" # Load contents of binary file into memory at address. default = 0x000000.\n"); printf(" = LOADMEM --file --addr <24 bit addr> --type <0 - Host RAM, 1 = Virtual RAM, 2 = Virtual ROM> [--offset --len ]\n"); printf(" = SAVE --file --addr <24bit addr> --end <24bit addr> [--size <24bit>] --type <0 - Host RAM, 1 = Virtual RAM, 2 = Virtual ROM, 3 = PageTable, 4 = IOPageTable>\n"); - printf(" = DUMP --addr <24bit addr> --end <24bit addr> [--size <24bit>] --type <0 - Host RAM, 1 = Virtual RAM, 2 = Virtual ROM, 3 = MemoryPageTable, 4 = IOPageTable> [--memorypage <0..31>]\n"); + printf(" = DUMP --addr <24bit addr> --end <24bit addr> [--size <24bit>] --type <0 - Host RAM, 1 = Virtual RAM, 2 = Virtual ROM, 3 = MemoryPageTable, 4 = IOPageTable> [--memorypage <0..41>]\n"); printf(" = CPLDCMD --data <32bit command> # Send adhoc 32bit command to CPLD.\n"); #if(DEBUG_ENABLED != 0) printf(" = DEBUG --level # 0 = off, 1..15 debug level, 15 is very verbose.\n"); diff --git a/software/FusionX/src/z80drv/MZ80A/z80driver.c b/software/FusionX/src/z80drv/src/z80driver.c similarity index 71% rename from software/FusionX/src/z80drv/MZ80A/z80driver.c rename to software/FusionX/src/z80drv/src/z80driver.c index ade923132..42a3bc3b2 100644 --- a/software/FusionX/src/z80drv/MZ80A/z80driver.c +++ b/software/FusionX/src/z80drv/src/z80driver.c @@ -29,11 +29,15 @@ // in host memory at full speed. // Feb 2023 - v1.2 Added MZ-80A Rom Filing System device driver. This allows the FusionX // hosted in an MZ-80A to run the original RFS Monitor and software. -// Feb 2023 - v11.3 Added tranZPUter SW device driver. This allows the FusionX hosted +// Feb 2023 - v1.3 Added tranZPUter SW device driver. This allows the FusionX hosted // in any supported host to run TZFS and the updated applications // such as CP/M, SA-5510 Basic, MS-Basic etc. Adding this device driver // prepares the ground to add the SOM GPU as the Video emulation of // the Sharp machines. +// Mar 2023 - v1.4 With the advent of the PCW-8XXX series, seperated distinct machine +// configurations into device driver modules, which are only built +// if same as the target machine. This then allows the z80 driver to be +// a vanilla Z80 or customisations for hosts pulled in. // // // Notes: See Makefile to enable/disable conditional components @@ -111,9 +115,23 @@ static DEFINE_MUTEX(Z80DRV_MUTEX); // Include the virtual hardware drivers. #if(TARGET_HOST_MZ80A == 1) + #include "z80vhw_mz80a.c" +#endif +#if(TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) #include "z80vhw_rfs.c" #endif -#include "z80vhw_tzpu.c" +#if(TARGET_HOST_MZ700 == 1) + #include "z80vhw_mz700.c" +#endif +#if(TARGET_HOST_MZ2000 == 1) + #include "z80vhw_mz2000.c" +#endif +#if(TARGET_HOST_PCW == 0) + #include "z80vhw_tzpu.c" +#endif +#if(TARGET_HOST_PCW == 1) + #include "z80vhw_pcw.c" +#endif //------------------------------------------------------------------------------------------------------------------------------- // @@ -128,269 +146,46 @@ static DEFINE_MUTEX(Z80DRV_MUTEX); static inline void decodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t ioFlag, uint8_t readFlag) { // Locals. - uint32_t idx; // If the RFS module is enabled, it must set the map otherwise use the default handler. // #if(TARGET_HOST_MZ80A == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ80A) + { + mz80aDecodeMemoryMapSetup(address, data, ioFlag, readFlag); + } else + #endif + #if(TARGET_HOST_MZ700 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ700) + { + mz700DecodeMemoryMapSetup(address, data, ioFlag, readFlag); + } else + #endif + #if(TARGET_HOST_MZ2000 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ2000) + { + mz2000DecodeMemoryMapSetup(address, data, ioFlag, readFlag); + } else + #endif + #if(TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_RFS) { rfsDecodeMemoryMapSetup(address, data, ioFlag, readFlag); } else #endif + #if(TARGET_HOST_PCW == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_PCW) + { + pcwDecodeMemoryMapSetup(address, data, ioFlag, readFlag); + } else + #endif + #if(TARGET_HOST_PCW == 0) if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_TZPU) { - rfsDecodeMemoryMapSetup(address, data, ioFlag, readFlag); + tzpuDecodeMemoryMapSetup(address, data, ioFlag, readFlag); } else + #endif { - // Decoding memory address or I/O address? - if(ioFlag == 0) - { - // Certain machines have memory mapped I/O, these need to be handled in-situ as some reads may change the memory map. - // These updates are made whilst waiting for the CPLD to retrieve the requested byte. - // - // 0000 - 0FFF : MZ80K/A/700 = Monitor ROM or RAM (MZ80A rom swap) - // 1000 - CFFF : MZ80K/A/700 = RAM - // C000 - CFFF : MZ80A = Monitor ROM (MZ80A rom swap) - // D000 - D7FF : MZ80K/A/700 = VRAM - // D800 - DFFF : MZ700 = Colour VRAM (MZ700) - // E000 - E003 : MZ80K/A/700 = 8255 - // E004 - E007 : MZ80K/A/700 = 8254 - // E008 - E00B : MZ80K/A/700 = LS367 - // E00C - E00F : MZ80A = Memory Swap (MZ80A) - // E010 - E013 : MZ80A = Reset Memory Swap (MZ80A) - // E014 : MZ80A/700 = Normat CRT display - // E015 : MZ80A/700 = Reverse CRT display - // E200 - E2FF : MZ80A/700 = VRAM roll up/roll down. - // E800 - EFFF : MZ80K/A/700 = User ROM socket or DD Eprom (MZ700) - // F000 - F7FF : MZ80K/A/700 = Floppy Disk interface. - // F800 - FFFF : MZ80K/A/700 = Floppy Disk interface. - switch(address) - { - #if(TARGET_HOST_MZ700 == 1) - #endif - #if(TARGET_HOST_MZ2000 == 1) - #endif - - #if(TARGET_HOST_MZ80A == 1) - // Memory map switch. - case 0xE00C: case 0xE00D: case 0xE00E: case 0xE00F: - if(readFlag) - { - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (0xC000+idx)); - setMemoryType((idx+0xC000)/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - } - Z80Ctrl->memSwitch = 1; - break; - - // Reset memory map switch. - case 0xE010: case 0xE011: case 0xE012: case 0xE013: - if(readFlag) - { - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - setMemoryType((idx+0xC000)/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (idx+0xC000)); - } - } - Z80Ctrl->memSwitch = 0; - break; - #endif - - default: - break; - } - } else - { - // Determine if this is a memory management port and update the memory page if required. - switch(address & 0x00FF) - { - // MZ700 memory mode switch. - // - // MZ-700 - // |0000:0FFF|1000:CFFF|D000:FFFF - // ------------------------------ - // OUT 0xE0 = |DRAM | | - // OUT 0xE1 = | | |DRAM - // OUT 0xE2 = |MONITOR | | - // OUT 0xE3 = | | |Memory Mapped I/O - // OUT 0xE4 = |MONITOR |DRAM |Memory Mapped I/O - // OUT 0xE5 = | | |Inhibit - // OUT 0xE6 = | | | - // - // = Return to the state prior to the complimentary command being invoked. - #if(TARGET_HOST_MZ700 == 1) - // Enable lower 4K block as DRAM - case IO_ADDR_E0: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - break; - - // Enable upper 12K block, including Video/Memory Mapped peripherals area, as DRAM. - case IO_ADDR_E1: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - // MZ-700 mode we only work in first 64K block. - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - } - break; - - // Enable MOnitor ROM in lower 4K block - case IO_ADDR_E2: - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - break; - - // Enable Video RAM and Memory mapped peripherals in upper 12K block. - case IO_ADDR_E3: - if(!Z80Ctrl->inhibitMode) - { - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Reset to power on condition memory map. - case IO_ADDR_E4: - // Lower 4K set to Monitor ROM. - for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - if(!Z80Ctrl->inhibitMode) - { - // Upper 12K to hardware. - for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - } - break; - - // Inhibit. Backup current page data in region 0xD000-0xFFFF and inhibit it. - case IO_ADDR_E5: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - backupMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_INHIBIT, idx); - } - Z80Ctrl->inhibitMode = 1; - break; - - // Restore D000-FFFF to its original state. - case IO_ADDR_E6: - for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - restoreMemoryType(idx/MEMORY_BLOCK_GRANULARITY); - } - Z80Ctrl->inhibitMode = 0; - break; - #endif - - #if(TARGET_HOST_MZ2000 == 1) - case IO_ADDR_E0: - break; - - case IO_ADDR_E1: - break; - - case IO_ADDR_E2: - break; - - case IO_ADDR_E3: - // Program control register. - if(value & 0x80) - { - } else - { - switch((value >> 1) & 0x07) - { - // NST toggle. - case 1: - // NST pages in all RAM and resets cpu. - if(value & 0x01) - { - Z80Ctrl->lowMemorySwap = 0; - for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(Z80Ctrl->defaultPageMode == USE_PHYSICAL_RAM) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - } - resetZ80(); - } - break; - - default: - break; - } - } - break; - - case IO_ADDR_E8: - // NEED FLAG TO SET THIS WHEN CALLED WITH NON MEMORY SWITCH BYTE - if(isPhysical(0xD000) && (value & 0x80) == 0) - { - for(idx=0xC000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(Z80Ctrl->defaultPageMode == USE_PHYSICAL_RAM) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (Z80Ctrl->lowMemorySwap ? idx - 0x8000 : idx)); - } - } - } else - if(value & 0x80) - { - if(value & 0x40) - { - setMemoryType(0xD000/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, 0xD000); - } else - { - for(idx=0xC000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - } - } - break; - #endif - - #if(TARGET_HOST_MZ80A == 1) - #endif - - // Port is not a memory management port. - default: - break; - } - } } return; @@ -407,6 +202,31 @@ static inline zuint8 readVirtual(zuint16 address, uint8_t ioFlag) // Invoke the specific hardware emulation. // #if(TARGET_HOST_MZ80A == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ80A) + { + data = mz80aRead(address, ioFlag); + } else + #endif + #if(TARGET_HOST_MZ700 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ700) + { + data = mz700Read(address, ioFlag); + } else + #endif + #if(TARGET_HOST_MZ2000 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ2000) + { + data = mz2000Read(address, ioFlag); + } else + #endif + #if(TARGET_HOST_PCW == 1) + if((Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_PCW)) + { + data = pcwRead(address, ioFlag); + } else + #endif + + #if(TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) // RFS only has memory mapped registers. if((Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_RFS) && ioFlag == 0) { @@ -414,15 +234,14 @@ static inline zuint8 readVirtual(zuint16 address, uint8_t ioFlag) } else #endif + #if(TARGET_HOST_PCW == 0) if((Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_TZPU)) { data = tzpuRead(address, ioFlag); - } + } else + #endif - else if(isVirtualMemory(address)) { - // Retrieve data from virtual memory. - data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); } return(data); @@ -437,6 +256,31 @@ static inline void writeVirtual(zuint16 address, zuint8 data, uint8_t ioFlag) // Invoke the specific hardware emulation. // #if(TARGET_HOST_MZ80A == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ80A) + { + mz80aWrite(address, data, ioFlag); + } else + #endif + #if(TARGET_HOST_MZ700 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ700) + { + mz700Write(address, data, ioFlag); + } else + #endif + #if(TARGET_HOST_MZ2000 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ2000) + { + mz2000Write(address, data, ioFlag); + } else + #endif + #if(TARGET_HOST_PCW == 1) + if((Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_PCW)) + { + pcwWrite(address, data, ioFlag); + } else + #endif + + #if(TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) // RFS only has memory mapped registers. if((Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_RFS) && ioFlag == 0) { @@ -444,15 +288,14 @@ static inline void writeVirtual(zuint16 address, zuint8 data, uint8_t ioFlag) } else #endif + #if(TARGET_HOST_PCW == 0) if((Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_TZPU)) { tzpuWrite(address, data, ioFlag); - } + } else + #endif - else if(isVirtualRAM(address)) { - // Update virtual memory. - writeVirtualRAM(address, data); } return; @@ -464,43 +307,133 @@ static inline void writeVirtual(zuint16 address, zuint8 data, uint8_t ioFlag) // time critical operations such as Floppy Disk read/write. // // This method attempts to decode the current opcode and if it is a hardware operation, make the request ahead of the Z80 emulator. -static inline void lookAhead(zuint16 address, zuint8 opcode, zuint8 opcode2) +static inline uint8_t lookAhead(zuint16 address, zuint8 opcode, zuint8 opcode2) { - // Locals. - // - - // IN r,(C) INI, INIR, IND, INDR - if( (opcode == 0xED && (( (opcode2 & 0x78) != 0) || ((opcode2 & 0xBA) != 0)) && ((opcode2 & 0x01) == 0x00)) ) + // IN A,(C) ED 78 + // IN B,(C) ED 40 + // IN C,(C) ED 48 + // IN D,(C) ED 50 + // IN E,(C) ED 58 + // IN H,(C) ED 60 + // IN L,(C) ED 68 + if( opcode == 0xED && (opcode2 == 0x78 || opcode2 == 0x40 || opcode2 == 0x48 || opcode2 == 0x50 || opcode2 == 0x58 || opcode2 == 0x60 || opcode2 == 0x68) ) { - SPI_SEND32( (Z80CPU.bc.uint16_value << 16) | CPLD_CMD_READIO_ADDR); - Z80Ctrl->ioReadAhead = 1; + if(isPhysicalIO(Z80CPU.bc.uint16_value)) + { + if((Z80CPU.bc.uint16_value&0x00ff) < 8) + { + SPI_SEND_8(CPLD_CMD_READIO_ADDR + (Z80CPU.bc.uint16_value&0x00ff)); + } else + { + //SPI_SEND_16((Z80CPU.bc.uint16_value&0x00ff) << 8 | CPLD_CMD_READIO_ADDR); + SPI_SEND_32(Z80CPU.bc.uint16_value, CPLD_CMD_READIO_ADDR); + } + Z80Ctrl->ioReadAhead = 1; + } } - // IN A, (N) + // IND ED AA + // INDR ED BA + // INI ED A2 + // INIR ED B2 + else if( opcode == 0xED && (opcode2 == 0xAA || opcode2 == 0xBA || opcode2 == 0xA2 || opcode2 == 0xB2) ) + { + if(isPhysicalIO(Z80CPU.bc.uint16_value)) + { + #if(TARGET_HOST_PCW == 0) + if((Z80CPU.bc.uint16_value&0x00ff) < 8) + { + SPI_SEND_8(CPLD_CMD_READIO_ADDR + (Z80CPU.bc.uint16_value&0x00ff)); + } else + { + SPI_SEND_32(Z80CPU.bc.uint16_value, CPLD_CMD_READIO_ADDR); + } + Z80Ctrl->ioReadAhead = 1; + #else + if((Z80CPU.bc.uint16_value&0x00ff) < 8) + { + SPI_SEND_8(CPLD_CMD_READIO_WRITE_ADDR + (Z80CPU.bc.uint16_value&0x00ff)); + } else + { + //SPI_SEND_32(Z80CPU.bc.uint16_value, CPLD_CMD_READIO_WRITE_ADDR); + SPI_SEND_16((Z80CPU.bc.uint16_value&0x00ff) << 8 | CPLD_CMD_READIO_WRITE_ADDR); + } + + // Send destination address. + SPI_SEND_P_16(Z80CPU.hl.uint16_value); + Z80Ctrl->ioReadAhead = 2; + #endif + } + } + // IN A,(N) DB XX else if(opcode == 0xDB) { - SPI_SEND32( ((Z80CPU.bc.uint16_value & 0xff00) | opcode2) << 16 | CPLD_CMD_READIO_ADDR); - Z80Ctrl->ioReadAhead = 1; + if(isPhysicalIO(opcode2)) + { + if(opcode2 < 8) + { + SPI_SEND_8(CPLD_CMD_READIO_ADDR + opcode2); + } else + { + SPI_SEND_32(((Z80CPU.bc.uint16_value & 0xff00) | opcode2), CPLD_CMD_READIO_ADDR); + // SPI_SEND_16(opcode2 << 8 | CPLD_CMD_READIO_ADDR); + } + //SPI_SEND_32(((Z80CPU.bc.uint16_value & 0xff00) | opcode2), CPLD_CMD_READIO_ADDR); + Z80Ctrl->ioReadAhead = 1; + } } - // OUT (C), r OTDR, OTIR, OUTD, OUTI - else if( (opcode == 0xED && (( (opcode2 & 0x79) != 0) || ((opcode2 & 0xBB) != 0)) && ((opcode2 & 0x01) == 0x01)) ) + + // OUT (C),A ED 79 + // OUT (C),B ED 41 + // OUT (C),C ED 49 + // OUT (C),D ED 51 + // OUT (C),E ED 59 + // OUT (C),H ED 61 + // OUT (C),L ED 69 + // OTDR ED BB + // OTIR ED B3 + // OUTD ED AB + // OUTI ED A3 + else if( opcode == 0xED && (opcode2 == 0x79 || opcode2 == 0x41 || opcode2 == 0x49 || opcode2 == 0x51 || opcode2 == 0x59 || opcode2 == 0x61 || opcode2 == 0x69 || opcode2 == 0xBB || opcode2 == 0xB3 || opcode2 == 0xAB || opcode2 == 0xA3) ) { - SPI_SEND32( (Z80CPU.bc.uint16_value << 16) | ((opcode2 == 0x79 ? Z80CPU.af.uint8_values.at_1 : - opcode2 == 0x41 ? Z80CPU.bc.uint8_values.at_1 : - opcode2 == 0x49 ? Z80CPU.bc.uint8_values.at_0 : - opcode2 == 0x51 ? Z80CPU.de.uint8_values.at_1 : - opcode2 == 0x59 ? Z80CPU.de.uint8_values.at_0 : - opcode2 == 0x61 ? Z80CPU.hl.uint8_values.at_1 : - opcode2 == 0x69 ? Z80CPU.hl.uint8_values.at_0 : - isVirtualROM((Z80CPU.hl.uint16_value)) ? readVirtualROM((Z80CPU.hl.uint16_value)) : readVirtualRAM((Z80CPU.hl.uint16_value))) << 8) - | CPLD_CMD_WRITEIO_ADDR); - Z80Ctrl->ioWriteAhead = 1; + uint16_t data = ((opcode2 == 0x79 ? Z80CPU.af.uint8_values.at_1 : + opcode2 == 0x41 ? Z80CPU.bc.uint8_values.at_1 : + opcode2 == 0x49 ? Z80CPU.bc.uint8_values.at_0 : + opcode2 == 0x51 ? Z80CPU.de.uint8_values.at_1 : + opcode2 == 0x59 ? Z80CPU.de.uint8_values.at_0 : + opcode2 == 0x61 ? Z80CPU.hl.uint8_values.at_1 : + opcode2 == 0x69 ? Z80CPU.hl.uint8_values.at_0 : + isVirtualROM((Z80CPU.hl.uint16_value)) ? readVirtualROM((Z80CPU.hl.uint16_value)) : readVirtualRAM((Z80CPU.hl.uint16_value))) << 8) | CPLD_CMD_WRITEIO_ADDR; + + if(isPhysicalIO(Z80CPU.bc.uint16_value)) + { + if((Z80CPU.bc.uint16_value&0x00ff) < 8) + { + SPI_SEND_16(data + (Z80CPU.bc.uint16_value&0x00ff)); + } else + { + SPI_SEND_32(Z80CPU.bc.uint16_value, data); + } + Z80Ctrl->ioWriteAhead = 1; + } } - // OUT (N), A + + // OUT (N),A D3 XX else if(opcode == 0xD3) { - SPI_SEND32( ((Z80CPU.bc.uint16_value & 0xff00) | opcode2) << 16 | (Z80CPU.af.uint8_values.at_1 << 8) | CPLD_CMD_WRITEIO_ADDR); - Z80Ctrl->ioWriteAhead = 1; + if(isPhysicalIO(((Z80CPU.bc.uint16_value & 0xff00) | opcode2))) + { + if(opcode2 < 8) + { + SPI_SEND_16((Z80CPU.af.uint8_values.at_1 << 8) | (CPLD_CMD_WRITEIO_ADDR + opcode2)); + } else + { + SPI_SEND_32(((Z80CPU.bc.uint16_value & 0xff00) | opcode2), (Z80CPU.af.uint8_values.at_1 << 8) | CPLD_CMD_WRITEIO_ADDR); + } + Z80Ctrl->ioWriteAhead = 1; + } } + + return(Z80Ctrl->ioReadAhead | Z80Ctrl->ioWriteAhead); } //------------------------------------------------------------------------------------------------------------------------------- @@ -522,10 +455,14 @@ static zuint8 z80_read(void *context, zuint16 address) Z_UNUSED(context) // Only read if the address is in physical RAM. + #if(TARGET_HOST_PCW == 0) if(isPhysical(address)) + #else + if(isPhysicalHW(address)) + #endif { // Commence cycle to retrieve the data from Real RAM. - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_READ_ADDR); + SPI_SEND_32(address, CPLD_CMD_READ_ADDR); // Decode address to action any host specific memory map changes. decodeMemoryMapSetup(address, 0, 0, true); @@ -543,6 +480,7 @@ static zuint8 z80_read(void *context, zuint16 address) data = readVirtual(address, 0); } + #if (TARGET_HOST_MZ80A == 1) // Keyport data? Store. if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 0) { @@ -554,8 +492,8 @@ static zuint8 z80_read(void *context, zuint16 address) if((Z80Ctrl->keyportStrobe & 0x0f) == 8 && (data & 0x1D) != 0x1D) { Z80Ctrl->keyportHotKey = (data & 0x01) == 0 ? HOTKEY_ORIGINAL : - (data & 0x04) == 0 ? HOTKEY_RFS80 : - (data & 0x08) == 0 ? HOTKEY_RFS40 : + (data & 0x04) == 0 ? HOTKEY_RFS40 : + (data & 0x08) == 0 ? HOTKEY_RFS80 : (data & 0x10) == 0 ? HOTKEY_LINUX : 0x00; Z80Ctrl->keyportTrigger = Z80Ctrl->keyportHotKey; } else @@ -568,6 +506,27 @@ static zuint8 z80_read(void *context, zuint16 address) Z80Ctrl->keyportTrigger = 0; } } + #elif (TARGET_HOST_MZ700 == 1) + // Keyport data? Store. + if(isHW(address) && address == 0xE001 && (Z80Ctrl->keyportStrobe & 0x0f) == 8) + { + Z80Ctrl->keyportShiftCtrl = (data & 0x40) == 0 ? 0x01 : 0x00; + } else + if(isHW(address) && address == 0xE001 && Z80Ctrl->keyportShiftCtrl == 1) + { + if((Z80Ctrl->keyportStrobe & 0x0f) == 5 && (data & 0xF0) != 0xF0) + { + Z80Ctrl->keyportHotKey = (data & 0x80) == 0 ? HOTKEY_ORIGINAL : + (data & 0x40) == 0 ? HOTKEY_RFS40 : + (data & 0x20) == 0 ? HOTKEY_TZFS : + (data & 0x10) == 0 ? HOTKEY_LINUX : 0x00; + Z80Ctrl->keyportTrigger = Z80Ctrl->keyportHotKey; + } else + { + Z80Ctrl->keyportTrigger = 0; + } + } + #endif #if(DEBUG_ENABLED & 1) if(Z80Ctrl->debug >= 3) @@ -586,17 +545,27 @@ static void z80_write(void *context, zuint16 address, zuint8 data) // Locals. Z_UNUSED(context) + #if (TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) // To detect Hotkey presses, we need to store the keyboard strobe data and on keydata read. if(isHW(address) && address == 0xE000) { Z80Ctrl->keyportStrobe = data; } + #endif // Write to physical host? - if(isPhysical(address)) + if(Z80Ctrl->ioReadAhead == 2) + { + // Write-thru to virtual memory if we update real memory. + if(isPhysicalRAM(address)) + writeVirtualRAM(address, data); + Z80Ctrl->ioReadAhead = 0; + } + else if(isPhysical(address)) { // Commence cycle to write the data to real RAM. - SPI_SEND32((uint32_t)address << 16 | data << 8 | CPLD_CMD_WRITE_ADDR); + SPI_SEND_32(address, data << 8 | CPLD_CMD_WRITE_ADDR); + //SPI_SEND32(address << 16 | data << 8 | CPLD_CMD_WRITE_ADDR); // Write-thru to virtual memory if we update real memory. if(isPhysicalRAM(address)) @@ -639,11 +608,17 @@ static void z80_write(void *context, zuint16 address, zuint8 data) static zuint8 z80_fetch_opcode(void *context, zuint16 address) { // Locals. - zuint8 opcode = 0x00; - volatile uint32_t idx; // Leave as volatile otherwise optimiser will optimise out the delay code. + zuint8 opcode = 0x00; + #if(TARGET_HOST_PCW == 1) + static zuint8 opcodeNxt = 0x00; + #endif + volatile uint32_t idx; // Leave as volatile otherwise optimiser will optimise out the delay code. Z_UNUSED(context) // Normally only opcode fetches occur in RAM but allow any physical address as it could be a Z80 programming trick. + + // PCW machines operate with write-thru < 128K and virtual >= 128K +#if(TARGET_HOST_PCW == 0) #if(TARGET_HOST_MZ80A == 1) // MZ-80A floppy disk controller uses address 0xF3FE/0xF7FE to direct program flow according to READY state of the // MB8866 controller. @@ -653,37 +628,97 @@ static zuint8 z80_fetch_opcode(void *context, zuint16 address) #endif { // Commence cycle to fetch the opcode from potentially Real RAM albeit it could be any physical hardware. - SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_FETCH_ADDR); + SPI_SEND_32(address, CPLD_CMD_FETCH_ADDR); + + // Set up to bypass governor if this is floppy access. + if(address == 0xF3FE) + Z80Ctrl->governorSkip = INSTRUCTION_GOVERNOR_IO_SKIP; + + // Wait for the data and retrieve. while(CPLD_READY() == 0); opcode = z80io_PRL_Read(); - - // Pause until the Last T-State is detected. - //while(CPLD_LAST_TSTATE() == 0); } else +#endif + + #if(TARGET_HOST_PCW == 1) // Virtual fetches only occur in memory as we are not emulating original hardware. if(isVirtualMemory(address)) { - // Delay loop is to govern execution speed to the same as the original host. Timing is primarily based on the main opcode - // fetch so long as the additional opcode parameter and read/write take less time than original host. + // Read the opcode and operand, operand needed for lookahead decisions. if(isVirtualROM(address)) { - opcode = readVirtualROM(address); - for(idx=0; idx < Z80Ctrl->cpuGovernorDelayROM; idx++); + opcode = readVirtualROM(address); + opcodeNxt = readVirtualROM((address+1)); } else { - opcode = readVirtualRAM(address); - for(idx=0; idx < Z80Ctrl->cpuGovernorDelayRAM; idx++); + opcode = readVirtualRAM(address); + opcodeNxt = readVirtualRAM((address+1)); + } + + // Multibyte opcodes, if triggered by an initial I/O read/write ahead operation, return immediately with the opcode. + if(Z80Ctrl->ioReadAhead == 0 && Z80Ctrl->ioWriteAhead == 0) + { + // Check if this operation is I/O or known memory I/O so we can look ahead to optimise sending request to CPLD. + if(!lookAhead(address, opcode, opcodeNxt) && !Z80Ctrl->governorSkip) + { + // Delay loop is to govern execution speed to the same as the original host. Timing is primarily based on the main opcode + // fetch so long as the additional opcode parameter and read/write take less time than original host. + if(isVirtualROM(address)) + { + // opcode = readVirtualROM(address); + for(idx=0; idx < Z80Ctrl->cpuGovernorDelayROM; idx++); + } else + { + // opcode = readVirtualRAM(address); + for(idx=0; idx < Z80Ctrl->cpuGovernorDelayRAM; idx++); + } + } else + { + // If skip is active, we bypass the delay governor in order to catch up on time wasted with the I/O overhead of the SSD202. + if(Z80Ctrl->governorSkip) + { + Z80Ctrl->governorSkip--; + } else + { + Z80Ctrl->governorSkip = INSTRUCTION_GOVERNOR_IO_SKIP; + } + } + } else + { + Z80Ctrl->ioReadAhead = 0; + Z80Ctrl->ioWriteAhead = 0; } } - - // Check if this operation is I/O or known memory I/O so we can look ahead to optimise sending request to CPLD. - lookAhead(address, opcode, isVirtualROM((address+1)) ? readVirtualROM((address+1)) : readVirtualRAM((address+1))); + #else + // Apply delay if required to match emulated CPU to host speed. + if(isVirtualROM(address)) + { + opcode = readVirtualROM(address); + if(Z80Ctrl->governorSkip) + Z80Ctrl->governorSkip--; + else + for(idx=0; idx < Z80Ctrl->cpuGovernorDelayROM; idx++); + } else + if(isVirtualRAM(address)) + { + opcode = readVirtualRAM(address); + if(Z80Ctrl->governorSkip) + Z80Ctrl->governorSkip--; + else + for(idx=0; idx < Z80Ctrl->cpuGovernorDelayRAM; idx++); + } + #endif #if(DEBUG_ENABLED & 1) if(Z80Ctrl->debug >= 3) { - if(address < 0xF036 || address > 0xF197) - pr_info("Fetch:%04x,%02x,%d\n", address, opcode, CPLD_Z80_INT()); + // if(address < 0xF036 || address > 0xF197) + if(address >= 0x0000) + { + pr_info("Fetch:%04x,%02x,%d,%d,%d\n", address, opcode, Z80Ctrl->ioReadAhead, Z80Ctrl->ioWriteAhead, CPLD_Z80_INT()); + udelay(2000); + udelay(1000); + } // If max level, add delay so that the kernel log doesnt overflow. if(Z80Ctrl->debug >= 15) @@ -711,15 +746,6 @@ static zuint8 z80_fetch(void *context, zuint16 address) // Given the limitation of the SigmaStar I/O, it isnt possible to fetch all code from ROM real time, it is therefore cached // and read from cache. data = isPhysicalROM(address) ? readVirtualROM(address) : readVirtualRAM(address); - - // Commence cycle to retrieve the data from Real RAM. - //SPI_SEND32((uint32_t)address << 16 | CPLD_CMD_READ_ADDR); - - //while(CPLD_READY() == 0); - //data = READ_CPLD_DATA_IN(); - - // Pause until the Last T-State is detected. - //while(CPLD_LAST_TSTATE() == 0); } else if(isVirtualMemory(address)) { @@ -739,6 +765,7 @@ static zuint8 z80_fetch(void *context, zuint16 address) { if(address < 0xF036 || address > 0xF197) pr_info("FetchB:%04x,%02x,%d\n", address, data, CPLD_Z80_INT()); + udelay(2000); } #endif return(data); @@ -756,15 +783,26 @@ static zuint8 z80_in(void *context, zuint16 port) // Physical port go direct to hardware to retrieve value. if(isPhysicalIO(port)) { + #if(TARGET_HOST_PCW == 1) if(Z80Ctrl->ioReadAhead == 0) { // Commence cycle to retrieve the value from the I/O port. Port contains the 16bit BC value. - SPI_SEND32((uint32_t)port << 16 | CPLD_CMD_READIO_ADDR); - - // Whilst waiting for the CPLD, we now determine if this is a memory management port and update the memory page if required. - decodeMemoryMapSetup(port, 0, 1, true); + SPI_SEND_32(port, CPLD_CMD_READIO_ADDR); } - Z80Ctrl->ioReadAhead = 0; + + // Whilst waiting for the CPLD, we now determine if this is a memory management port and update the memory page if required. + decodeMemoryMapSetup(port, 0, 1, true); + + if(Z80Ctrl->ioReadAhead != 2) + Z80Ctrl->ioReadAhead = 0; + #else + // Commence cycle to retrieve the value from the I/O port. Port contains the 16bit BC value. + SPI_SEND_32(port, CPLD_CMD_READIO_ADDR); + Z80Ctrl->governorSkip = INSTRUCTION_GOVERNOR_IO_SKIP; + + // Whilst waiting for the CPLD, we now determine if this is a memory management port and update the memory page if required. + decodeMemoryMapSetup(port, 0, 1, true); + #endif // Finally ensure the data from the port is ready and retrieve it. while(CPLD_READY() == 0); @@ -778,8 +816,9 @@ static zuint8 z80_in(void *context, zuint16 port) } #if(DEBUG_ENABLED & 1) - if(Z80Ctrl->debug >= 3) pr_info("z80_in:0x%x, 0x%x\n", port, value); + if(Z80Ctrl->debug >= 2) pr_info("z80_in:0x%x, 0x%x\n", port, value); #endif + // pr_info("z80_in:0x%x, 0x%x\n", port, value); return(value); } @@ -791,24 +830,30 @@ static zuint8 z80_in(void *context, zuint16 port) static void z80_out(void *context, zuint16 port, zuint8 value) { // Locals. - #if(TARGET_HOST_MZ2000 == 1) - uint32_t idx; - #endif Z_UNUSED(context) // Physical port go direct to hardware to retrieve value. if(isPhysicalIO(port)) { + #if(TARGET_HOST_PCW == 1) // If byte has already been written during the fetch phase, skip. if(Z80Ctrl->ioWriteAhead == 0) { // Commence cycle to write the value to the I/O port. Port contains the 16bit BC value. - SPI_SEND32((uint32_t)port << 16 | value << 8 | CPLD_CMD_WRITEIO_ADDR); + SPI_SEND_32(port, value << 8 | CPLD_CMD_WRITEIO_ADDR); } Z80Ctrl->ioWriteAhead = 0; // Decode address to action any host specific memory map changes. decodeMemoryMapSetup(port, value, 1, false); + #else + // Commence cycle to write the value to the I/O port. Port contains the 16bit BC value. + SPI_SEND_32(port, value << 8 | CPLD_CMD_WRITEIO_ADDR); + Z80Ctrl->governorSkip = INSTRUCTION_GOVERNOR_IO_SKIP; + + // Decode address to action any host specific memory map changes. + decodeMemoryMapSetup(port, value, 1, false); + #endif } else if(isVirtualIO(port)) { @@ -817,8 +862,9 @@ static void z80_out(void *context, zuint16 port, zuint8 value) } #if(DEBUG_ENABLED & 1) - if(Z80Ctrl->debug >= 3) pr_info("z80_out:0x%x, 0x%x\n", port, value); + if(Z80Ctrl->debug >= 2) pr_info("z80_out:0x%x, 0x%x\n", port, value); #endif + // pr_info("z80_out:0x%x, 0x%x\n", port, value); } // NOP - No Operation method. This instruction is used for timing, padding out an application or during @@ -834,7 +880,7 @@ static zuint8 z80_nop(void *context, zuint16 address) { // If autorefresh is not enabled, send a single refresh request. if(Z80Ctrl->refreshDRAM == 0) - SPI_SEND8(CPLD_CMD_REFRESH); + SPI_SEND_32(0x0000, CPLD_CMD_REFRESH); } return 0x00; } @@ -848,7 +894,7 @@ static void z80_halt(void *context, zboolean state) // Inform CPLD of halt state. pr_info("z80_halt\n"); - SPI_SEND8(CPLD_CMD_HALT); + SPI_SEND_32(0x0000, CPLD_CMD_HALT); Z80CPU.cycles = Z80_MAXIMUM_CYCLES; } @@ -856,36 +902,48 @@ static void z80_halt(void *context, zboolean state) static zuint8 z80_context(void *context, zuint16 address) { Z_UNUSED(context) - pr_info("z80_context\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 2) pr_info("z80_context\n"); + #endif return 0x00; } static zuint8 z80_nmia(void *context, zuint16 address) { Z_UNUSED(context) - pr_info("z80_nmia\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 2) pr_info("z80_nmia\n"); + #endif return 0x00; } static zuint8 z80_inta(void *context, zuint16 address) { Z_UNUSED(context) - //pr_info("z80_inta\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 2) pr_info("z80_inta\n"); + #endif return 0x00; } static zuint8 z80_intFetch(void *context, zuint16 address) { Z_UNUSED(context) - pr_info("z80_int_fetch\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 2) pr_info("z80_int_fetch\n"); + #endif return 0x00; } static void z80_ldia(void *context) { Z_UNUSED(context) - pr_info("z80_ldia\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 2) pr_info("z80_ldia\n"); + #endif } static void z80_ldra(void *context) { Z_UNUSED(context) - pr_info("z80_ldra\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 2) pr_info("z80_ldra\n"); + #endif } static void z80_reti(void *context) { @@ -907,12 +965,16 @@ static void z80_reti(void *context) static void z80_retn(void *context) { Z_UNUSED(context) - pr_info("z80_retn\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 3) pr_info("z80_retn\n"); + #endif } static zuint8 z80_illegal(void *context, zuint8 opcode) { Z_UNUSED(context) - pr_info("z80_illegal\n"); + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 3) pr_info("z80_illegal\n"); + #endif return 0x00; } @@ -940,7 +1002,7 @@ int thread_z80(void * thread_nr) while(!kthread_should_stop()) { // Run the Z80 emulation if enabled. - if(canRun) z80_run(&Z80CPU, 100); + if(canRun) z80_run(&Z80CPU, 10); // Reset pressed? if(CPLD_RESET()) @@ -959,14 +1021,24 @@ int thread_z80(void * thread_nr) if(Z80RunMode == Z80_RUNNING) canRun=1; else canRun=0; mutex_unlock(&Z80RunModeMutex); + #if (TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) // Hotkey pressed? Bring up user menu. if(Z80Ctrl->keyportTrigger != 0x00 && Z80Ctrl->keyportTriggerLast == 0) { z80menu(); + + // Send signal to arbiter to change run mode. sendSignal(Z80Ctrl->arbTask, SIGUSR1); Z80Ctrl->keyportShiftCtrl = 0; + + // Suspend processing until arbiter sets up new environment. + mutex_lock(&Z80RunModeMutex); + Z80RunMode = Z80_STOPPED; + canRun = 0; + mutex_unlock(&Z80RunModeMutex); } Z80Ctrl->keyportTriggerLast = Z80Ctrl->keyportTrigger; + #endif } } @@ -1231,7 +1303,7 @@ int memoryDump(uint32_t memaddr, uint32_t memsize, uint32_t dispaddr, uint8_t di { if(pnt+i < endAddr) { - SPI_SEND32((uint16_t)(pnt+i) << 16 | CPLD_CMD_READ_ADDR); + SPI_SEND_32((uint16_t)(pnt+i), CPLD_CMD_READ_ADDR); while(CPLD_READY() == 0); data = z80io_PRL_Read(); pr_info(KERN_CONT "%02X", data); @@ -1249,7 +1321,7 @@ int memoryDump(uint32_t memaddr, uint32_t memsize, uint32_t dispaddr, uint8_t di // print single ascii char for (i=0; i < displayWidth; i++) { - SPI_SEND32((uint16_t)(pnt+i) << 16 | CPLD_CMD_READ_ADDR); + SPI_SEND_32((uint16_t)(pnt+i), CPLD_CMD_READ_ADDR); while(CPLD_READY() == 0); c = (char)z80io_PRL_Read(); if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~')) @@ -1280,7 +1352,6 @@ int memoryDump(uint32_t memaddr, uint32_t memsize, uint32_t dispaddr, uint8_t di void setupMemory(enum Z80_MEMORY_PROFILE mode) { // Locals. - uint32_t idx; // Check to see if the memory mode page has been allocated for current mode. if(Z80Ctrl->page[Z80Ctrl->memoryMode] == NULL) @@ -1294,169 +1365,45 @@ void setupMemory(enum Z80_MEMORY_PROFILE mode) } } - // Setup default mode according to run mode, ie. Physical run or Virtual run. - // - if(mode == USE_PHYSICAL_RAM) - { - #if(TARGET_HOST_MZ700 == 1) - #endif - #if(TARGET_HOST_MZ2000 == 1) - // Initialise the page pointers and memory to use physical RAM. - for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x8000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - else //if(idx >= 0x8000 && idx < 0xD000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - - // Video RAM labelled as HW as we dont want to cache it. - //else if(idx >= 0xD000 && idx < 0xE000) - // { - // setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - //} else - // { - // setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - // } - } - #endif - #if(TARGET_HOST_MZ80A == 1) - // Initialise the page pointers and memory to use physical RAM. - for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x1000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } - else if(idx >= 0x1000 && idx < 0xD000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - else if(idx >= 0xD000 && idx < 0xE000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - else if(idx >= 0xE000 && idx < 0xE800) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); - } - else if(idx >= 0xE800 && idx < 0x10000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); - } else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); - } - } - #endif - for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) - { - Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; - } - // Cancel refresh as using physical RAM for program automatically refreshes DRAM. - Z80Ctrl->refreshDRAM = 0; - } - else if(mode == USE_VIRTUAL_RAM) - { - #if(TARGET_HOST_MZ2000 == 1) - // Initialise the page pointers and memory to use virtual RAM. - // MZ-2000 comes up in IPL mode where lower 32K is ROM and upper 32K is RAM remapped from 0x0000. - for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x8000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); - } - else - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (Z80Ctrl->lowMemorySwap ? idx - 0x8000 : idx)); - } - } - for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) - { - Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; - } - // Enable refresh as using virtual RAM stops refresh of host DRAM. - Z80Ctrl->refreshDRAM = 1; - #endif - #if(TARGET_HOST_MZ80A == 1) - // Initialise the page pointers and memory to use virtual RAM. - for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) - { - if(idx >= 0 && idx < 0x1000) - { - setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, idx); - } - else if(idx >= 0x1000 && idx < 0xD000) - { - setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); - } - else if(idx >= 0xD000 && idx < 0xE000) - { - setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_VRAM, idx); - } - else if(idx >= 0xE000 && idx < 0xE800) - { - setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_HW, idx); - } - else if(idx >= 0xE800 && idx < 0xF000) - { - setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_HW, idx); - } - else if(idx >= 0xF000 && idx < 0x10000) - { - setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, idx); - } - } - for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) - { - Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; - } - // Enable refresh as using virtual RAM stops refresh of host DRAM. - Z80Ctrl->refreshDRAM = 0; - #endif - } - // Call driver specific methods to change default memory map per device requirements. #if(TARGET_HOST_MZ80A == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ80A) + mz80aSetupMemory(mode); + else + #endif + #if(TARGET_HOST_MZ700 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ700) + mz700SetupMemory(mode); + else + #endif + #if(TARGET_HOST_MZ2000 == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_MZ2000) + mz2000SetupMemory(mode); + else + #endif + // RFS board only works in an MZ-80A at present. + #if(TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_RFS) rfsSetupMemory(mode); else #endif + // tranZPUter operates in all supported Sharp machines. + #if(TARGET_HOST_PCW == 0) if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_TZPU) tzpuSetupMemory(mode); else + #endif + #if(TARGET_HOST_PCW == 1) + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_PCW) + pcwSetupMemory(mode); + else + #endif { - // Original mode, ie. no virtual devices active, copy the host BIOS into the Virtual ROM and initialise remainder of ROM memory - // such that the host behaves as per original spec. - pr_info("Sync Host BIOS to virtual ROM.\n"); - for(idx=0; idx < Z80_VIRTUAL_ROM_SIZE; idx++) - { - #if(TARGET_HOST_MZ700 == 1) - if(idx >= 0x0000 && idx < 0x1000) - #endif - #if(TARGET_HOST_MZ80A == 1) - if((idx >= 0x0000 && idx < 0x1000) || (idx >= 0xF000 && idx < 0x10000)) - #endif - #if(TARGET_HOST_MZ2000 == 1) - if(idx >= 0x0000 && idx < 0x8000) - #endif - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->rom[idx] = z80io_PRL_Read8(1); - } else - { - Z80Ctrl->rom[idx] = 0x00; - } - } } - // Enable autorefresh if refreshDRAM is set. - SPI_SEND8(Z80Ctrl->refreshDRAM == 1 ? CPLD_CMD_SET_AUTO_REFRESH : CPLD_CMD_CLEAR_AUTO_REFRESH); + // Enable autorefresh if refreshDRAM is set. 0 = disable, 1 = enable, > 1 = ignore and use CPLD default. + if(Z80Ctrl->refreshDRAM < 2) + SPI_SEND_32(0x0000, Z80Ctrl->refreshDRAM == 1 ? CPLD_CMD_SET_AUTO_REFRESH : CPLD_CMD_CLEAR_AUTO_REFRESH); // Inhibit mode disabled. Z80Ctrl->inhibitMode = 0; @@ -1507,8 +1454,8 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) // Command to power on and start the Z80 CPU. case IOCTL_CMD_Z80_START: mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_RUNNING; mutex_unlock(&Z80RunModeMutex); - z80_power(&Z80CPU, TRUE); + #if(DEBUG_ENABLED & 1) if(Z80Ctrl->debug >= 3) pr_info("Z80 started.\n"); #endif @@ -1539,7 +1486,7 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) resetZ80(); - mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); + mutex_lock(&Z80RunModeMutex); Z80RunMode = Z80_RUNNING; mutex_unlock(&Z80RunModeMutex); #if(DEBUG_ENABLED & 1) if(Z80Ctrl->debug >= 3) pr_info("Z80 Reset.\n"); #endif @@ -1590,7 +1537,7 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) // Copy virtual memory to host DRAM. for(idx=0x1000; idx < 0xD000; idx++) { - SPI_SEND32((uint32_t)idx << 16 | Z80Ctrl->ram[idx] << 8 | CPLD_CMD_WRITE_ADDR); + SPI_SEND_32(idx, Z80Ctrl->ram[idx] << 8 | CPLD_CMD_WRITE_ADDR); } mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); @@ -1686,17 +1633,19 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) { if(Z80Ctrl->virtualDevice[idx] == ioctlCmd.vdev.device) { - //pr_info("Virtual Device already installed.\n"); + pr_info("Virtual Device already installed.\n"); break; } } if(idx < Z80Ctrl->virtualDeviceCnt) break; - #if(TARGET_HOST_MZ700 == 1 || TARGET_HOST_MZ2000 == 1) + #if(TARGET_HOST_MZ80A == 0 && TARGET_HOST_MZ700 == 0) if(ioctlCmd.vdev.device & VIRTUAL_DEVICE_RFS) - pr_info("RFS Board currently supported on MZ-80A Host only.\n"); - break; + { + pr_info("RFS Board currently supported on MZ-80A/MZ-700 Hosts only.\n"); + break; + } #endif // Stop the CPU prior to adding virtual device. @@ -1709,6 +1658,14 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) switch(ioctlCmd.vdev.device) { #if(TARGET_HOST_MZ80A == 1) + case VIRTUAL_DEVICE_MZ80A: + Z80Ctrl->virtualDevice[Z80Ctrl->virtualDeviceCnt++] = ioctlCmd.vdev.device; + Z80Ctrl->virtualDeviceBitMap |= ioctlCmd.vdev.device; + mz80aInit(0); + break; + #endif + + #if(TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) case VIRTUAL_DEVICE_RFS40: case VIRTUAL_DEVICE_RFS80: Z80Ctrl->virtualDevice[Z80Ctrl->virtualDeviceCnt++] = ioctlCmd.vdev.device; @@ -1717,23 +1674,51 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) break; #endif + #if(TARGET_HOST_MZ700 == 1) + case VIRTUAL_DEVICE_MZ700: + Z80Ctrl->virtualDevice[Z80Ctrl->virtualDeviceCnt++] = ioctlCmd.vdev.device; + Z80Ctrl->virtualDeviceBitMap |= ioctlCmd.vdev.device; + mz700Init(0); + break; + #endif + + #if(TARGET_HOST_MZ2000 == 1) + case VIRTUAL_DEVICE_MZ2000: + Z80Ctrl->virtualDevice[Z80Ctrl->virtualDeviceCnt++] = ioctlCmd.vdev.device; + Z80Ctrl->virtualDeviceBitMap |= ioctlCmd.vdev.device; + mz2000Init(0); + break; + #endif + + #if(TARGET_HOST_PCW == 0) case VIRTUAL_DEVICE_TZPU: Z80Ctrl->virtualDevice[Z80Ctrl->virtualDeviceCnt++] = VIRTUAL_DEVICE_TZPU; Z80Ctrl->virtualDeviceBitMap |= VIRTUAL_DEVICE_TZPU; tzpuInit(); break; + #endif + + #if(TARGET_HOST_PCW == 1) + case VIRTUAL_DEVICE_PCW: + Z80Ctrl->virtualDevice[Z80Ctrl->virtualDeviceCnt++] = VIRTUAL_DEVICE_PCW; + Z80Ctrl->virtualDeviceBitMap |= VIRTUAL_DEVICE_PCW; + + #ifdef TARGET_HOST_PCW8XXX + pcwInit(0); + #else + pcwInit(1); + #endif + break; + #endif case VIRTUAL_DEVICE_NONE: default: break; } - // Re-initialise the memory map to reflect device removal. - setupMemory(Z80Ctrl->defaultPageMode); - // Z80 can continue. mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); - //pr_info("Virtual device added.\n"); + pr_info("Virtual device added.\n"); break; // Command to remove a device from the Z80 configuration. @@ -1765,6 +1750,13 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) switch(ioctlCmd.vdev.device) { #if(TARGET_HOST_MZ80A == 1) + case VIRTUAL_DEVICE_MZ80A: + Z80Ctrl->virtualDeviceBitMap &= ~ioctlCmd.vdev.device; + mz80aRemove(); + break; + #endif + + #if(TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) case VIRTUAL_DEVICE_RFS40: case VIRTUAL_DEVICE_RFS80: Z80Ctrl->virtualDeviceBitMap &= ~ioctlCmd.vdev.device; @@ -1772,19 +1764,39 @@ static long int z80drv_ioctl(struct file *file, unsigned cmd, unsigned long arg) break; #endif + #if(TARGET_HOST_MZ700 == 1) + case VIRTUAL_DEVICE_MZ700: + Z80Ctrl->virtualDeviceBitMap &= ~ioctlCmd.vdev.device; + mz700Remove(); + break; + #endif + + #if(TARGET_HOST_MZ2000 == 1) + case VIRTUAL_DEVICE_MZ2000: + Z80Ctrl->virtualDeviceBitMap &= ~ioctlCmd.vdev.device; + mz2000Remove(); + break; + #endif + + #if(TARGET_HOST_PCW == 0) case VIRTUAL_DEVICE_TZPU: Z80Ctrl->virtualDeviceBitMap &= ~VIRTUAL_DEVICE_TZPU; tzpuRemove(); break; + #endif + + #if(TARGET_HOST_PCW == 1) + case VIRTUAL_DEVICE_PCW: + Z80Ctrl->virtualDeviceBitMap &= ~VIRTUAL_DEVICE_PCW; + pcwRemove(); + break; + #endif case VIRTUAL_DEVICE_NONE: default: break; } - // Re-initialise the memory map to reflect device removal. - setupMemory(Z80Ctrl->defaultPageMode); - // Z80 can continue. mutex_lock(&Z80RunModeMutex); Z80RunMode = currentRunMode; mutex_unlock(&Z80RunModeMutex); //pr_info("Device removed\n"); @@ -1983,78 +1995,7 @@ static int __init ModuleInit(void) // Initialise the hardware to host interface. z80io_init(); - - // Initialise the virtual RAM from the HOST DRAM. This is to maintain compatibility as some applications (in my experience) have - // bugs, which Im putting down to not initialising variables. The host DRAM is in a pattern of 0x00..0x00, 0xFF..0xFF repeating - // when first powered on. - pr_info("Sync Host RAM to virtual RAM.\n"); - for(idx=0; idx < Z80_VIRTUAL_RAM_SIZE; idx++) - { - #if(TARGET_HOST_MZ700 == 1) - if(idx >= 0x1000 && idx < 0xD000) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->ram[idx] = z80io_PRL_Read8(1); - } else - { - Z80Ctrl->ram[idx] = 0x00; - } - #endif - #if(TARGET_HOST_MZ2000 == 1) - if(idx >= 0x8000 && idx < 0x10000) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->ram[idx-0x8000] = z80io_PRL_Read8(1); - } else - { - if(idx >= 0x0000 && idx < 0x8000) - Z80Ctrl->ram[idx+0x8000] = 0x00; - else - Z80Ctrl->ram[idx] = 0x00; - } - #endif - #if(TARGET_HOST_MZ80A == 1) - if(idx >= 0x1000 && idx < 0xD000) - { - SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); - while(CPLD_READY() == 0); - Z80Ctrl->ram[idx] = z80io_PRL_Read8(1); - } else - { - Z80Ctrl->ram[idx] = 0x00; - } - #endif - } - - // Add in a test program to guage execution speed. - #if(TARGET_HOST_MZ700 == 1) - Z80Ctrl->ram[0x1200] = 0x01; - Z80Ctrl->ram[0x1201] = 0x86; - Z80Ctrl->ram[0x1202] = 0xf2; - Z80Ctrl->ram[0x1203] = 0x3e; - Z80Ctrl->ram[0x1204] = 0x15; - Z80Ctrl->ram[0x1205] = 0x3d; - Z80Ctrl->ram[0x1206] = 0x20; - Z80Ctrl->ram[0x1207] = 0xfd; - Z80Ctrl->ram[0x1208] = 0x0b; - Z80Ctrl->ram[0x1209] = 0x78; - Z80Ctrl->ram[0x120a] = 0xb1; - Z80Ctrl->ram[0x120b] = 0x20; - Z80Ctrl->ram[0x120c] = 0xf6; - Z80Ctrl->ram[0x120d] = 0xc3; - Z80Ctrl->ram[0x120e] = 0x00; - Z80Ctrl->ram[0x120f] = 0x00; - #endif - - #if(TARGET_HOST_MZ2000 == 1) - Z80Ctrl->lowMemorySwap = 1; - #endif - - #if(TARGET_HOST_MZ80A == 1) - Z80Ctrl->memSwitch = 0; - #endif + SPI_SET_FRAME_SIZE(); // Initialise the virtual device array. for(idx=0; idx < MAX_VIRTUAL_DEVICES; idx++) @@ -2071,6 +2012,7 @@ static int __init ModuleInit(void) // Setup the governor delay, it is the delay per opcode fetch to restrict the Z80 CPU to a given speed. Z80Ctrl->cpuGovernorDelayROM = ROM_DELAY_NORMAL; Z80Ctrl->cpuGovernorDelayRAM = RAM_DELAY_NORMAL; + Z80Ctrl->governorSkip = 0; // Setup the default Page Mode. This is needed if an event such as a reset occurs which needs to return the page and iotable back to default. Z80Ctrl->defaultPageMode = USE_VIRTUAL_RAM; @@ -2090,16 +2032,18 @@ static int __init ModuleInit(void) Z80Ctrl->ioReadAhead = 0; Z80Ctrl->ioWriteAhead = 0; + #if (TARGET_HOST_MZ80A == 1 || TARGET_HOST_MZ700 == 1) // Initialse hotkey detection variables. Z80Ctrl->keyportStrobe = 0x00; Z80Ctrl->keyportShiftCtrl = 0x00; Z80Ctrl->keyportHotKey = 0x00; Z80Ctrl->keyportTrigger = 0x00; Z80Ctrl->keyportTriggerLast = 0x00; + #endif - // PC to start and power on the CPU + // PC to start. Z80_PC(Z80CPU) = 0; - z80_power(&Z80CPU, TRUE); + //z80_power(&Z80CPU, TRUE); // Initialise Debug logic if compile time enabled. #if(DEBUG_ENABLED != 0) diff --git a/software/FusionX/src/z80drv/MZ80A/z80driver.h b/software/FusionX/src/z80drv/src/z80driver.h similarity index 78% rename from software/FusionX/src/z80drv/MZ80A/z80driver.h rename to software/FusionX/src/z80drv/src/z80driver.h index c7fe7062b..2c2f3a0cc 100644 --- a/software/FusionX/src/z80drv/MZ80A/z80driver.h +++ b/software/FusionX/src/z80drv/src/z80driver.h @@ -35,16 +35,41 @@ #ifndef Z80DRIVER_H #define Z80DRIVER_H +// Build time target. Overrides if compile time definition given. +#if defined(TARGET_HOST_MZ700) + #define TARGET_HOST_MZ700 1 + #define TARGET_HOST_MZ2000 0 + #define TARGET_HOST_MZ80A 0 + #define TARGET_HOST_PCW 0 +#elif defined(TARGET_HOST_MZ2000) + #define TARGET_HOST_MZ2000 1 + #define TARGET_HOST_MZ700 0 + #define TARGET_HOST_MZ80A 0 + #define TARGET_HOST_PCW 0 +#elif defined(TARGET_HOST_MZ80A) + #define TARGET_HOST_MZ80A 1 + #define TARGET_HOST_MZ2000 0 + #define TARGET_HOST_MZ700 0 + #define TARGET_HOST_PCW 0 +#elif defined(TARGET_HOST_PCW8XXX) || defined(TARGET_HOST_PCW9XXX) + #define TARGET_HOST_PCW 1 + #define TARGET_HOST_MZ2000 0 + #define TARGET_HOST_MZ700 0 + #define TARGET_HOST_MZ80A 0 +#else + #define TARGET_HOST_MZ700 0 // Target compilation for an MZ700 + #define TARGET_HOST_MZ2000 0 // MZ2000 + #define TARGET_HOST_MZ80A 0 // MZ80A + #define TARGET_HOST_PCW 0 // Amstrad PCW8XXX/9XXX +#endif + // Constants. #define DRIVER_LICENSE "GPL" #define DRIVER_AUTHOR "Philip D Smart" #define DRIVER_DESCRIPTION "Z80 CPU Emulator and Hardware Interface Driver" -#define DRIVER_VERSION "v1.3" -#define DRIVER_VERSION_DATE "Feb 2023" +#define DRIVER_VERSION "v1.4" +#define DRIVER_VERSION_DATE "Apr 2023" #define DRIVER_COPYRIGHT "(C) 2018-2023" -#define TARGET_HOST_MZ700 0 // Target compilation for an MZ700 -#define TARGET_HOST_MZ2000 0 // MZ2000 -#define TARGET_HOST_MZ80A 1 // MZ80A #define Z80_VIRTUAL_ROM_SIZE (65536 * 32) // Sized to maximum Kernel contiguous allocation size, 2M which is 4x512K ROMS. #define Z80_VIRTUAL_RAM_SIZE (65536 * 32) // Sized to maximum Kernel contiguous allocation size, 2M. #define Z80_MEMORY_PAGE_SIZE 16 @@ -69,13 +94,14 @@ #define MEMORY_TYPE_VIRTUAL_ROM 0x04000000 #define MEMORY_TYPE_VIRTUAL_RAM_RO 0x02000000 #define MEMORY_TYPE_VIRTUAL_HW 0x01000000 +#define MEMORY_TYPE_PHYSICAL_RAM_WT MEMORY_TYPE_PHYSICAL_RAM | MEMORY_TYPE_VIRTUAL_RAM #define IO_TYPE_PHYSICAL_HW 0x80000000 #define IO_TYPE_VIRTUAL_HW 0x40000000 // Hotkeys handled. #define HOTKEY_ORIGINAL 0xE8 -#define HOTKEY_RFS80 0xE9 -#define HOTKEY_RFS40 0xEA +#define HOTKEY_RFS40 0xE9 +#define HOTKEY_RFS80 0xEA #define HOTKEY_TZFS 0xEB #define HOTKEY_LINUX 0xEC @@ -100,22 +126,42 @@ // Approximate governor delays to regulate emulated CPU speed. // MZ-700 #if(TARGET_HOST_MZ700 == 1) -#define INSTRUCTION_DELAY_ROM_3_54MHZ 253 -#define INSTRUCTION_DELAY_ROM_7MHZ 126 -#define INSTRUCTION_DELAY_ROM_14MHZ 63 -#define INSTRUCTION_DELAY_ROM_28MHZ 32 -#define INSTRUCTION_DELAY_ROM_56MHZ 16 -#define INSTRUCTION_DELAY_ROM_112MHZ 8 -#define INSTRUCTION_DELAY_ROM_224MHZ 4 -#define INSTRUCTION_DELAY_ROM_448MHZ 1 -#define INSTRUCTION_DELAY_RAM_3_54MHZ 253 -#define INSTRUCTION_DELAY_RAM_7MHZ 126 -#define INSTRUCTION_DELAY_RAM_14MHZ 63 -#define INSTRUCTION_DELAY_RAM_28MHZ 32 -#define INSTRUCTION_DELAY_RAM_56MHZ 16 -#define INSTRUCTION_DELAY_RAM_112MHZ 8 -#define INSTRUCTION_DELAY_RAM_224MHZ 4 -#define INSTRUCTION_DELAY_RAM_448MHZ 1 +#if(DEBUG_ENABLED > 0) + #define INSTRUCTION_DELAY_ROM_3_54MHZ 253 + #define INSTRUCTION_DELAY_ROM_7MHZ 126 + #define INSTRUCTION_DELAY_ROM_14MHZ 63 + #define INSTRUCTION_DELAY_ROM_28MHZ 32 + #define INSTRUCTION_DELAY_ROM_56MHZ 16 + #define INSTRUCTION_DELAY_ROM_112MHZ 8 + #define INSTRUCTION_DELAY_ROM_224MHZ 4 + #define INSTRUCTION_DELAY_ROM_448MHZ 1 + #define INSTRUCTION_DELAY_RAM_3_54MHZ 253 + #define INSTRUCTION_DELAY_RAM_7MHZ 126 + #define INSTRUCTION_DELAY_RAM_14MHZ 63 + #define INSTRUCTION_DELAY_RAM_28MHZ 32 + #define INSTRUCTION_DELAY_RAM_56MHZ 16 + #define INSTRUCTION_DELAY_RAM_112MHZ 8 + #define INSTRUCTION_DELAY_RAM_224MHZ 4 + #define INSTRUCTION_DELAY_RAM_448MHZ 1 +#endif +#if(DEBUG_ENABLED == 0) + #define INSTRUCTION_DELAY_ROM_3_54MHZ 253 + #define INSTRUCTION_DELAY_ROM_7MHZ 126 + #define INSTRUCTION_DELAY_ROM_14MHZ 63 + #define INSTRUCTION_DELAY_ROM_28MHZ 32 + #define INSTRUCTION_DELAY_ROM_56MHZ 16 + #define INSTRUCTION_DELAY_ROM_112MHZ 8 + #define INSTRUCTION_DELAY_ROM_224MHZ 4 + #define INSTRUCTION_DELAY_ROM_448MHZ 1 + #define INSTRUCTION_DELAY_RAM_3_54MHZ 253 + #define INSTRUCTION_DELAY_RAM_7MHZ 126 + #define INSTRUCTION_DELAY_RAM_14MHZ 63 + #define INSTRUCTION_DELAY_RAM_28MHZ 32 + #define INSTRUCTION_DELAY_RAM_56MHZ 16 + #define INSTRUCTION_DELAY_RAM_112MHZ 8 + #define INSTRUCTION_DELAY_RAM_224MHZ 4 + #define INSTRUCTION_DELAY_RAM_448MHZ 1 +#endif #define INSTRUCTION_EQUIV_FREQ_3_54MHZ 3540000 #define INSTRUCTION_EQUIV_FREQ_7MHZ 7000000 #define INSTRUCTION_EQUIV_FREQ_14MHZ 14000000 @@ -124,6 +170,7 @@ #define INSTRUCTION_EQUIV_FREQ_112MHZ 112000000 #define INSTRUCTION_EQUIV_FREQ_224MHZ 224000000 #define INSTRUCTION_EQUIV_FREQ_448MHZ 448000000 +#define INSTRUCTION_GOVERNOR_IO_SKIP 10 enum Z80_INSTRUCTION_DELAY { ROM_DELAY_NORMAL = INSTRUCTION_DELAY_ROM_3_54MHZ, @@ -141,7 +188,7 @@ enum Z80_INSTRUCTION_DELAY { RAM_DELAY_X16 = INSTRUCTION_DELAY_RAM_56MHZ, RAM_DELAY_X32 = INSTRUCTION_DELAY_RAM_112MHZ, RAM_DELAY_X64 = INSTRUCTION_DELAY_RAM_224MHZ, - RAM_DELAY_X128 = INSTRUCTION_DELAY_RAM_448MHZ + RAM_DELAY_X128 = INSTRUCTION_DELAY_RAM_448MHZ, CPU_FREQUENCY_NORMAL = INSTRUCTION_EQUIV_FREQ_3_54MHZ, CPU_FREQUENCY_X2 = INSTRUCTION_EQUIV_FREQ_7MHZ, CPU_FREQUENCY_X4 = INSTRUCTION_EQUIV_FREQ_14MHZ, @@ -155,22 +202,43 @@ enum Z80_INSTRUCTION_DELAY { // MZ-2000 #if(TARGET_HOST_MZ2000 == 1) -#define INSTRUCTION_DELAY_ROM_4MHZ 243 -#define INSTRUCTION_DELAY_ROM_8MHZ 122 -#define INSTRUCTION_DELAY_ROM_16MHZ 61 -#define INSTRUCTION_DELAY_ROM_32MHZ 30 -#define INSTRUCTION_DELAY_ROM_64MHZ 15 -#define INSTRUCTION_DELAY_ROM_128MHZ 7 -#define INSTRUCTION_DELAY_ROM_256MHZ 3 -#define INSTRUCTION_DELAY_ROM_512MHZ 1 -#define INSTRUCTION_DELAY_RAM_4MHZ 218 -#define INSTRUCTION_DELAY_RAM_8MHZ 112 -#define INSTRUCTION_DELAY_RAM_16MHZ 56 -#define INSTRUCTION_DELAY_RAM_32MHZ 28 -#define INSTRUCTION_DELAY_RAM_64MHZ 14 -#define INSTRUCTION_DELAY_RAM_128MHZ 7 -#define INSTRUCTION_DELAY_RAM_256MHZ 3 -#define INSTRUCTION_DELAY_RAM_512MHZ 1 + +#if(DEBUG_ENABLED > 0) + #define INSTRUCTION_DELAY_ROM_4MHZ 213 + #define INSTRUCTION_DELAY_ROM_8MHZ 109 + #define INSTRUCTION_DELAY_ROM_16MHZ 54 + #define INSTRUCTION_DELAY_ROM_32MHZ 27 + #define INSTRUCTION_DELAY_ROM_64MHZ 14 + #define INSTRUCTION_DELAY_ROM_128MHZ 7 + #define INSTRUCTION_DELAY_ROM_256MHZ 3 + #define INSTRUCTION_DELAY_ROM_512MHZ 1 + #define INSTRUCTION_DELAY_RAM_4MHZ 212 + #define INSTRUCTION_DELAY_RAM_8MHZ 106 + #define INSTRUCTION_DELAY_RAM_16MHZ 53 + #define INSTRUCTION_DELAY_RAM_32MHZ 26 + #define INSTRUCTION_DELAY_RAM_64MHZ 13 + #define INSTRUCTION_DELAY_RAM_128MHZ 7 + #define INSTRUCTION_DELAY_RAM_256MHZ 3 + #define INSTRUCTION_DELAY_RAM_512MHZ 1 +#endif +#if(DEBUG_ENABLED == 0) + #define INSTRUCTION_DELAY_ROM_4MHZ 295 + #define INSTRUCTION_DELAY_ROM_8MHZ 148 + #define INSTRUCTION_DELAY_ROM_16MHZ 74 + #define INSTRUCTION_DELAY_ROM_32MHZ 37 + #define INSTRUCTION_DELAY_ROM_64MHZ 19 + #define INSTRUCTION_DELAY_ROM_128MHZ 10 + #define INSTRUCTION_DELAY_ROM_256MHZ 5 + #define INSTRUCTION_DELAY_ROM_512MHZ 3 + #define INSTRUCTION_DELAY_RAM_4MHZ 240 // These values are smaller than the ROM as Rom has 1 wait state added per cycle. + #define INSTRUCTION_DELAY_RAM_8MHZ 148 + #define INSTRUCTION_DELAY_RAM_16MHZ 74 + #define INSTRUCTION_DELAY_RAM_32MHZ 37 + #define INSTRUCTION_DELAY_RAM_64MHZ 19 + #define INSTRUCTION_DELAY_RAM_128MHZ 10 + #define INSTRUCTION_DELAY_RAM_256MHZ 5 + #define INSTRUCTION_DELAY_RAM_512MHZ 3 +#endif #define INSTRUCTION_EQUIV_FREQ_4MHZ 4000000 #define INSTRUCTION_EQUIV_FREQ_8MHZ 8000000 #define INSTRUCTION_EQUIV_FREQ_16MHZ 16000000 @@ -179,6 +247,7 @@ enum Z80_INSTRUCTION_DELAY { #define INSTRUCTION_EQUIV_FREQ_128MHZ 128000000 #define INSTRUCTION_EQUIV_FREQ_256MHZ 256000000 #define INSTRUCTION_EQUIV_FREQ_512MHZ 512000000 +#define INSTRUCTION_GOVERNOR_IO_SKIP 4 enum Z80_INSTRUCTION_DELAY { ROM_DELAY_NORMAL = INSTRUCTION_DELAY_ROM_4MHZ, @@ -258,6 +327,7 @@ enum Z80_INSTRUCTION_DELAY { #define INSTRUCTION_EQUIV_FREQ_64MHZ 64000000 #define INSTRUCTION_EQUIV_FREQ_128MHZ 128000000 #define INSTRUCTION_EQUIV_FREQ_256MHZ 256000000 +#define INSTRUCTION_GOVERNOR_IO_SKIP 2 // Table of governor delays to be used to control run frequency, enum Z80_INSTRUCTION_DELAY { @@ -288,6 +358,82 @@ enum Z80_INSTRUCTION_DELAY { }; #endif +// Amstrad PCW-8256 +#if(TARGET_HOST_PCW == 1) +#if(DEBUG_ENABLED > 0) + #define INSTRUCTION_DELAY_ROM_4MHZ 295 + #define INSTRUCTION_DELAY_ROM_8MHZ 148 + #define INSTRUCTION_DELAY_ROM_16MHZ 74 + #define INSTRUCTION_DELAY_ROM_32MHZ 37 + #define INSTRUCTION_DELAY_ROM_64MHZ 19 + #define INSTRUCTION_DELAY_ROM_128MHZ 10 + #define INSTRUCTION_DELAY_ROM_256MHZ 5 + #define INSTRUCTION_DELAY_ROM_512MHZ 3 + #define INSTRUCTION_DELAY_RAM_4MHZ 240 + #define INSTRUCTION_DELAY_RAM_8MHZ 148 + #define INSTRUCTION_DELAY_RAM_16MHZ 74 + #define INSTRUCTION_DELAY_RAM_32MHZ 37 + #define INSTRUCTION_DELAY_RAM_64MHZ 19 + #define INSTRUCTION_DELAY_RAM_128MHZ 10 + #define INSTRUCTION_DELAY_RAM_256MHZ 5 + #define INSTRUCTION_DELAY_RAM_512MHZ 3 +#endif +#if(DEBUG_ENABLED == 0) + #define INSTRUCTION_DELAY_ROM_4MHZ 295 + #define INSTRUCTION_DELAY_ROM_8MHZ 148 + #define INSTRUCTION_DELAY_ROM_16MHZ 74 + #define INSTRUCTION_DELAY_ROM_32MHZ 37 + #define INSTRUCTION_DELAY_ROM_64MHZ 19 + #define INSTRUCTION_DELAY_ROM_128MHZ 10 + #define INSTRUCTION_DELAY_ROM_256MHZ 5 + #define INSTRUCTION_DELAY_ROM_512MHZ 3 + #define INSTRUCTION_DELAY_RAM_4MHZ 240 + #define INSTRUCTION_DELAY_RAM_8MHZ 148 + #define INSTRUCTION_DELAY_RAM_16MHZ 74 + #define INSTRUCTION_DELAY_RAM_32MHZ 37 + #define INSTRUCTION_DELAY_RAM_64MHZ 19 + #define INSTRUCTION_DELAY_RAM_128MHZ 10 + #define INSTRUCTION_DELAY_RAM_256MHZ 5 + #define INSTRUCTION_DELAY_RAM_512MHZ 3 +#endif +#define INSTRUCTION_EQUIV_FREQ_4MHZ 4000000 +#define INSTRUCTION_EQUIV_FREQ_8MHZ 8000000 +#define INSTRUCTION_EQUIV_FREQ_16MHZ 16000000 +#define INSTRUCTION_EQUIV_FREQ_32MHZ 32000000 +#define INSTRUCTION_EQUIV_FREQ_64MHZ 64000000 +#define INSTRUCTION_EQUIV_FREQ_128MHZ 128000000 +#define INSTRUCTION_EQUIV_FREQ_256MHZ 256000000 +#define INSTRUCTION_EQUIV_FREQ_512MHZ 512000000 +#define INSTRUCTION_GOVERNOR_IO_SKIP 5 + +enum Z80_INSTRUCTION_DELAY { + ROM_DELAY_NORMAL = INSTRUCTION_DELAY_ROM_4MHZ, + ROM_DELAY_X2 = INSTRUCTION_DELAY_ROM_8MHZ, + ROM_DELAY_X4 = INSTRUCTION_DELAY_ROM_16MHZ, + ROM_DELAY_X8 = INSTRUCTION_DELAY_ROM_32MHZ, + ROM_DELAY_X16 = INSTRUCTION_DELAY_ROM_64MHZ, + ROM_DELAY_X32 = INSTRUCTION_DELAY_ROM_128MHZ, + ROM_DELAY_X64 = INSTRUCTION_DELAY_ROM_256MHZ, + ROM_DELAY_X128 = INSTRUCTION_DELAY_ROM_512MHZ, + RAM_DELAY_NORMAL = INSTRUCTION_DELAY_RAM_4MHZ, + RAM_DELAY_X2 = INSTRUCTION_DELAY_RAM_8MHZ, + RAM_DELAY_X4 = INSTRUCTION_DELAY_RAM_16MHZ, + RAM_DELAY_X8 = INSTRUCTION_DELAY_RAM_32MHZ, + RAM_DELAY_X16 = INSTRUCTION_DELAY_RAM_64MHZ, + RAM_DELAY_X32 = INSTRUCTION_DELAY_RAM_128MHZ, + RAM_DELAY_X64 = INSTRUCTION_DELAY_RAM_256MHZ, + RAM_DELAY_X128 = INSTRUCTION_DELAY_RAM_512MHZ, + CPU_FREQUENCY_NORMAL = INSTRUCTION_EQUIV_FREQ_4MHZ, + CPU_FREQUENCY_X2 = INSTRUCTION_EQUIV_FREQ_8MHZ, + CPU_FREQUENCY_X4 = INSTRUCTION_EQUIV_FREQ_16MHZ, + CPU_FREQUENCY_X8 = INSTRUCTION_EQUIV_FREQ_32MHZ, + CPU_FREQUENCY_X16 = INSTRUCTION_EQUIV_FREQ_64MHZ, + CPU_FREQUENCY_X32 = INSTRUCTION_EQUIV_FREQ_128MHZ, + CPU_FREQUENCY_X64 = INSTRUCTION_EQUIV_FREQ_256MHZ, + CPU_FREQUENCY_X128 = INSTRUCTION_EQUIV_FREQ_512MHZ, +}; +#endif + // IOCTL commands. Passed from user space using the IOCTL method to command the driver to perform an action. #define IOCTL_CMD_Z80_STOP 's' #define IOCTL_CMD_Z80_START 'S' @@ -426,9 +572,12 @@ enum Z80_INSTRUCTION_DELAY { }\ } #define resetZ80() {\ - if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_TZPU)\ - sendSignal(Z80Ctrl->ioTask, SIGUSR1); \ setupMemory(Z80Ctrl->defaultPageMode);\ + if(Z80Ctrl->virtualDeviceBitMap & VIRTUAL_DEVICE_TZPU)\ + {\ + sendSignal(Z80Ctrl->ioTask, SIGUSR1); \ + udelay(2000);\ + }\ z80_instant_reset(&Z80CPU);\ } @@ -460,10 +609,14 @@ enum Z80_MEMORY_PROFILE { }; enum VIRTUAL_DEVICE { VIRTUAL_DEVICE_NONE = 0x00000000, + VIRTUAL_DEVICE_MZ80A = 0x00000001, + VIRTUAL_DEVICE_MZ700 = 0x00000002, + VIRTUAL_DEVICE_MZ2000 = 0x00000004, + VIRTUAL_DEVICE_PCW = 0x00000008, VIRTUAL_DEVICE_RFS40 = 0x01000000, VIRTUAL_DEVICE_RFS80 = 0x02000000, VIRTUAL_DEVICE_RFS = 0x03000000, - VIRTUAL_DEVICE_TZPU = 0x04000000 + VIRTUAL_DEVICE_TZPU = 0x04000000, }; typedef struct { @@ -524,14 +677,6 @@ typedef struct { uint8_t ioReadAhead; uint8_t ioWriteAhead; - #if(TARGET_HOST_MZ2000 == 1) - uint8_t lowMemorySwap; - #endif - #if(TARGET_HOST_MZ80A == 1) - // MZ-80A can relocate the lower 4K ROM by swapping RAM at 0xC000. - uint8_t memSwitch; - #endif - // Keyboard strobe and data. Required to detect hotkey press. uint8_t keyportStrobe; uint8_t keyportShiftCtrl; @@ -546,6 +691,7 @@ typedef struct { // is quicker than RAM (both are in the same kernel memory) as a pointer calculation needs to be made. uint32_t cpuGovernorDelayROM; uint32_t cpuGovernorDelayRAM; + uint8_t governorSkip; // An I/O processor, running as a User Space daemon, can register to receive signals and events. struct task_struct *ioTask; diff --git a/software/FusionX/src/z80drv/MZ80A/z80io.c b/software/FusionX/src/z80drv/src/z80io.c similarity index 100% rename from software/FusionX/src/z80drv/MZ80A/z80io.c rename to software/FusionX/src/z80drv/src/z80io.c diff --git a/software/FusionX/src/z80drv/MZ80A/z80io.h b/software/FusionX/src/z80drv/src/z80io.h similarity index 79% rename from software/FusionX/src/z80drv/MZ80A/z80io.h rename to software/FusionX/src/z80drv/src/z80io.h index 641cea5ed..5639da632 100755 --- a/software/FusionX/src/z80drv/MZ80A/z80io.h +++ b/software/FusionX/src/z80drv/src/z80io.h @@ -82,6 +82,14 @@ #define CPLD_CMD_READIO_ADDR_P5 0x35 #define CPLD_CMD_READIO_ADDR_P6 0x36 #define CPLD_CMD_READIO_ADDR_P7 0x37 +#define CPLD_CMD_READIO_WRITE_ADDR 0x38 +#define CPLD_CMD_READIO_WRITE_ADDR_P1 0x39 +#define CPLD_CMD_READIO_WRITE_ADDR_P2 0x3A +#define CPLD_CMD_READIO_WRITE_ADDR_P3 0x3B +#define CPLD_CMD_READIO_WRITE_ADDR_P4 0x3C +#define CPLD_CMD_READIO_WRITE_ADDR_P5 0x3D +#define CPLD_CMD_READIO_WRITE_ADDR_P6 0x3E +#define CPLD_CMD_READIO_WRITE_ADDR_P7 0x3F #define CPLD_CMD_HALT 0x50 #define CPLD_CMD_REFRESH 0x51 #define CPLD_CMD_SET_SIGROUP1 0xF0 @@ -130,7 +138,7 @@ #define PAD_SPIO_2_ADDR 0x103C14 #define PAD_SPIO_3_ADDR 0x103C16 #define PAD_Z80IO_HIGH_BYTE_ADDR 0x1425 -#define PAD_Z80IO_READY_ADDR 0x103C18 +#define PAD_Z80IO_READY_ADDR 0x103C18 // GPIO12 #define PAD_Z80IO_LTSTATE_ADDR 0x103C30 // GPIO47 #define PAD_Z80IO_BUSRQ_ADDR 0x103C1A #define PAD_Z80IO_BUSACK_ADDR 0x103C1C @@ -386,8 +394,8 @@ #define CPLD_LAST_TSTATE() (MHal_RIU_REG(PAD_Z80IO_LTSTATE_ADDR) & 0x4) #define CPLD_Z80_INT() (MHal_RIU_REG(PAD_Z80IO_INT_ADDR) & 0x4) #define CPLD_Z80_NMI() (MHal_RIU_REG(PAD_Z80IO_NMI_ADDR) & 0x4) -#define SPI_SEND8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ +#define SPI_SEND_8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d_); \ MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); \ while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ @@ -396,9 +404,18 @@ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE);\ } +#define SPI_SEND_I_8(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d_); \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 1); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE);\ + } #define SPI_SEND16(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ @@ -406,10 +423,9 @@ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ } -#define SPI_SEND32(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ +#define SPI_SEND_16(_d1_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d1_); \ while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ @@ -417,24 +433,63 @@ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ } -#define SPI_SEND32i(_d_) { uint32_t timeout = MAX_CHECK_CNT; \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ - MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ - MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ - pr_info("Stage 0");\ - while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0);\ - pr_info("Stage 1");\ +#define SPI_SEND_P_16(_d1_) { uint32_t timeout = MAX_CHECK_CNT; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 2); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d1_); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) != 0);\ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ - pr_info("Stage 2");\ - timeout = MAX_CHECK_CNT; \ - while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; }; \ - pr_info("Stage 3");\ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + } +#define SPI_SET_FRAME_SIZE() { MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + } +#define SPI_SEND32(_d_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)(_d_)); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)((_d_) >> 16)); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; };\ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + } +#define SPI_SEND_32(_d1_, _d2_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d2_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)_d1_); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; };\ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + } +#define SPI_SEND_I_32(_d1_, _d2_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 4); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d2_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)_d1_); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ + MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ + } +#define SPI_SEND_48(_d1_, _d2_, _d3_) { uint32_t timeout = MAX_CHECK_CNT*2; \ + MSPI_WRITE(MSPI_WBF_SIZE_OFFSET, 6); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET, (uint16_t)_d3_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+1, (uint16_t)_d2_); \ + MSPI_WRITE(MSPI_WRITE_BUF_OFFSET+2, (uint16_t)_d1_); \ + MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_ENABLE); \ + while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; };\ + MSPI_WRITE(MSPI_TRIGGER_OFFSET, MSPI_TRIGGER); \ + while((MSPI_READ(MSPI_DONE_OFFSET) & MSPI_DONE_FLAG) == 0) { if(--timeout == 0) break; } \ MSPI_WRITE(MSPI_CHIP_SELECT_OFFSET, MSPI_CS8_DISABLE | MSPI_CS7_DISABLE | MSPI_CS6_DISABLE | MSPI_CS5_DISABLE | MSPI_CS4_DISABLE | MSPI_CS3_DISABLE | MSPI_CS2_DISABLE | MSPI_CS1_DISABLE); \ MSPI_WRITE(MSPI_DONE_CLEAR_OFFSET, MSPI_CLEAR_DONE); \ } - // while((MHal_RIU_REG(PAD_Z80IO_READY_ADDR) & 0x1) == 0) { if(--timeout == 0) break; }; // read 2 byte #define MSPI_READ(_reg_) READ_WORD(gMspBaseAddr + ((_reg_)<<2)) // write 2 byte diff --git a/software/FusionX/src/z80drv/MZ80A/z80io_test.c b/software/FusionX/src/z80drv/src/z80io_test.c similarity index 98% rename from software/FusionX/src/z80drv/MZ80A/z80io_test.c rename to software/FusionX/src/z80drv/src/z80io_test.c index ac7346b57..afaeed03b 100644 --- a/software/FusionX/src/z80drv/MZ80A/z80io_test.c +++ b/software/FusionX/src/z80drv/src/z80io_test.c @@ -54,7 +54,7 @@ uint8_t z80io_Z80_TestMemory(void) spinlock_t spinLock; unsigned long flags; - SPI_SEND8(CPLD_CMD_CLEAR_AUTO_REFRESH); + SPI_SEND_8(CPLD_CMD_CLEAR_AUTO_REFRESH); SPI_SEND32(0x00E30000 | (0x07 << 8) | CPLD_CMD_WRITEIO_ADDR); udelay(100); @@ -185,7 +185,7 @@ uint8_t z80io_Z80_TestMemory(void) // Read back the same byte. cmd = 0x10; - SPI_SEND8(cmd); + SPI_SEND_8(cmd); while(CPLD_READY() == 0); result = READ_CPLD_DATA_IN(); @@ -223,7 +223,7 @@ uint8_t z80io_Z80_TestMemory(void) // Read back the same byte. cmd = 0x20; - SPI_SEND8(cmd); + SPI_SEND_8(cmd); while(CPLD_READY() == 0); result = READ_CPLD_DATA_IN(); @@ -254,7 +254,7 @@ uint8_t z80io_Z80_TestMemory(void) } else { cmd = 0x11; - SPI_SEND8(cmd); + SPI_SEND_8(cmd); } while(CPLD_READY() == 0); result = READ_CPLD_DATA_IN(); @@ -280,7 +280,7 @@ uint8_t z80io_Z80_TestMemory(void) } else { cmd = 0x21; - SPI_SEND8(cmd); + SPI_SEND_8(cmd); } while(CPLD_READY() == 0); result = READ_CPLD_DATA_IN(); @@ -306,7 +306,7 @@ uint8_t z80io_Z80_TestMemory(void) } else { cmd = 0x19; - SPI_SEND8(cmd); + SPI_SEND_8(cmd); } } for(idx=0; idx < iterations; idx++) @@ -322,7 +322,7 @@ uint8_t z80io_Z80_TestMemory(void) } else { cmd = 0x19; - SPI_SEND8(cmd); + SPI_SEND_8(cmd); } } } diff --git a/software/FusionX/src/z80drv/MZ2000/z80menu.c b/software/FusionX/src/z80drv/src/z80menu.c similarity index 100% rename from software/FusionX/src/z80drv/MZ2000/z80menu.c rename to software/FusionX/src/z80drv/src/z80menu.c diff --git a/software/FusionX/src/z80drv/MZ2000/z80menu.h b/software/FusionX/src/z80drv/src/z80menu.h similarity index 100% rename from software/FusionX/src/z80drv/MZ2000/z80menu.h rename to software/FusionX/src/z80drv/src/z80menu.h diff --git a/software/FusionX/src/z80drv/src/z80vhw_mz2000.c b/software/FusionX/src/z80drv/src/z80vhw_mz2000.c new file mode 100644 index 000000000..fb0c51c0d --- /dev/null +++ b/software/FusionX/src/z80drv/src/z80vhw_mz2000.c @@ -0,0 +1,436 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: z80vhw_mz2000.c +// Created: Oct 2022 +// Author(s): Philip Smart +// Description: Z80 Virtual Hardware Driver - MZ-2000 +// This file contains the methods used to emulate the original Sharp MZ-2000 without +// any additions, such as the RFS or TZFS boards. +// +// These drivers are intended to be instantiated inline to reduce overhead of a call +// and as such, they are included like header files rather than C linked object files. +// Credits: +// Copyright: (c) 2019-2023 Philip Smart +// +// History: Mar 2023 v1.0 - Initial write based on the RFS hardware module. +// +// Notes: See Makefile to enable/disable conditional components +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// This source file is free software: you can redistribute it and#or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "z80io.h" + +#include +#include +#include +#include + +// Device constants. +#define RAM_BASE_ADDR 0x00000 // Base address of the 512K RAM. + +// System ROM's, either use the host machine ROM or preload a ROM image. +#define ROM_DIR "/apps/FusionX/host/MZ-2000/ROMS/" +#define ROM_IPL_ORIG_FILENAME ROM_DIR "mz2000_ipl.orig" + +// Boot ROM rom load and size definitions. +#define ROM_BOOT_LOAD_ADDR 0x000000 +#define ROM_BOOT_SIZE 0x800 + +// PCW control. +typedef struct { + uint8_t lowMemorySwap; // Boot mode lower memory is swapped to 0x8000:0xFFFF + uint8_t highMemoryVRAM; // Flag to indicate high memory range 0xD000:0xFFFF is assigned to VRAM. + uint8_t graphicsVRAM; // Flag to indicate graphics VRAM selected, default is character VRAM (0). + uint8_t regCtrl; // Control register. +} t_MZ2000Ctrl; + +// RFS Board control. +static t_MZ2000Ctrl MZ2000Ctrl; + +//------------------------------------------------------------------------------------------------------------------------------- +// +// +//------------------------------------------------------------------------------------------------------------------------------- + +// Method to setup the memory page config to reflect the PCW configuration. +void mz2000SetupMemory(enum Z80_MEMORY_PROFILE mode) +{ + // Locals. + uint32_t idx; + + // The PCW contains upto 512KB of standard RAM which can be expnded to a physical max of 2MB. The kernel malloc limit is 2MB so the whole virtual + // memory can be mapped into the PCW memory address range. + + // Setup defaults. + MZ2000Ctrl.lowMemorySwap = 0x01; // Set memory swap flag to swapped, ie. IPL mode sees DRAM 0x0000:0x7FFF swapped to 0x8000:0xFFFF and ROM pages into 0x0000. + MZ2000Ctrl.highMemoryVRAM = 0x00; + MZ2000Ctrl.graphicsVRAM = 0x00; + MZ2000Ctrl.regCtrl = 0x00; + + // Setup default mode according to run mode, ie. Physical run or Virtual run. + // + if(mode == USE_PHYSICAL_RAM) + { + // Initialise the page pointers and memory to use physical RAM. + for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx >= 0 && idx < 0x8000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); + } + else //if(idx >= 0x8000 && idx < 0xD000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + } + + // Video RAM labelled as HW as we dont want to cache it. + //else if(idx >= 0xD000 && idx < 0xE000) + // { + // setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + //} else + // { + // setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + // } + } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + // Cancel refresh as using physical RAM for program automatically refreshes DRAM. + Z80Ctrl->refreshDRAM = 0; + } + else if(mode == USE_VIRTUAL_RAM) + { + // Initialise the page pointers and memory to use virtual RAM. + // MZ-2000 comes up in IPL mode where lower 32K is ROM and upper 32K is RAM remapped from 0x0000. + for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx >= 0 && idx < 0x8000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); + } + else + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (MZ2000Ctrl.lowMemorySwap ? idx - 0x8000 : idx)); + } + } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + // Enable refresh as using virtual RAM stops refresh of host DRAM. + Z80Ctrl->refreshDRAM = 2; + } + + pr_info("MZ-2000 Memory Setup complete.\n"); +} + +// Method to load a ROM image into the RAM memory. +// +uint8_t mz2000LoadROM(const char* romFileName, uint32_t loadAddr, uint32_t loadSize) +{ + // Locals. + uint8_t result = 0; + long noBytes; + struct file *fp; + + fp = filp_open(romFileName, O_RDONLY, 0); + if(IS_ERR(fp)) + { + pr_info("Error opening ROM Image:%s\n:", romFileName); + result = 1; + } else + { + vfs_llseek(fp, 0, SEEK_SET); + noBytes = kernel_read(fp, fp->f_pos, &Z80Ctrl->ram[loadAddr], loadSize); + if(noBytes < loadSize) + { + pr_info("Short load, ROM Image:%s, bytes loaded:%08x\n:", romFileName, loadSize); + } + filp_close(fp,NULL); + } + + return(result); +} + +// Perform any setup operations, such as variable initialisation, to enable use of this module. +void mz2000Init(uint8_t mode) +{ + // Locals. + uint32_t idx; + + // Initialise the virtual RAM from the HOST DRAM. This is to maintain compatibility as some applications (in my experience) have + // bugs, which Im putting down to not initialising variables. The host DRAM is in a pattern of 0x00..0x00, 0xFF..0xFF repeating + // when first powered on. + pr_info("Sync Host RAM to virtual RAM.\n"); + for(idx=0; idx < Z80_VIRTUAL_RAM_SIZE; idx++) + { + // Lower memory is actually upper on startup, but ROM paged in, so set to zero. + if(idx >= 0x0000 && idx < 0x8000) + { + Z80Ctrl->ram[idx+0x8000] = 0x00; + } else + // Lower memory is paged in at 0x8000:0xFFFF + if(idx >= 0x8000 && idx < 0x10000) + { + SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); + while(CPLD_READY() == 0); + Z80Ctrl->ram[idx-0x8000] = z80io_PRL_Read8(1); + } else + { + Z80Ctrl->ram[idx] = 0x00; + } + } + + // Original mode, ie. no virtual devices active, copy the host BIOS into the Virtual ROM and initialise remainder of ROM memory + // such that the host behaves as per original spec. + pr_info("Sync Host BIOS to virtual ROM.\n"); + for(idx=0; idx < Z80_VIRTUAL_ROM_SIZE; idx++) + { + if(idx >= 0x0000 && idx < 0x8000) + { + SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); + while(CPLD_READY() == 0); + Z80Ctrl->rom[idx] = z80io_PRL_Read8(1); + } else + { + Z80Ctrl->rom[idx] = 0x00; + } + } + + // Initial memory config. + mz2000SetupMemory(Z80Ctrl->defaultPageMode); + + // mz2000LoadROM(ROM_IPL_ORIG_FILENAME, ROM_BOOT_LOAD_ADDR, ROM_BOOT_SIZE); + + pr_info("Enabling MZ-2000 driver.\n"); + return; +} + +// Perform any de-initialisation when the driver is removed. +void mz2000Remove(void) +{ + pr_info("Removing MZ-2000 driver.\n"); + return; +} + +// Method to decode an address and make any system memory map changes as required. +// +static inline void mz2000DecodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t ioFlag, uint8_t readFlag) +{ + // Locals. + uint32_t idx; + + // Decoding memory address or I/O address? + if(ioFlag == 0) + { + // Certain machines have memory mapped I/O, these need to be handled in-situ as some reads may change the memory map. + // These updates are made whilst waiting for the CPLD to retrieve the requested byte. + // + switch(address) + { + default: + break; + } + } else + { + // Determine if this is a memory management port and update the memory page if required. + switch(address & 0x00FF) + { + // 8255 - Port A + case IO_ADDR_E0: + break; + + // 8255 - Port B + case IO_ADDR_E1: + break; + + // 8255 - Port C + // Bit 3 - L = Reset and enter IPL mode. + // Bit 1 - H = Set memory to normal state and reset cpu, RAM 0x0000:0xFFFF, L = no change. + case IO_ADDR_E2: + if(data & 0x01) + data = 0x03; + else if((data & 0x08) == 0) + data = 0x06; + else + break; + + // 8255 - Control Port + // Bit 7 - H = Control word, L 3:1 define port C bit, bit 0 defines its state. + case IO_ADDR_E3: +//pr_info("E3:%02x\n", data); + // Program control register. + if(data & 0x80) + { + // Do nothing, this is the register which sets the 8255 mode. + } else + { + switch((data >> 1) & 0x07) + { + // NST toggle. + case 1: + // NST pages in all RAM and resets cpu. + if(data & 0x01) + { + MZ2000Ctrl.lowMemorySwap = 0; + for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(Z80Ctrl->defaultPageMode == USE_PHYSICAL_RAM) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + } + else + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } + } + //resetZ80(); + } + break; + + // IPL start. + case 3: + // If IPL is active (L), reconfigure memory for power on state. + if((data & 0x01) == 0) + { + mz2000SetupMemory(Z80Ctrl->defaultPageMode); + } + break; + + default: + break; + } + } + break; + + // Port A - Z80 PIO, contains control bits affecting memory mapping. + // Bit + // 7 - Assign address range 0xD000:0xFFFF to V-RAM when H, when L assign RAM + // 6 - Character VRAM (H), Graphics VRAM (L) + // 4 - Change screen to 80 Char (H), 40 Char (L) + // NB. When the VRAM is paged in, if Character VRAM is selected, range 0xD000:0xD7FF is VRAM, 0xC000:0xCFFF, 0xE000:0xFFFF is RAM. + case IO_ADDR_E8: + // High memory being assigned to VRAM or reverting? + if(MZ2000Ctrl.highMemoryVRAM && (data & 0x80) == 0) + { + for(idx=0xC000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(Z80Ctrl->defaultPageMode == USE_PHYSICAL_RAM) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + } else + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (MZ2000Ctrl.lowMemorySwap ? idx - 0x8000 : idx)); + } + } + MZ2000Ctrl.highMemoryVRAM = 0; + } else + // If this is the first activation of the VRAM or the state of it changes, ie. character <-> graphics, then update the memory mapping. + if( (!MZ2000Ctrl.highMemoryVRAM && (data & 0x80) != 0) || (MZ2000Ctrl.highMemoryVRAM && (MZ2000Ctrl.graphicsVRAM >> 6) != (data & 0x40)) ) + { + for(idx=0xC000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + // Graphics RAM see's the entire range set to PHYSICAL, Character RAM only see's 0xD000:0xD7FF set to PHYSICAL. + if( ((data & 0x40) && (idx >= 0xD000 && idx < 0xD800)) || ((data & 0x40) == 0) ) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + } + MZ2000Ctrl.highMemoryVRAM = 1; + } + MZ2000Ctrl.graphicsVRAM = (data & 0x040) ? 1 : 0; + break; + + // Port is not a memory management port. + default: + break; + } + } +} + +// Method to read from either the memory mapped registers if enabled else the RAM. +static inline uint8_t mz2000Read(zuint16 address, uint8_t ioFlag) +{ + // Locals. + uint8_t data = 0xFF; + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + default: + break; + } + } else + { + switch(address) + { + default: + if(isVirtualMemory(address)) + { + // Retrieve data from virtual memory. + data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); + } + break; + } + } + + return(data); +} + +// Method to handle writes. +static inline void mz2000Write(zuint16 address, zuint8 data, uint8_t ioFlag) +{ + // Locals. + // uint32_t idx; + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + default: + break; + } + } else + { + switch(address) + { + default: + if(isVirtualRAM(address)) + { + // Update virtual memory. + writeVirtualRAM(address, data); + } + } + } + return; +} diff --git a/software/FusionX/src/z80drv/src/z80vhw_mz700.c b/software/FusionX/src/z80drv/src/z80vhw_mz700.c new file mode 100644 index 000000000..de229b19f --- /dev/null +++ b/software/FusionX/src/z80drv/src/z80vhw_mz700.c @@ -0,0 +1,481 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: z80vhw_mz700.c +// Created: Oct 2022 +// Author(s): Philip Smart +// Description: Z80 Virtual Hardware Driver - MZ-700 +// This file contains the methods used to emulate the original Sharp MZ-700 without +// any additions, such as the RFS or TZFS boards. +// +// These drivers are intended to be instantiated inline to reduce overhead of a call +// and as such, they are included like header files rather than C linked object files. +// Credits: +// Copyright: (c) 2019-2023 Philip Smart +// +// History: Mar 2023 v1.0 - Initial write based on the RFS hardware module. +// Apr 2023 v1.1 - Updates from the PCW/MZ2000 changes. +// +// Notes: See Makefile to enable/disable conditional components +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// This source file is free software: you can redistribute it and#or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "z80io.h" + +#include +#include +#include +#include + +// Device constants. +#define RAM_BASE_ADDR 0x00000 // Base address of the 512K RAM. + +// PCW control. +typedef struct { + uint8_t regCtrl; // Control register. +} t_MZ700Ctrl; + +// RFS Board control. +static t_MZ700Ctrl MZ700Ctrl; + +//------------------------------------------------------------------------------------------------------------------------------- +// +// +//------------------------------------------------------------------------------------------------------------------------------- + +// Method to setup the memory page config to reflect the PCW configuration. +void mz700SetupMemory(enum Z80_MEMORY_PROFILE mode) +{ + // Locals. + uint32_t idx; + + // The PCW contains upto 512KB of standard RAM which can be expnded to a physical max of 2MB. The kernel malloc limit is 2MB so the whole virtual + // memory can be mapped into the PCW memory address range. + + // Setup defaults. + MZ700Ctrl.regCtrl = 0x00; + + // Setup default mode according to run mode, ie. Physical run or Virtual run. + // + if(mode == USE_PHYSICAL_RAM) + { + // Initialise the page pointers and memory to use physical RAM. + for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx >= 0 && idx < 0x1000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); + } + else if(idx >= 0x1000 && idx < 0xD000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + } + else if(idx >= 0xD000 && idx < 0xE000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + else if(idx >= 0xE000 && idx < 0xE800) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); + } + else if(idx >= 0xE800 && idx < 0x10000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); + } else + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + } + } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + // Cancel refresh as using physical RAM for program automatically refreshes DRAM. + Z80Ctrl->refreshDRAM = 0; + } + else if(mode == USE_VIRTUAL_RAM) + { + // Initialise the page pointers and memory to use virtual RAM. + for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx >= 0 && idx < 0x1000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, idx); + } + else if(idx >= 0x1000 && idx < 0xD000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } + else if(idx >= 0xD000 && idx < 0xE000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + else if(idx >= 0xE000 && idx < 0xE800) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_HW, idx); + } + else if(idx >= 0xE800 && idx < 0xF000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, idx); + } + else if(idx >= 0xF000 && idx < 0x10000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, idx); + } + } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + // Enable refresh as using virtual RAM stops refresh of host DRAM. + Z80Ctrl->refreshDRAM = 2; + } + + // Reset memory paging to default. + SPI_SEND_32(0x00e4, 0x00 << 8 | CPLD_CMD_WRITEIO_ADDR); + + pr_info("MZ-700 Memory Setup complete.\n"); +} + +// Method to load a ROM image into the RAM memory. +// +uint8_t mz700LoadROM(const char* romFileName, uint32_t loadAddr, uint32_t loadSize) +{ + // Locals. + uint8_t result = 0; + long noBytes; + struct file *fp; + + fp = filp_open(romFileName, O_RDONLY, 0); + if(IS_ERR(fp)) + { + pr_info("Error opening ROM Image:%s\n:", romFileName); + result = 1; + } else + { + vfs_llseek(fp, 0, SEEK_SET); + noBytes = kernel_read(fp, fp->f_pos, &Z80Ctrl->ram[loadAddr], loadSize); + if(noBytes < loadSize) + { + pr_info("Short load, ROM Image:%s, bytes loaded:%08x\n:", romFileName, loadSize); + } + filp_close(fp,NULL); + } + + return(result); +} + +// Perform any setup operations, such as variable initialisation, to enable use of this module. +void mz700Init(uint8_t mode) +{ + // Locals. + uint32_t idx; + + // Initialise the virtual RAM from the HOST DRAM. This is to maintain compatibility as some applications (in my experience) have + // bugs, which Im putting down to not initialising variables. The host DRAM is in a pattern of 0x00..0x00, 0xFF..0xFF repeating + // when first powered on. + pr_info("Sync Host RAM to virtual RAM.\n"); + for(idx=0; idx < Z80_VIRTUAL_RAM_SIZE; idx++) + { + if(idx >= 0x1000 && idx < 0xD000) + { + SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); + while(CPLD_READY() == 0); + Z80Ctrl->ram[idx] = z80io_PRL_Read8(1); + } else + { + Z80Ctrl->ram[idx] = 0x00; + } + } + + // Original mode, ie. no virtual devices active, copy the host BIOS into the Virtual ROM and initialise remainder of ROM memory + // such that the host behaves as per original spec. + pr_info("Sync Host BIOS to virtual ROM.\n"); + for(idx=0; idx < Z80_VIRTUAL_ROM_SIZE; idx++) + { + // Copy BIOS and any add-on ROMS. + if((idx >= 0x0000 && idx < 0x1000) || (idx >= 0xE800 && idx < 0x10000)) + { + SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); + while(CPLD_READY() == 0); + Z80Ctrl->rom[idx] = z80io_PRL_Read8(1); + } else + { + Z80Ctrl->rom[idx] = 0x00; + } + } + + // Add in a test program to guage execution speed. + #if(TARGET_HOST_MZ700 == 1) + Z80Ctrl->ram[0x1200] = 0x01; + Z80Ctrl->ram[0x1201] = 0x86; + Z80Ctrl->ram[0x1202] = 0xf2; + Z80Ctrl->ram[0x1203] = 0x3e; + Z80Ctrl->ram[0x1204] = 0x15; + Z80Ctrl->ram[0x1205] = 0x3d; + Z80Ctrl->ram[0x1206] = 0x20; + Z80Ctrl->ram[0x1207] = 0xfd; + Z80Ctrl->ram[0x1208] = 0x0b; + Z80Ctrl->ram[0x1209] = 0x78; + Z80Ctrl->ram[0x120a] = 0xb1; + Z80Ctrl->ram[0x120b] = 0x20; + Z80Ctrl->ram[0x120c] = 0xf6; + Z80Ctrl->ram[0x120d] = 0xc3; + Z80Ctrl->ram[0x120e] = 0x00; + Z80Ctrl->ram[0x120f] = 0x00; + #endif + + // Reset memory paging to default. + SPI_SEND_32(0x00e4, 0x00 << 8 | CPLD_CMD_WRITEIO_ADDR); + + pr_info("Enabling MZ-700 driver.\n"); + return; +} + +// Perform any de-initialisation when the driver is removed. +void mz700Remove(void) +{ + pr_info("Removing MZ-700 driver.\n"); + return; +} + +// Method to decode an address and make any system memory map changes as required. +// +static inline void mz700DecodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t ioFlag, uint8_t readFlag) +{ + // Locals. + uint32_t idx; + + // Decoding memory address or I/O address? + if(ioFlag == 0) + { + // #if(DEBUG_ENABLED & 1) + // if(Z80Ctrl->debug >= 2) + // { + // pr_info("MEM:%04x,%02x,%d,%d\n", address, data, ioFlag, readFlag); + // } + // #endif + // Certain machines have memory mapped I/O, these need to be handled in-situ as some reads may change the memory map. + // These updates are made whilst waiting for the CPLD to retrieve the requested byte. + // + // 0000 - 0FFF : MZ80K/A/700 = Monitor ROM or RAM (MZ80A rom swap) + // 1000 - CFFF : MZ80K/A/700 = RAM + // C000 - CFFF : MZ80A = Monitor ROM (MZ80A rom swap) + // D000 - D7FF : MZ80K/A/700 = VRAM + // D800 - DFFF : MZ700 = Colour VRAM (MZ700) + // E000 - E003 : MZ80K/A/700 = 8255 + // E004 - E007 : MZ80K/A/700 = 8254 + // E008 - E00B : MZ80K/A/700 = LS367 + // E00C - E00F : MZ80A = Memory Swap (MZ80A) + // E010 - E013 : MZ80A = Reset Memory Swap (MZ80A) + // E014 : MZ80A/700 = Normat CRT display + // E015 : MZ80A/700 = Reverse CRT display + // E200 - E2FF : MZ80A/700 = VRAM roll up/roll down. + // E800 - EFFF : MZ80K/A/700 = User ROM socket or DD Eprom (MZ700) + // F000 - F7FF : MZ80K/A/700 = Floppy Disk interface. + // F800 - FFFF : MZ80K/A/700 = Floppy Disk interface. + switch(address) + { + default: + break; + } + } else + { + // #if(DEBUG_ENABLED & 1) + // if(Z80Ctrl->debug >= 2) + // { + // pr_info("IO:%04x,%02x,%d,%d\n", address, data, ioFlag, readFlag); + // } + // #endif + + // Determine if this is a memory management port and update the memory page if required. + switch(address & 0x00FF) + { + // MZ700 memory mode switch. + // + // MZ-700 + // |0000:0FFF|1000:CFFF|D000:FFFF + // ------------------------------ + // OUT 0xE0 = |DRAM | | + // OUT 0xE1 = | | |DRAM + // OUT 0xE2 = |MONITOR | | + // OUT 0xE3 = | | |Memory Mapped I/O + // OUT 0xE4 = |MONITOR |DRAM |Memory Mapped I/O + // OUT 0xE5 = | | |Inhibit + // OUT 0xE6 = | | | + // + // = Return to the state prior to the complimentary command being invoked. + // Enable lower 4K block as DRAM + case IO_ADDR_E0: + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } + break; + + // Enable upper 12K block, including Video/Memory Mapped peripherals area, as DRAM. + case IO_ADDR_E1: + if(!Z80Ctrl->inhibitMode) + { + for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + // MZ-700 mode we only work in first 64K block. + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } + } + break; + + // Enable MOnitor ROM in lower 4K block + case IO_ADDR_E2: + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); + } + break; + + // Enable Video RAM and Memory mapped peripherals in upper 12K block. + case IO_ADDR_E3: + if(!Z80Ctrl->inhibitMode) + { + for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); + } + } + break; + + // Reset to power on condition memory map. + case IO_ADDR_E4: + // Lower 4K set to Monitor ROM. + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); + } + if(!Z80Ctrl->inhibitMode) + { + // Upper 12K to hardware. + for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); + } + } + break; + + // Inhibit. Backup current page data in region 0xD000-0xFFFF and inhibit it. + case IO_ADDR_E5: + for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + backupMemoryType(idx/MEMORY_BLOCK_GRANULARITY); + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_INHIBIT, idx); + } + Z80Ctrl->inhibitMode = 1; + break; + + // Restore D000-FFFF to its original state. + case IO_ADDR_E6: + for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + restoreMemoryType(idx/MEMORY_BLOCK_GRANULARITY); + } + Z80Ctrl->inhibitMode = 0; + break; + + // Port is not a memory management port. + default: + break; + } + } +} + +// Method to read from either the memory mapped registers if enabled else the RAM. +static inline uint8_t mz700Read(zuint16 address, uint8_t ioFlag) +{ + // Locals. + uint8_t data = 0xFF; + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + default: + break; + } + } else + { + switch(address) + { + default: + if(isVirtualMemory(address)) + { + // Retrieve data from virtual memory. + data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); + } + break; + } + } + + return(data); +} + +// Method to handle writes. +static inline void mz700Write(zuint16 address, zuint8 data, uint8_t ioFlag) +{ + // Locals. + // uint32_t idx; + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + default: + break; + } + } else + { + switch(address) + { + default: + if(isVirtualRAM(address)) + { + // Update virtual memory. + writeVirtualRAM(address, data); + } + } + } + return; +} diff --git a/software/FusionX/src/z80drv/src/z80vhw_mz80a.c b/software/FusionX/src/z80drv/src/z80vhw_mz80a.c new file mode 100644 index 000000000..e9b80ab68 --- /dev/null +++ b/software/FusionX/src/z80drv/src/z80vhw_mz80a.c @@ -0,0 +1,382 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: z80vhw_mz80a.c +// Created: Oct 2022 +// Author(s): Philip Smart +// Description: Z80 Virtual Hardware Driver - MZ-80A +// This file contains the methods used to emulate the original Sharp MZ-80A without +// any additions, such as the RFS or TZFS boards. +// +// These drivers are intended to be instantiated inline to reduce overhead of a call +// and as such, they are included like header files rather than C linked object files. +// Credits: +// Copyright: (c) 2019-2023 Philip Smart +// +// History: Mar 2023 v1.0 - Initial write based on the RFS hardware module. +// +// Notes: See Makefile to enable/disable conditional components +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// This source file is free software: you can redistribute it and#or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "z80io.h" + +#include +#include +#include +#include + +// Device constants. +#define RAM_BASE_ADDR 0x00000 // Base address of the 512K RAM. + +// 40/80 Video Module control registers. +// +#define DSPCTL 0xDFFF // Display 40/80 select register (bit 7) + +// PCW control. +typedef struct { + // MZ-80A can relocate the lower 4K ROM by swapping RAM at 0xC000. + uint8_t memSwitch; + + uint8_t regCtrl; // Control register. +} t_MZ80ACtrl; + +// RFS Board control. +static t_MZ80ACtrl MZ80ACtrl; + +//------------------------------------------------------------------------------------------------------------------------------- +// +// +//------------------------------------------------------------------------------------------------------------------------------- + +// Method to setup the memory page config to reflect the PCW configuration. +void mz80aSetupMemory(enum Z80_MEMORY_PROFILE mode) +{ + // Locals. + uint32_t idx; + + // Setup defaults. + MZ80ACtrl.memSwitch = 0x00; + MZ80ACtrl.regCtrl = 0x00; + + // Setup default mode according to run mode, ie. Physical run or Virtual run. + // + if(mode == USE_PHYSICAL_RAM) + { + // Initialise the page pointers and memory to use physical RAM. + for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx >= 0 && idx < 0x1000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); + } + else if(idx >= 0x1000 && idx < 0xD000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + } + else if(idx >= 0xD000 && idx < 0xE000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + else if(idx >= 0xE000 && idx < 0xE800) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); + } + else if(idx >= 0xE800 && idx < 0x10000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_ROM, idx); + } else + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM, idx); + } + } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + + // Cancel refresh as using physical RAM for program automatically refreshes DRAM. + Z80Ctrl->refreshDRAM = 0; + } + else if(mode == USE_VIRTUAL_RAM) + { + // Initialise the page pointers and memory to use virtual RAM. + for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx >= 0 && idx < 0x1000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, idx); + } + else if(idx >= 0x1000 && idx < 0xD000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } + else if(idx >= 0xD000 && idx < 0xE000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + else if(idx >= 0xE000 && idx < 0xE800) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_HW, idx); + } + else if(idx >= 0xE800 && idx < 0xF000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_HW, idx); + } + else if(idx >= 0xF000 && idx < 0x10000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, idx); + } + } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + + // Enable refresh as using virtual RAM stops refresh of host DRAM. + Z80Ctrl->refreshDRAM = 2; + } + + // Original mode, ie. no virtual devices active, copy the host BIOS into the Virtual ROM and initialise remainder of ROM memory + // such that the host behaves as per original spec. + pr_info("Sync Host BIOS to virtual ROM.\n"); + for(idx=0; idx < Z80_VIRTUAL_ROM_SIZE; idx++) + { + if((idx >= 0x0000 && idx < 0x1000) || (idx >= 0xF000 && idx < 0x10000)) + { + SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); + while(CPLD_READY() == 0); + Z80Ctrl->rom[idx] = z80io_PRL_Read8(1); + } else + { + Z80Ctrl->rom[idx] = 0x00; + } + } + + pr_info("MZ-80A Memory Setup complete.\n"); +} + +// Method to load a ROM image into the RAM memory. +// +uint8_t mz80aLoadROM(const char* romFileName, uint32_t loadAddr, uint32_t loadSize) +{ + // Locals. + uint8_t result = 0; + long noBytes; + struct file *fp; + + fp = filp_open(romFileName, O_RDONLY, 0); + if(IS_ERR(fp)) + { + pr_info("Error opening ROM Image:%s\n:", romFileName); + result = 1; + } else + { + vfs_llseek(fp, 0, SEEK_SET); + noBytes = kernel_read(fp, fp->f_pos, &Z80Ctrl->ram[loadAddr], loadSize); + if(noBytes < loadSize) + { + pr_info("Short load, ROM Image:%s, bytes loaded:%08x\n:", romFileName, loadSize); + } + filp_close(fp,NULL); + } + + return(result); +} + +// Perform any setup operations, such as variable initialisation, to enable use of this module. +void mz80aInit(uint8_t mode) +{ + // Locals. + uint32_t idx; + + // Initialise the virtual RAM from the HOST DRAM. This is to maintain compatibility as some applications (in my experience) have + // bugs, which Im putting down to not initialising variables. The host DRAM is in a pattern of 0x00..0x00, 0xFF..0xFF repeating + // when first powered on. + pr_info("Sync Host RAM to virtual RAM.\n"); + for(idx=0; idx < Z80_VIRTUAL_RAM_SIZE; idx++) + { + if(idx >= 0x1000 && idx < 0xD000) + { + SPI_SEND32((uint32_t)idx << 16 | CPLD_CMD_READ_ADDR); + while(CPLD_READY() == 0); + Z80Ctrl->ram[idx] = z80io_PRL_Read8(1); + } else + { + Z80Ctrl->ram[idx] = 0x00; + } + } + MZ80ACtrl.memSwitch = 0; + + // If the 40/80 Video Module board is installed, ensure 40 character mode is selected. + SPI_SEND_32(DSPCTL, CPLD_CMD_READ_ADDR); + while(CPLD_READY() == 0); + SPI_SEND_32(DSPCTL, CPLD_CMD_WRITE_ADDR); + + pr_info("Enabling MZ-80A driver.\n"); + return; +} + +// Perform any de-initialisation when the driver is removed. +void mz80aRemove(void) +{ + pr_info("Removing MZ-80A driver.\n"); + return; +} + +// Method to decode an address and make any system memory map changes as required. +// +static inline void mz80aDecodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t ioFlag, uint8_t readFlag) +{ + // Locals. + uint32_t idx; + + // Decoding memory address or I/O address? + if(ioFlag == 0) + { + // Certain machines have memory mapped I/O, these need to be handled in-situ as some reads may change the memory map. + // These updates are made whilst waiting for the CPLD to retrieve the requested byte. + // + // 0000 - 0FFF : MZ80K/A/700 = Monitor ROM or RAM (MZ80A rom swap) + // 1000 - CFFF : MZ80K/A/700 = RAM + // C000 - CFFF : MZ80A = Monitor ROM (MZ80A rom swap) + // D000 - D7FF : MZ80K/A/700 = VRAM + // D800 - DFFF : MZ700 = Colour VRAM (MZ700) + // E000 - E003 : MZ80K/A/700 = 8255 + // E004 - E007 : MZ80K/A/700 = 8254 + // E008 - E00B : MZ80K/A/700 = LS367 + // E00C - E00F : MZ80A = Memory Swap (MZ80A) + // E010 - E013 : MZ80A = Reset Memory Swap (MZ80A) + // E014 : MZ80A/700 = Normat CRT display + // E015 : MZ80A/700 = Reverse CRT display + // E200 - E2FF : MZ80A/700 = VRAM roll up/roll down. + // E800 - EFFF : MZ80K/A/700 = User ROM socket or DD Eprom (MZ700) + // F000 - F7FF : MZ80K/A/700 = Floppy Disk interface. + // F800 - FFFF : MZ80K/A/700 = Floppy Disk interface. + switch(address) + { + // Memory map switch. + case 0xE00C: case 0xE00D: case 0xE00E: case 0xE00F: + if(readFlag) + { + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (0xC000+idx)); + setMemoryType((idx+0xC000)/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); + } + } + MZ80ACtrl.memSwitch = 1; + break; + + // Reset memory map switch. + case 0xE010: case 0xE011: case 0xE012: case 0xE013: + if(readFlag) + { + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); + setMemoryType((idx+0xC000)/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (idx+0xC000)); + } + } + MZ80ACtrl.memSwitch = 0; + break; + + default: + break; + } + } else + { + // Determine if this is a memory management port and update the memory page if required. + switch(address & 0x00FF) + { + // Port is not a memory management port. + default: + break; + } + } +} + +// Method to read from either the memory mapped registers if enabled else the RAM. +static inline uint8_t mz80aRead(zuint16 address, uint8_t ioFlag) +{ + // Locals. + uint8_t data = 0xFF; + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + default: + break; + } + } else + { + switch(address) + { + default: + if(isVirtualMemory(address)) + { + // Retrieve data from virtual memory. + data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); + } + break; + } + } + + return(data); +} + +// Method to handle writes. +static inline void mz80aWrite(zuint16 address, zuint8 data, uint8_t ioFlag) +{ + // Locals. + // uint32_t idx; + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + default: + break; + } + } else + { + switch(address) + { + default: + if(isVirtualRAM(address)) + { + // Update virtual memory. + writeVirtualRAM(address, data); + } + } + } + return; +} diff --git a/software/FusionX/src/z80drv/src/z80vhw_pcw.c b/software/FusionX/src/z80drv/src/z80vhw_pcw.c new file mode 100644 index 000000000..d10117d4b --- /dev/null +++ b/software/FusionX/src/z80drv/src/z80vhw_pcw.c @@ -0,0 +1,379 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: z80vhw_pcw.c +// Created: Oct 2022 +// Author(s): Philip Smart +// Description: Z80 Virtual Hardware Driver - Amstrad PCW-8xxx/PCW-9xxx +// This file contains the methods used to emulate the Amstrad PCW specific +// hardware. +// +// These drivers are intended to be instantiated inline to reduce overhead of a call +// and as such, they are included like header files rather than C linked object files. +// Credits: +// Copyright: (c) 2019-2023 Philip Smart +// +// History: Mar 2023 v1.0 - Initial write based on the RFS hardware module. +// +// Notes: See Makefile to enable/disable conditional components +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// This source file is free software: you can redistribute it and#or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "z80io.h" + +#include +#include +#include +#include + +// Device constants. +#define RAM_BASE_ADDR 0x00000 // Base address of the 512K RAM. + +// IO Ports. +#define IO_FDC_STATUS 0x00 // NEC765 FDC Status Register. +#define IO_FDC_DATA 0x01 // NEC765 FDC Data Register. +#define IO_MEMBNK0 0xF0 // Memory bank 0000:3FFF register. +#define IO_MEMBNK1 0xF1 // Memory bank 4000:7FFF register. +#define IO_MEMBNK2 0xF2 // Memory bank 8000:BFFF register. +#define IO_MEMBNK3 0xF3 // Memory bank C000:FFFF register. +#define IO_MEMLOCK 0xF4 // CPC mode memory lock range. +#define IO_ROLLERRAM 0xF5 // Set the Roller RAM address. +#define IO_VORIGIN 0xF6 // Set screen vertical origin. +#define IO_SCREENATTR 0xF7 // Set screen attributes. +#define IO_GACMD 0xF8 // Gatearray command register. +#define IO_GASTATUS 0xF8 // Gatearray status register. + +// The boot code for the PCW-8256 is located within the printer controller. To avoid special hardware within the CPLD, this code is incorporated +// into this module for rapid loading into RAM. +#define ROM_DIR "/apps/FusionX/host/PCW/roms/" +#define ROM_PCW8_BOOT_FILENAME ROM_DIR "PCW8256_boot.bin" +#define ROM_PCW9_BOOT_FILENAME ROM_DIR "PCW9256_boot.bin" + +// Boot ROM rom load and size definitions. +#define ROM_BOOT_LOAD_ADDR 0x000000 +#define ROM_BOOT_SIZE 275 + +// PCW control. +typedef struct { + uint8_t regMemBank0; // Mirror of register F0, memory block select 0x0000-0x3FFF. + uint8_t regMemBank1; // Mirror of register F1, memory block select 0x4000-0x7FFF. + uint8_t regMemBank2; // Mirror of register F2, memory block select 0x8000-0xBFFF. + uint8_t regMemBank3; // Mirror of register F3, memory block select 0xC000-0xFFFF. + uint8_t regCPCPageMode; // Mirror of the CPC paging lock register F4. + uint8_t regRollerRAM; // Mirror of Roller-RAM address register. + uint8_t regCtrl; // Control register. +} t_PCWCtrl; + +// RFS Board control. +static t_PCWCtrl PCWCtrl; + +//------------------------------------------------------------------------------------------------------------------------------- +// +// +//------------------------------------------------------------------------------------------------------------------------------- + +// Method to setup the memory page config to reflect the PCW configuration. +void pcwSetupMemory(enum Z80_MEMORY_PROFILE mode) +{ + // Locals. + uint32_t idx; + + // The PCW contains upto 512KB of standard RAM which can be expnded to a physical max of 2MB. The kernel malloc limit is 2MB so the whole virtual + // memory can be mapped into the PCW memory address range. + + // Setup defaults. + PCWCtrl.regMemBank0 = 0x00; + PCWCtrl.regMemBank1 = 0x01; + PCWCtrl.regMemBank2 = 0x02; + PCWCtrl.regMemBank3 = 0x03; // Keyboard is in locations 0x3FF0 - 0x3FFF of this memory block. + PCWCtrl.regCPCPageMode = 0x00; + PCWCtrl.regRollerRAM = 0x00; + PCWCtrl.regCtrl = 0x00; + + // Initialise the page pointers and memory to reflect a PCW, lower 128K is used by video logic so must always be accessed in hardware. + for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx >= 0x0000 && idx < 0xFFF0) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_RAM_WT, (RAM_BASE_ADDR+idx)); + } + if(idx >= 0xFFF0 && idx < 0x10000) + { + // The keyboard is memory mapped into upper bytes of block 3. + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, (RAM_BASE_ADDR+idx)); + } + } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + + // Enable refresh as using virtual RAM stops refresh of host DRAM. + Z80Ctrl->refreshDRAM = 2; + + // No I/O Ports on the RFS board. + pr_info("PCW Memory Setup complete.\n"); +} + +// Method to load a ROM image into the RAM memory. +// +uint8_t loadROM(const char* romFileName, uint32_t loadAddr, uint32_t loadSize) +{ + // Locals. + uint8_t result = 0; + long noBytes; + struct file *fp; + + fp = filp_open(romFileName, O_RDONLY, 0); + if(IS_ERR(fp)) + { + pr_info("Error opening ROM Image:%s\n:", romFileName); + result = 1; + } else + { + vfs_llseek(fp, 0, SEEK_SET); + noBytes = kernel_read(fp, fp->f_pos, &Z80Ctrl->ram[loadAddr], loadSize); + if(noBytes < loadSize) + { + pr_info("Short load, ROM Image:%s, bytes loaded:%08x\n:", romFileName, loadSize); + } + filp_close(fp,NULL); + } + + return(result); +} + +// Perform any setup operations, such as variable initialisation, to enable use of this module. +void pcwInit(uint8_t mode) +{ + // Locals. + // + uint32_t idx; + + // Clear memory as previous use or malloc can leave it randomly set. + for(idx=0; idx < Z80_VIRTUAL_RAM_SIZE; idx++) + { + Z80Ctrl->ram[idx] = 0x00; + } + + // Disable boot mode, we dont need to fetch the boot rom as we preload it. + SPI_SEND32( (0x00F8 << 16) | (0x00 << 8) | CPLD_CMD_WRITEIO_ADDR); + + // Load boot ROM. + loadROM(mode == 0 ? ROM_PCW8_BOOT_FILENAME : ROM_PCW9_BOOT_FILENAME, ROM_BOOT_LOAD_ADDR, ROM_BOOT_SIZE); + + // Reset. + //SPI_SEND32( (0x00F8 << 16) | (0x01 << 8) | CPLD_CMD_WRITEIO_ADDR); + + // First two bytes to NULL as were not using the bootstrap and normal operations after bootstrap would disable the mode. + Z80Ctrl->ram[0] = 0x00; + Z80Ctrl->ram[1] = 0x00; + + pr_info("Enabling PCW-%s driver.\n", mode == 0 ? "8256" : "9256"); + return; +} + +// Perform any de-initialisation when the driver is removed. +void pcwRemove(void) +{ + pr_info("Removing PCW driver.\n"); + return; +} + +// Method to decode an address and make any system memory map changes as required. +// +static inline void pcwDecodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t ioFlag, uint8_t readFlag) +{ + // Locals. + uint32_t idx; + + // IO Switch. + if(ioFlag) + { + switch(address&0xff) + { + case IO_FDC_STATUS: +//pr_info("FDC_STATUS:%02x\n", data); + break; + + case IO_FDC_DATA: +//pr_info("FDC_DATA:%02x\n", data); + break; + + case IO_MEMBNK0: + if(!readFlag) + { + PCWCtrl.regMemBank0 = (data & 0x80) ? data & 0x7f : PCWCtrl.regMemBank0; + pr_info("Setting Bank 0:%02x\n", PCWCtrl.regMemBank0); + for(idx=0x0000; idx < 0x4000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType((idx+0x0000)/MEMORY_BLOCK_GRANULARITY, PCWCtrl.regMemBank0 >= 8 ? MEMORY_TYPE_VIRTUAL_RAM : MEMORY_TYPE_PHYSICAL_RAM_WT, (RAM_BASE_ADDR+(PCWCtrl.regMemBank0*16384)+idx)); + } + } + break; + + case IO_MEMBNK1: + if(!readFlag) + { + PCWCtrl.regMemBank1 = (data & 0x80) ? data & 0x7f : PCWCtrl.regMemBank1; + pr_info("Setting Bank 1:%02x\n", PCWCtrl.regMemBank1); + for(idx=0x0000; idx < 0x4000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType((idx+0x4000)/MEMORY_BLOCK_GRANULARITY, PCWCtrl.regMemBank1 >= 8 ? MEMORY_TYPE_VIRTUAL_RAM : MEMORY_TYPE_PHYSICAL_RAM_WT, (RAM_BASE_ADDR+(PCWCtrl.regMemBank1*16384)+idx)); + } + } + break; + + case IO_MEMBNK2: + if(!readFlag) + { + PCWCtrl.regMemBank2 = (data & 0x80) ? data & 0x7f : PCWCtrl.regMemBank2; + pr_info("Setting Bank 2:%02x\n", PCWCtrl.regMemBank2); + for(idx=0x0000; idx < 0x4000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType((idx+0x8000)/MEMORY_BLOCK_GRANULARITY, PCWCtrl.regMemBank2 >= 8 ? MEMORY_TYPE_VIRTUAL_RAM : MEMORY_TYPE_PHYSICAL_RAM_WT, (RAM_BASE_ADDR+(PCWCtrl.regMemBank2*16384)+idx)); + } + } + break; + + case IO_MEMBNK3: + if(!readFlag) + { + PCWCtrl.regMemBank3 = (data & 0x80) ? data & 0x7f : PCWCtrl.regMemBank3; + pr_info("Setting Bank 3:%02x\n", PCWCtrl.regMemBank3); + for(idx=0x0000; idx < 0x4000; idx+=MEMORY_BLOCK_GRANULARITY) + { + if(idx < 0x3FF0) + setMemoryType((idx+0xC000)/MEMORY_BLOCK_GRANULARITY, PCWCtrl.regMemBank3 >= 8 ? MEMORY_TYPE_VIRTUAL_RAM : MEMORY_TYPE_PHYSICAL_RAM_WT, (RAM_BASE_ADDR+(PCWCtrl.regMemBank3*16384)+idx)); + } + } + break; + + case IO_MEMLOCK: + if(!readFlag) + { +pr_info("MEMLOCK:%02x\n", data); + PCWCtrl.regCPCPageMode = data; + } + break; + + case IO_ROLLERRAM: + if(!readFlag) + { +pr_info("********RollerRAM********:%02x => %04x\n", data, (((data >> 5)&0x7) * 16384)+((data&0x1f)*512)); + PCWCtrl.regRollerRAM = data; + } + break; + + case IO_VORIGIN: +pr_info("VORIGIN:%02x\n", data); + break; + case IO_SCREENATTR: +pr_info("SCREENATTR:%02x\n", data); + break; + case IO_GACMD: +pr_info("GACMD:%02x\n", data); + break; + + default: +pr_info("Unknown:ADDR:%02x,%02x\n", address&0xff, data); + break; + } + } else + // Memory map switch. + { + switch(address) + { + default: + break; + } + } +} + +// Method to read from either the memory mapped registers if enabled else the RAM. +static inline uint8_t pcwRead(zuint16 address, uint8_t ioFlag) +{ + // Locals. + uint8_t data = 0xFF; + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + + default: + break; + } + } else + { + switch(address) + { + default: + // Return the contents of the ROM at given address. + data = isVirtualROM(address) ? readVirtualROM(address) : readVirtualRAM(address); + break; + } + } + + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 3) pr_info("PCW-Read:%04x, BK0:%02x, BK1:%02x, BK2:%02x, BK3:%02x, CTRL:%02x\n", address, PCWCtrl.regMemBank0, PCWCtrl.regMemBank1, PCWCtrl.regMemBank2, PCWCtrl.regMemBank3, PCWCtrl.regCtrl); + #endif + return(data); +} + +// Method to handle writes. +static inline void pcwWrite(zuint16 address, zuint8 data, uint8_t ioFlag) +{ + // Locals. + // uint32_t idx; + + + // I/O Operation? + if(ioFlag) + { + switch(address) + { + default: + break; + } + } else + { + switch(address) + { + default: + // Any unprocessed write is commited to RAM. + writeVirtualRAM(address, data); + break; + } + } + #if(DEBUG_ENABLED & 1) + if(Z80Ctrl->debug >= 3) pr_info("PCW-Write:%04x, BK0:%02x, BK1:%02x, BK2:%02x, BK3:%02x, CTRL:%02x\n", address, PCWCtrl.regMemBank0, PCWCtrl.regMemBank1, PCWCtrl.regMemBank2, PCWCtrl.regMemBank3, PCWCtrl.regCtrl); + #endif + return; +} diff --git a/software/FusionX/src/z80drv/MZ80A/z80vhw_rfs.c b/software/FusionX/src/z80drv/src/z80vhw_rfs.c similarity index 94% rename from software/FusionX/src/z80drv/MZ80A/z80vhw_rfs.c rename to software/FusionX/src/z80drv/src/z80vhw_rfs.c index 83022e662..00d93fdbf 100644 --- a/software/FusionX/src/z80drv/MZ80A/z80vhw_rfs.c +++ b/software/FusionX/src/z80drv/src/z80vhw_rfs.c @@ -67,6 +67,10 @@ #define BNKSELUSER 0xEFFE // Select RFS Bank2 (User ROM) #define BNKCTRL 0xEFFF // Bank Control register (read/write). +// 40/80 Video Module control registers. +// +#define DSPCTL 0xDFFF // Display 40/80 select register (bit 7) + // // RFS v2 Control Register constants. // @@ -87,7 +91,13 @@ #define BNKCTRLDEF BBMOSI+SDCS+BBCLK // Default on startup for the Bank Control register. // RFS Board ROM rom filename definitions. -#define ROM_DIR "/apps/FusionX/host/MZ-80A/RFS/" +#if(TARGET_HOST_MZ80A == 1) + #define ROM_DIR "/apps/FusionX/host/MZ-80A/RFS/" +#elif(TARGET_HOST_MZ700 == 1) + #define ROM_DIR "/apps/FusionX/host/MZ-700/RFS/" +#else + #error "Unknown host configured." +#endif #define ROM_MROM_40C_FILENAME ROM_DIR "MROM_256_40c.bin" #define ROM_USER_I_40C_FILENAME ROM_DIR "USER_ROM_256_40c.bin" #define ROM_USER_II_40C_FILENAME ROM_DIR "USER_ROM_II_256_40c.bin" @@ -167,6 +177,7 @@ typedef struct { uint8_t upCntr; // Register enable up counter. uint32_t mromAddr; // Actual address in MROM of active bank. uint32_t uromAddr; // Actual address in UROM of active bank. + uint8_t memSwitch; // MZ-80A can relocate the lower 4K ROM by swapping RAM at 0xC000. t_SDCtrl sd; // SD Control. } t_RFSCtrl; @@ -194,7 +205,7 @@ void rfsSetupMemory(enum Z80_MEMORY_PROFILE mode) RFSCtrl.regCtrl = 0x00; RFSCtrl.mromAddr = MROM_ADDR; RFSCtrl.uromAddr = USER_ROM_I_ADDR; - Z80Ctrl->memSwitch = 0; + RFSCtrl.memSwitch = 0; RFSCtrl.sd.trainingCnt = 0; RFSCtrl.sd.initialised = 0; RFSCtrl.sd.dataOutFlag = 0; @@ -214,7 +225,19 @@ void rfsSetupMemory(enum Z80_MEMORY_PROFILE mode) { setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, (RFSCtrl.mromAddr+idx)); } - if(idx >= 0xE800 && idx < 0xF000) + else if(idx >= 0x1000 && idx < 0xD000) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } + else if(idx >= 0xD000 && idx < 0xE000) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + else if(idx >= 0xE000 && idx < 0xE800) + { + setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_PHYSICAL_HW, idx); + } + else if(idx >= 0xE800 && idx < 0xF000) { // Memory is both ROM and hardware, the registers share the same address space. setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM | MEMORY_TYPE_VIRTUAL_HW, (RFSCtrl.uromAddr+(idx-0xE800))); @@ -224,6 +247,18 @@ void rfsSetupMemory(enum Z80_MEMORY_PROFILE mode) setMemoryType((idx/MEMORY_BLOCK_GRANULARITY), MEMORY_TYPE_VIRTUAL_ROM, (idx+(Z80_VIRTUAL_ROM_SIZE - 0x10000))); } } + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + + // Enable refresh as using virtual RAM stops refresh of host DRAM. + Z80Ctrl->refreshDRAM = 2; + + #if (TARGET_HOST_MZ700 == 1) + // Reset memory paging to default. + SPI_SEND_32(0x00e4, 0x00 << 8 | CPLD_CMD_WRITEIO_ADDR); + #endif // No I/O Ports on the RFS board. pr_info("RFS Memory Setup complete.\n"); @@ -231,7 +266,7 @@ void rfsSetupMemory(enum Z80_MEMORY_PROFILE mode) // Method to load a ROM image into the ROM memory. // -uint8_t loadROM(const char* romFileName, uint32_t loadAddr, uint32_t loadSize) +uint8_t rfsLoadROM(const char* romFileName, uint32_t loadAddr, uint32_t loadSize) { // Locals. uint8_t result = 0; @@ -264,10 +299,10 @@ void rfsInit(uint8_t mode80c) uint32_t idx; // Load ROMS according to the display configuration, 40 char = standard, 80 char = 40/80 board installed. - loadROM(mode80c == 0 ? ROM_MROM_40C_FILENAME : ROM_MROM_80C_FILENAME, ROM_MROM_LOAD_ADDR, ROM_MROM_SIZE); - loadROM(mode80c == 0 ? ROM_USER_I_40C_FILENAME : ROM_USER_I_80C_FILENAME, ROM_USER_I_LOAD_ADDR, ROM_MROM_SIZE); - loadROM(mode80c == 0 ? ROM_USER_II_40C_FILENAME : ROM_USER_II_80C_FILENAME, ROM_USER_II_LOAD_ADDR, ROM_MROM_SIZE); - loadROM(mode80c == 0 ? ROM_USER_III_40C_FILENAME : ROM_USER_II_80C_FILENAME, ROM_USER_III_LOAD_ADDR, ROM_MROM_SIZE); + rfsLoadROM(mode80c == 0 ? ROM_MROM_40C_FILENAME : ROM_MROM_80C_FILENAME, ROM_MROM_LOAD_ADDR, ROM_MROM_SIZE); + rfsLoadROM(mode80c == 0 ? ROM_USER_I_40C_FILENAME : ROM_USER_I_80C_FILENAME, ROM_USER_I_LOAD_ADDR, ROM_MROM_SIZE); + rfsLoadROM(mode80c == 0 ? ROM_USER_II_40C_FILENAME : ROM_USER_II_80C_FILENAME, ROM_USER_II_LOAD_ADDR, ROM_MROM_SIZE); + rfsLoadROM(mode80c == 0 ? ROM_USER_III_40C_FILENAME : ROM_USER_II_80C_FILENAME, ROM_USER_III_LOAD_ADDR, ROM_MROM_SIZE); // Copy the Floppy ROM to the top portion of ROM. USER III isnt normally used and if it is, 4K will be free. for(idx=0xF000; idx < 0x10000; idx++) @@ -276,7 +311,13 @@ void rfsInit(uint8_t mode80c) while(CPLD_READY() == 0); Z80Ctrl->rom[idx+(Z80_VIRTUAL_ROM_SIZE-0x10000)] = z80io_PRL_Read8(1); } - pr_info("Enabling RFS driver.\n"); + + #if (TARGET_HOST_MZ700 == 1) + // Reset memory paging to default. + SPI_SEND_32(0x00e4, 0x00 << 8 | CPLD_CMD_WRITEIO_ADDR); + #endif + + pr_info("Enabling RFS(%d) driver.\n", mode80c == 1 ? 80 : 40); return; } @@ -304,7 +345,7 @@ static inline void rfsDecodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (0xC000+idx)); setMemoryType((idx+0xC000)/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, (RFSCtrl.mromAddr+idx)); } - Z80Ctrl->memSwitch = 0x01; + RFSCtrl.memSwitch = 0x01; } // Reset memory map switch. @@ -318,7 +359,7 @@ static inline void rfsDecodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t setMemoryType((idx+0xC000)/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, (0xC000+idx)); } } - Z80Ctrl->memSwitch = 0x00; + RFSCtrl.memSwitch = 0x00; } } } @@ -755,7 +796,7 @@ static inline void rfsWrite(zuint16 address, zuint8 data, uint8_t ioFlag) // Update memory map to reflect register change. for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) { - if(Z80Ctrl->memSwitch) + if(RFSCtrl.memSwitch) { // Monitor ROM is located at 0xC000. setMemoryType((0xC000+idx)/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, (RFSCtrl.mromAddr+idx)); diff --git a/software/FusionX/src/z80drv/MZ80A/z80vhw_tzpu.c b/software/FusionX/src/z80drv/src/z80vhw_tzpu.c similarity index 84% rename from software/FusionX/src/z80drv/MZ80A/z80vhw_tzpu.c rename to software/FusionX/src/z80drv/src/z80vhw_tzpu.c index ad1633b22..2b4a69111 100644 --- a/software/FusionX/src/z80drv/MZ80A/z80vhw_tzpu.c +++ b/software/FusionX/src/z80drv/src/z80vhw_tzpu.c @@ -17,6 +17,7 @@ // Copyright: (c) 2019-2023 Philip Smart // // History: Feb 2023 v1.0 - Initial write based on the tranZPUter SW hardware. +// Apr 2023 v1.1 - Updates & bug fixes. // // Notes: See Makefile to enable/disable conditional components // @@ -72,6 +73,9 @@ typedef struct { // TZPU Board control. static t_TZPUCtrl TZPUCtrl; +// Forward prototypes. +static inline void tzpuWrite(zuint16 address, zuint8 data, uint8_t ioFlag); + //------------------------------------------------------------------------------------------------------------------------------- // // @@ -115,15 +119,145 @@ static t_TZPUCtrl TZPUCtrl; // 30 - All memory and IO are on the tranZPUter board, 64K block 6 selected. // 31 - All memory and IO are on the tranZPUter board, 64K block 7 selected. +// Method to setup the memory page config to reflect installation of a tranZPUter SW Board. This sets up the default +// as the memory map changes according to selection and handled in-situ. +void tzpuSetupMemory(enum Z80_MEMORY_PROFILE mode) +{ + // Locals. + uint32_t idx; + + // The tranZPUter SW uses a CPLD to set a 4K Z80 memory range window into a 512K-1MB linear RAM block. The actual map required + // at any one time is governed by the Memory Config register at I/O port 0x60. + // This method sets the initial state, which is a normal Sharp operating mode, all memory and IO (except tranZPUter + // control IO block) are on the mainboard. + + // Setup defaults. + TZPUCtrl.clkSrc = 0x00; // Clock defaults to host. + TZPUCtrl.regCmd = 0x00; // Default for the CPLD Command. + TZPUCtrl.regCmdStatus = 0x00; // Default for the CPLD Command Status. + TZPUCtrl.regCpuCfg = 0x00; // Not used, as no FPGA available, but need to store/return value if addressed. + TZPUCtrl.regCpuInfo = 0x00; // Not used, as no FPGA available, but need to store/return value if addressed. + // Setup the CPLD status value, this is used by the host for configuration of tzfs. + #if(TARGET_HOST_MZ80A == 1) + TZPUCtrl.regCpldInfo = (CPLD_VERSION << 4) | (CPLD_HAS_FPGA_VIDEO << 3) | HWMODE_MZ80A; + #endif + #if(TARGET_HOST_MZ700 == 1) + TZPUCtrl.regCpldInfo = (CPLD_VERSION << 4) | (CPLD_HAS_FPGA_VIDEO << 3) | HWMODE_MZ700; + #endif + #if(TARGET_HOST_MZ2000 == 1) + TZPUCtrl.regCpldInfo = (CPLD_VERSION << 4) | (CPLD_HAS_FPGA_VIDEO << 3) | HWMODE_MZ2000; + #endif + TZPUCtrl.regCpldCfg = 0x00; // Not used, as no CPLD available, but need to store/return value if addressed. + + // Default memory mode, TZFS. + Z80Ctrl->memoryMode = TZMM_ORIG; + + // Reset IO mapping. + for(idx=0x0000; idx < IO_PAGE_SIZE; idx++) + { + Z80Ctrl->iopage[idx] = idx | IO_TYPE_PHYSICAL_HW; + } + + // I/O Ports on the tranZPUter SW board. All hosts have the same ports for the tzpu board. + for(idx=0x0000; idx < IO_PAGE_SIZE; idx+=0x0100) + { + Z80Ctrl->iopage[idx+IO_TZ_CTRLLATCH] = IO_TZ_CTRLLATCH | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_SETXMHZ] = IO_TZ_SETXMHZ | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_SET2MHZ] = IO_TZ_SET2MHZ | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CLKSELRD] = IO_TZ_CLKSELRD | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_SVCREQ] = IO_TZ_SVCREQ | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_SYSREQ] = IO_TZ_SYSREQ | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CPLDCMD] = IO_TZ_CPLDCMD | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CPLDSTATUS] = IO_TZ_CPLDSTATUS | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CPUCFG] = IO_TZ_CPUCFG | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CPUSTATUS] = IO_TZ_CPUSTATUS | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CPUINFO] = IO_TZ_CPUINFO | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CPLDCFG] = IO_TZ_CPLDCFG | IO_TYPE_VIRTUAL_HW; + Z80Ctrl->iopage[idx+IO_TZ_CPLDINFO] = IO_TZ_CPLDINFO | IO_TYPE_VIRTUAL_HW; + } + + #if (TARGET_HOST_MZ700 == 1) + // Reset memory paging to default. + SPI_SEND_32(0x00e4, 0x00 << 8 | CPLD_CMD_WRITEIO_ADDR); + #endif + + pr_info("TZPU Memory Setup complete.\n"); +} + +// Method to load a ROM image into the ROM memory. +// +uint8_t tzpuLoadROM(const char* romFileName, uint32_t loadAddr, uint32_t loadSize) +{ + // Locals. + uint8_t result = 0; + long noBytes; + struct file *fp; + + fp = filp_open(romFileName, O_RDONLY, 0); + if(IS_ERR(fp)) + { + pr_info("Error opening ROM Image:%s\n:", romFileName); + result = 1; + } else + { + vfs_llseek(fp, 0, SEEK_SET); + noBytes = kernel_read(fp, fp->f_pos, &Z80Ctrl->rom[loadAddr], loadSize); + if(noBytes < loadSize) + { + // pr_info("Short load, ROM Image:%s, bytes loaded:%08x\n:", romFileName, loadSize); + } + filp_close(fp,NULL); + } + + return(result); +} + // Perform any setup operations, such as variable initialisation, to enable use of this module. void tzpuInit(void) { + // Setup all initial TZFS memory modes + tzpuWrite(IO_TZ_CTRLLATCH, TZMM_ORIG, 1); + tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS, 1); + tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS2, 1); + tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS3, 1); + tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS4, 1); + + // Ensure memory configuration is correct before requesting K64F to load Bios. + tzpuSetupMemory(USE_VIRTUAL_RAM); + + // Default memory mode, TZFS. + Z80Ctrl->memoryMode = TZMM_TZFS; + + #if (TARGET_HOST_MZ700 == 1) + // Reset memory paging to default. + SPI_SEND_32(0x00e4, 0x00 << 8 | CPLD_CMD_WRITEIO_ADDR); + #endif + + // New memory maps setup, perform a reset so that the K64F CPU loads the required ROMS. + sendSignal(Z80Ctrl->ioTask, SIGUSR1); + pr_info("Enabling TZPU driver.\n"); } // Perform any de-initialisation when the driver is removed. void tzpuRemove(void) { + // Locals. + uint32_t idx; + + // Go through and clear all memory maps, leave the original page in slot 0. + for(idx=1; idx < MEMORY_MODES; idx++) + { + if(Z80Ctrl->page[idx] != NULL) + { + kfree(Z80Ctrl->page[idx]); + Z80Ctrl->page[idx] = NULL; + } + } + + // Default memory mode, ORIG. + Z80Ctrl->memoryMode = TZMM_ORIG; + pr_info("Removing TZPU driver.\n"); return; } @@ -133,27 +267,154 @@ void tzpuRemove(void) static inline void tzpuDecodeMemoryMapSetup(zuint16 address, zuint8 data, uint8_t ioFlag, uint8_t readFlag) { // Locals. + uint32_t idx; // I/O or Memory? if(ioFlag == 0) { - // Memory map switch. - if(readFlag == 0) + // #if(DEBUG_ENABLED & 1) + // if(Z80Ctrl->debug >= 2) + // { + // pr_info("MEM:%04x,%02x,%d,%d\n", address, data, ioFlag, readFlag); + // } + // #endif + // Certain machines have memory mapped I/O, these need to be handled in-situ as some reads may change the memory map. + // These updates are made whilst waiting for the CPLD to retrieve the requested byte. + // + // 0000 - 0FFF : MZ80K/A/700 = Monitor ROM or RAM (MZ80A rom swap) + // 1000 - CFFF : MZ80K/A/700 = RAM + // C000 - CFFF : MZ80A = Monitor ROM (MZ80A rom swap) + // D000 - D7FF : MZ80K/A/700 = VRAM + // D800 - DFFF : MZ700 = Colour VRAM (MZ700) + // E000 - E003 : MZ80K/A/700 = 8255 + // E004 - E007 : MZ80K/A/700 = 8254 + // E008 - E00B : MZ80K/A/700 = LS367 + // E00C - E00F : MZ80A = Memory Swap (MZ80A) + // E010 - E013 : MZ80A = Reset Memory Swap (MZ80A) + // E014 : MZ80A/700 = Normat CRT display + // E015 : MZ80A/700 = Reverse CRT display + // E200 - E2FF : MZ80A/700 = VRAM roll up/roll down. + // E800 - EFFF : MZ80K/A/700 = User ROM socket or DD Eprom (MZ700) + // F000 - F7FF : MZ80K/A/700 = Floppy Disk interface. + // F800 - FFFF : MZ80K/A/700 = Floppy Disk interface. + switch(address) { - - } else - { - + default: + break; } } else // I/O Decoding. { - // Only lower 8 bits recognised in the tzpu. - switch(address & 0xFF) + // #if(DEBUG_ENABLED & 1) + // if(Z80Ctrl->debug >= 2) + // { + // pr_info("IO:%04x,%02x,%d,%d\n", address, data, ioFlag, readFlag); + // } + // #endif + + // Determine if this is a memory management port and update the memory page if required. + switch(address & 0x00FF) { - default: + // MZ700 memory mode switch. + // + // MZ-700 + // |0000:0FFF|1000:CFFF|D000:FFFF + // ------------------------------ + // OUT 0xE0 = |DRAM | | + // OUT 0xE1 = | | |DRAM + // OUT 0xE2 = |MONITOR | | + // OUT 0xE3 = | | |Memory Mapped I/O + // OUT 0xE4 = |MONITOR |DRAM |Memory Mapped I/O + // OUT 0xE5 = | | |Inhibit + // OUT 0xE6 = | | | + // + // = Return to the state prior to the complimentary command being invoked. + // Enable lower 4K block as DRAM + case IO_ADDR_E0: + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } break; + // Enable upper 12K block, including Video/Memory Mapped peripherals area, as DRAM. + case IO_ADDR_E1: + if(!Z80Ctrl->inhibitMode) + { + for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + // MZ-700 mode we only work in first 64K block. + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_RAM, idx); + } + } + break; + + // Enable MOnitor ROM in lower 4K block + case IO_ADDR_E2: + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); + } + break; + + // Enable Video RAM and Memory mapped peripherals in upper 12K block. + case IO_ADDR_E3: + if(!Z80Ctrl->inhibitMode) + { + for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); + } + } + break; + + // Reset to power on condition memory map. + case IO_ADDR_E4: + // Lower 4K set to Monitor ROM. + for(idx=0x0000; idx < 0x1000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_VIRTUAL_ROM, idx); + } + if(!Z80Ctrl->inhibitMode) + { + // Upper 12K to hardware. + for(idx=0xD000; idx < 0xE000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_VRAM, idx); + } + for(idx=0xE000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_PHYSICAL_HW, idx); + } + } + break; + + // Inhibit. Backup current page data in region 0xD000-0xFFFF and inhibit it. + case IO_ADDR_E5: + for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + backupMemoryType(idx/MEMORY_BLOCK_GRANULARITY); + setMemoryType(idx/MEMORY_BLOCK_GRANULARITY, MEMORY_TYPE_INHIBIT, idx); + } + Z80Ctrl->inhibitMode = 1; + break; + + // Restore D000-FFFF to its original state. + case IO_ADDR_E6: + for(idx=0xD000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + { + restoreMemoryType(idx/MEMORY_BLOCK_GRANULARITY); + } + Z80Ctrl->inhibitMode = 0; + break; + + // Port is not a memory management port. + default: + break; } } } @@ -252,7 +513,9 @@ static inline void tzpuWrite(zuint16 address, zuint8 data, uint8_t ioFlag) switch(address & 0x00FF) { case IO_TZ_CTRLLATCH: - //pr_info("CTRLLATCH:%02x\n", data); + #if(DEBUG_ENABLED & 0x01) + if(Z80Ctrl->debug >=3) pr_info("CTRLLATCH:%02x\n", data); + #endif // Check to see if the memory mode page has been allocated for requested mode, if it hasnt, we need to allocate and then define. Z80Ctrl->memoryMode = (data & (MEMORY_MODES - 1)); @@ -264,13 +527,13 @@ static inline void tzpuWrite(zuint16 address, zuint8 data, uint8_t ioFlag) (Z80Ctrl->page[Z80Ctrl->memoryMode]) = (uint32_t *)kmalloc((MEMORY_BLOCK_SLOTS*sizeof(uint32_t)), GFP_KERNEL); if ((Z80Ctrl->page[Z80Ctrl->memoryMode]) == NULL) { - pr_info("z80drv: failed to allocate memory mapping page:%d memory!", Z80Ctrl->memoryMode); + pr_info("z80drv: failed to allocate memory mapping page:%d memory!", Z80Ctrl->memoryMode); Z80Ctrl->page[Z80Ctrl->memoryMode] = Z80Ctrl->page[0]; } // A lot of the memory maps below are identical, minor changes such as RAM bank. This is a direct conversion of the VHDL code from the CPLD. // - for(idx=0x0000; idx < 0x10000; idx+=MEMORY_BLOCK_GRANULARITY) + for(idx=0x0000; idx < MEMORY_PAGE_SIZE; idx+=MEMORY_BLOCK_GRANULARITY) { switch(Z80Ctrl->memoryMode) { @@ -744,71 +1007,3 @@ static inline void tzpuWrite(zuint16 address, zuint8 data, uint8_t ioFlag) return; } -// Method to setup the memory page config to reflect installation of a tranZPUter SW Board. This sets up the default -// as the memory map changes according to selection and handled in-situ. -void tzpuSetupMemory(enum Z80_MEMORY_PROFILE mode) -{ - // Locals. - uint32_t idx; - - // The tranZPUter SW uses a CPLD to set a 4K Z80 memory range window into a 512K-1MB linear RAM block. The actual map required - // at any one time is governed by the Memory Config register at I/O port 0x60. - // This method sets the initial state, which is a normal Sharp operating mode, all memory and IO (except tranZPUter - // control IO block) are on the mainboard. - - // Setup defaults. - TZPUCtrl.clkSrc = 0x00; // Clock defaults to host. - TZPUCtrl.regCmd = 0x00; // Default for the CPLD Command. - TZPUCtrl.regCmdStatus = 0x00; // Default for the CPLD Command Status. - TZPUCtrl.regCpuCfg = 0x00; // Not used, as no FPGA available, but need to store/return value if addressed. - TZPUCtrl.regCpuInfo = 0x00; // Not used, as no FPGA available, but need to store/return value if addressed. - // Setup the CPLD status value, this is used by the host for configuration of tzfs. - #if(TARGET_HOST_MZ80A == 1) - TZPUCtrl.regCpldInfo = (CPLD_VERSION << 4) | (CPLD_HAS_FPGA_VIDEO << 3) | HWMODE_MZ80A; - #endif - #if(TARGET_HOST_MZ700 == 1) - TZPUCtrl.regCpldInfo = (CPLD_VERSION << 4) | (CPLD_HAS_FPGA_VIDEO << 3) | HWMODE_MZ700; - #endif - #if(TARGET_HOST_MZ2000 == 1) - TZPUCtrl.regCpldInfo = (CPLD_VERSION << 4) | (CPLD_HAS_FPGA_VIDEO << 3) | HWMODE_MZ2000; - #endif - TZPUCtrl.regCpldCfg = 0x00; // Not used, as no CPLD available, but need to store/return value if addressed. - - // Go through and clear all memory maps, valid for startup and reset. - for(idx=0; idx < MEMORY_MODES; idx++) - { - if(Z80Ctrl->page[idx] != NULL) - { - kfree(Z80Ctrl->page[idx]); - Z80Ctrl->page[idx] = NULL; - } - } - - // Setup all initial TZFS memory modes - tzpuWrite(IO_TZ_CTRLLATCH, TZMM_ORIG, 1); - tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS, 1); - tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS2, 1); - tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS3, 1); - tzpuWrite(IO_TZ_CTRLLATCH, TZMM_TZFS4, 1); - Z80Ctrl->memoryMode = 0x02; // Default memory mode, MZ-80A. - - // I/O Ports on the tranZPUter SW board. All hosts have the same ports for the tzpu board. - for(idx=0x0000; idx < 0x10000; idx+=0x0100) - { - Z80Ctrl->iopage[idx+IO_TZ_CTRLLATCH] = IO_TZ_CTRLLATCH | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_SETXMHZ] = IO_TZ_SETXMHZ | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_SET2MHZ] = IO_TZ_SET2MHZ | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CLKSELRD] = IO_TZ_CLKSELRD | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_SVCREQ] = IO_TZ_SVCREQ | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_SYSREQ] = IO_TZ_SYSREQ | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CPLDCMD] = IO_TZ_CPLDCMD | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CPLDSTATUS] = IO_TZ_CPLDSTATUS | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CPUCFG] = IO_TZ_CPUCFG | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CPUSTATUS] = IO_TZ_CPUSTATUS | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CPUINFO] = IO_TZ_CPUINFO | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CPLDCFG] = IO_TZ_CPLDCFG | IO_TYPE_VIRTUAL_HW; - Z80Ctrl->iopage[idx+IO_TZ_CPLDINFO] = IO_TZ_CPLDINFO | IO_TYPE_VIRTUAL_HW; - } - - pr_info("TZPU Memory Setup complete.\n"); -}

|8;uA+Hy+B!ZU_<(3iOHrj1O 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