From bd19914132a78a7d5df8d34b4e5012cebddb651c Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Sat, 6 Aug 2022 19:37:10 +0200 Subject: [PATCH] Initial emulation of the Athens clock ASIC. --- devices/common/i2c/athens.cpp | 136 ++++++++++++++++++++++++++++++++++ devices/common/i2c/athens.h | 75 +++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 devices/common/i2c/athens.cpp create mode 100644 devices/common/i2c/athens.h diff --git a/devices/common/i2c/athens.cpp b/devices/common/i2c/athens.cpp new file mode 100644 index 0000000..66a7d74 --- /dev/null +++ b/devices/common/i2c/athens.cpp @@ -0,0 +1,136 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 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 . +*/ + +/** @file Athens clock generator emulation. */ + +/** + Athens (Apple part# 343S1191) is a programmable clock generator ASIC + used in the PCI Power Macintosh computers. + It has two outputs: system clock and video aka dot clock. Both can be + programmed using HW jumpers or I2C interface. + This high-level emulator focuses on the Athen's I2C interface providing + methods for querying the resulting virtual frequences. + This is helpful especially for calculating video parameters. + */ + +#include +#include + +#include +#include + +AthensClocks::AthensClocks(uint8_t dev_addr) +{ + supports_types(HWCompType::I2C_DEV); + + this->my_addr = dev_addr; + + // set up power on values + this->regs[AthensRegs::P2_MUX2] = 0x62; +} + +void AthensClocks::start_transaction() +{ + this->pos = 0; // reset read/write position +} + +bool AthensClocks::send_subaddress(uint8_t sub_addr) +{ + LOG_F(INFO, "Athens: subaddress set to 0x%X", sub_addr); + return true; +} + +bool AthensClocks::send_byte(uint8_t data) +{ + switch (this->pos) { + case 0: + this->reg_num = data; + this->pos++; + break; + case 1: + if (this->reg_num >= ATHENS_NUM_REGS) { + LOG_F(WARNING, "Athens: invalid register number %d", this->reg_num); + return false; // return NACK + } + this->regs[this->reg_num] = data; + if (reg_num == 3) { + LOG_F(INFO, "Athens: dot clock frequency set to %d Hz", get_dot_freq()); + } + break; + default: + LOG_F(WARNING, "Athens: too much data received!"); + return false; // return NACK + } + return true; +} + +bool AthensClocks::receive_byte(uint8_t* p_data) +{ + LOG_F(INFO, "Athens: receive_byte called"); + return false; // return NACK for now +} + +int AthensClocks::get_sys_freq() +{ + return 0; +} + +int AthensClocks::get_dot_freq() +{ + static std::vector D2_commons = {7, 11, 13, 14, 15, 17, 23, 31}; + static std::vector N2_commons = { + 22, 27, 28, 31, 35, 37, 38, 42, 49, 55, 56, 78, 125 + }; + + float out_freq = 0.0f; + + int d2 = this->regs[AthensRegs::D2]; + int n2 = this->regs[AthensRegs::N2]; + + int post_div = 1 << (3 - (this->regs[AthensRegs::P2_MUX2] & 3)); + + if (std::find(D2_commons.begin(), D2_commons.end(), d2) == D2_commons.end()) { + LOG_F(WARNING, "Athens: untested D2 value %d", d2); + } + + if (std::find(N2_commons.begin(), N2_commons.end(), n2) == N2_commons.end()) { + LOG_F(WARNING, "Athens: untested N2 value %d", d2); + } + + int mux = (this->regs[AthensRegs::P2_MUX2] >> 4) & 3; + + switch (mux) { + case 0: // clock source -> dot cock VCO + out_freq = ATHENS_XTAL * ((float)n2 / (float)(d2 * post_div)); + break; + case 1: // clock source -> system clock VCO + LOG_F(WARNING, "Athens: system clock VCO not supported yet!"); + break; + case 2: // clock source -> crystal frequency + out_freq = ATHENS_XTAL / post_div; + break; + case 3: + LOG_F(WARNING, "Athens: attempt to use reserved Mux value!"); + break; + } + + return static_cast(out_freq + 0.5f); +} diff --git a/devices/common/i2c/athens.h b/devices/common/i2c/athens.h new file mode 100644 index 0000000..13217c8 --- /dev/null +++ b/devices/common/i2c/athens.h @@ -0,0 +1,75 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 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 . +*/ + +/** @file Athens clock generator definitions. */ + +#ifndef ATHENS_H +#define ATHENS_H + +#include +#include + +#include + +#define ATHENS_NUM_REGS 8 + +constexpr auto ATHENS_XTAL = 31334400.0f; // external crystal oscillator frequency + +namespace AthensRegs { + +enum AthensRegs: uint8_t { + ID = 0, + D2 = 1, + N2 = 2, + P2_MUX2 = 3, + VN_CTRL = 4, // vendor specific control bits + BD1 = 5, + BN1 = 6, + P1 = 7 +}; + +}; // namespace AthensRegs + +class AthensClocks : public I2CDevice, public HWComponent +{ +public: + AthensClocks(uint8_t dev_addr); + ~AthensClocks() = default; + + // I2CDevice methods + void start_transaction(); + bool send_subaddress(uint8_t sub_addr); + bool send_byte(uint8_t data); + bool receive_byte(uint8_t* p_data); + + // methods for querying virtual frequences + int get_sys_freq(); + int get_dot_freq(); + +private: + uint8_t my_addr = 0; + uint8_t reg_num = 0; + int pos = 0; + + uint8_t regs[ATHENS_NUM_REGS]; +}; + +#endif // ATHENS_H