/* DingusPPC - The Experimental PowerPC Macintosh emulator Copyright (C) 2018-24 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) This program 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 program 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 . */ /** VIA-CUDA combo device emulation. Versatile interface adapter (VIA) is an old I/O controller that can be found in nearly every Macintosh computer. In the 68k era, VIA was used to control various peripheral devices. In a Power Macintosh, its function is limited to the I/O interface with the Cuda MCU. I therefore decided to put VIA emulation code here. Cuda MCU is a multipurpose IC built around a custom version of the Motorola MC68HC05 microcontroller. It provides several functions including: - Apple Desktop Bus (ADB) master - I2C bus master - Realtime clock (RTC) - parameter RAM (first generation of the Power Macintosh) - power management MC68HC05 doesn't provide any dedicated hardware for serial communication protocols. All signals required for ADB and I2C will be generated by Cuda firmware using bit banging (https://en.wikipedia.org/wiki/Bit_banging). */ #ifndef VIACUDA_H #define VIACUDA_H #include #include #include #include #include class AdbBus; class InterruptCtrl; #define VIA_CLOCK_HZ 783360 /** VIA register offsets. */ enum { VIA_B = 0x00, // input/output register B VIA_A = 0x01, // input/output register A VIA_DIRB = 0x02, // direction B VIA_DIRA = 0x03, // direction A VIA_T1CL = 0x04, // low-order timer 1 counter VIA_T1CH = 0x05, // high-order timer 1 counter VIA_T1LL = 0x06, // low-order timer 1 latches VIA_T1LH = 0x07, // high-order timer 1 latches VIA_T2CL = 0x08, // low-order timer 2 latches VIA_T2CH = 0x09, // high-order timer 2 counter VIA_SR = 0x0A, // shift register VIA_ACR = 0x0B, // auxiliary control register VIA_PCR = 0x0C, // periheral control register VIA_IFR = 0x0D, // interrupt flag register VIA_IER = 0x0E, // interrupt enable register VIA_ANH = 0x0F, // input/output register A, no handshake }; /** VIA interrupt flags */ enum { VIA_IF_CA2 = 1 << 0, VIA_IF_CA1 = 1 << 1, VIA_IF_SR = 1 << 2, VIA_IF_CB2 = 1 << 3, VIA_IF_CB1 = 1 << 4, VIA_IF_T2 = 1 << 5, VIA_IF_T1 = 1 << 6 }; enum class ViaLine { CA1, CA2, CB1, CB2 }; /** Cuda communication signals (active low -> 0 - true, 1 - false). */ enum { CUDA_TIP = 0x20, // transaction in progress CUDA_BYTEACK = 0x10, // byte acknowledge/handshake CUDA_TREQ = 0x08 // Cuda requests transaction from host }; /** Cuda packet types. */ enum { CUDA_PKT_ADB = 0, CUDA_PKT_PSEUDO = 1, CUDA_PKT_ERROR = 2, CUDA_PKT_TICK = 3, CUDA_PKT_POWER = 4 }; /** Cuda pseudo commands. */ enum { CUDA_WARM_START = 0x00, // warm start CUDA_START_STOP_AUTOPOLL = 0x01, // start/stop device auto-polling CUDA_READ_MCU_MEM = 0x02, // read internal Cuda memory CUDA_GET_REAL_TIME = 0x03, // get real time CUDA_READ_PRAM = 0x07, // read parameter RAM CUDA_WRITE_MCU_MEM = 0x08, // write internal Cuda memory CUDA_SET_REAL_TIME = 0x09, // set real time CUDA_POWER_DOWN = 0x0A, // power down system CUDA_WRITE_PRAM = 0x0C, // write parameter RAM CUDA_MONO_STABLE_RESET = 0x0D, // mono stable reset CUDA_RESTART_SYSTEM = 0x11, // restart system CUDA_FILE_SERVER_FLAG = 0x13, // set file server flag CUDA_SET_AUTOPOLL_RATE = 0x14, // set auto-polling rate CUDA_GET_AUTOPOLL_RATE = 0x16, // get auto-polling rate CUDA_SET_DEVICE_LIST = 0x19, // set device list CUDA_GET_DEVICE_LIST = 0x1A, // get device list CUDA_ONE_SECOND_MODE = 0x1B, // one second interrupt mode CUDA_SET_POWER_MESSAGES = 0x21, // set power message flag CUDA_READ_WRITE_I2C = 0x22, // read/write I2C device CUDA_COMB_FMT_I2C = 0x25, // combined format I2C transaction CUDA_OUT_PB0 = 0x26, // output one bit to Cuda's PB0 line }; /** Cuda error codes. */ enum { CUDA_ERR_BAD_PKT = 1, // invalid packet type CUDA_ERR_BAD_CMD = 2, // invalid pseudo command CUDA_ERR_BAD_SIZE = 3, // invalid packet size CUDA_ERR_BAD_PAR = 4, // invalid parameter CUDA_ERR_I2C = 5 // invalid I2C data or no acknowledge }; #define CUDA_IN_BUF_SIZE 256 /** PRAM addresses within Cuda's internal memory. */ #define CUDA_PRAM_START 0x100 // starting address of PRAM #define CUDA_PRAM_END 0x1FF // last byte of PRAM #define CUDA_ROM_START 0xF00 // starting address of ROM containing Cuda FW /** Latest Cuda Firmware version. */ #define CUDA_FW_VERSION_MAJOR 0x0002 #define CUDA_FW_VERSION_MINOR 0x0029 class ViaCuda : public HWComponent, public I2CBus { public: ViaCuda(); ~ViaCuda(); static std::unique_ptr create() { return std::unique_ptr(new ViaCuda()); } // HWComponent methods int device_postinit(); uint8_t read(int reg); void write(int reg, uint8_t value); void assert_ctrl_line(ViaLine line); #ifdef DEBUG_CPU_INT void assert_int(uint8_t flags); #endif private: // VIA virtual HW registers uint8_t via_portb = 0; uint8_t via_porta = 0; uint8_t via_ddrb = 0; uint8_t via_ddra = 0; uint8_t via_t1cl = 0xFF; uint8_t via_t1ll = 0xFF; uint8_t via_t1lh = 0xFF; uint8_t via_t2cl = 0xFF; uint8_t via_sr; uint8_t via_acr = 0; uint8_t via_pcr = 0; float via_clk_dur; // one VIA clock duration = 1,27655 us // VIA internal state uint32_t sr_timer_id = 0; uint8_t last_orb = 0; // last value written to the ORB register. // timer 1 state uint16_t t1_counter; uint32_t t1_timer_id = 0; uint64_t t1_start_time = 0; // timer 2 state uint16_t t2_counter; uint32_t t2_timer_id = 0; uint64_t t2_start_time = 0; // VIA interrupt related stuff InterruptCtrl* int_ctrl; uint32_t irq_id; uint8_t _via_ifr; uint8_t _via_ier; uint8_t old_ifr = 0; /* Cuda state. */ uint8_t old_tip; uint8_t old_byteack; uint8_t treq; uint32_t treq_timer_id = 0; uint8_t in_buf[CUDA_IN_BUF_SIZE]; int32_t in_count; uint8_t out_buf[16]; int32_t out_count; int32_t out_pos; uint8_t poll_rate; uint32_t last_time = 0; uint32_t time_offset = 0; std::chrono::time_point mac_epoch; uint8_t one_sec_mode = 0; bool file_server; uint16_t device_mask = 0; bool is_open_ended; // true if current transaction is open-ended uint8_t curr_i2c_addr; // current I2C address uint8_t cur_pram_addr; // current PRAM address, range 0...FF void (ViaCuda::*out_handler)(void); void (ViaCuda::*next_out_handler)(void); std::unique_ptr pram_obj; AdbBus* adb_bus_obj = nullptr; bool autopoll_enabled = false; // VIA methods void print_enabled_ints(); // print enabled VIA interrupts and their sources void init_ints(); void update_irq(); void assert_sr_int(); void assert_t1_int(); void assert_t2_int(); void schedule_sr_int(uint64_t timeout_ns); uint16_t calc_counter_val(const uint16_t last_val, const uint64_t& last_time); // CUDA methods void cuda_init(); void write(uint8_t new_state); void response_header(uint32_t pkt_type, uint32_t pkt_flag); void error_response(uint32_t error); void process_packet(); void process_adb_command(); void pseudo_command(); uint32_t calc_real_time(); void null_out_handler(void); void pram_out_handler(void); void out_buf_handler(void); void i2c_handler(void); void autopoll_handler(); /* I2C related methods */ void i2c_simple_transaction(uint8_t dev_addr, const uint8_t* in_buf, int in_bytes); void i2c_comb_transaction(uint8_t dev_addr, uint8_t sub_addr, uint8_t dev_addr1, const uint8_t* in_buf, int in_bytes); }; #endif // VIACUDA_H