dingusppc/devices/common/viacuda.h
Maxim Poliakovski 20b2fb1bef viacuda: refactor emulation of the VIA registers.
Take the DDRA and DDRB registers into account as well because
there is software that tries to change state of the pins configured
as inputs.
This commit fixes Cuda for Rhapsody.
2024-08-04 21:43:06 +02:00

279 lines
9.0 KiB
C++

/*
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 <https://www.gnu.org/licenses/>.
*/
/** 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 <devices/common/hwcomponent.h>
#include <devices/common/i2c/i2c.h>
#include <devices/common/nvram.h>
#include <memory>
#include <chrono>
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<HWComponent> create() {
return std::unique_ptr<ViaCuda>(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<std::chrono::system_clock> 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<NVram> 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