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