mirror of
https://github.com/dingusdev/dingusppc.git
synced 2025-08-07 17:25:08 +00:00
Basic support for display identification.
This commit is contained in:
@@ -21,21 +21,30 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#include <atirage.h>
|
#include <atirage.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <thirdparty/loguru/loguru.hpp>
|
||||||
#include "endianswap.h"
|
#include "endianswap.h"
|
||||||
#include "memreadwrite.h"
|
#include "memreadwrite.h"
|
||||||
#include "pcidevice.h"
|
#include "pcidevice.h"
|
||||||
#include <thirdparty/loguru/loguru.hpp>
|
#include "displayid.h"
|
||||||
|
|
||||||
|
|
||||||
ATIRage::ATIRage(uint16_t dev_id) : PCIDevice("ati-rage")
|
ATIRage::ATIRage(uint16_t dev_id) : PCIDevice("ati-rage")
|
||||||
{
|
{
|
||||||
WRITE_DWORD_BE_A(&this->pci_cfg[0], (dev_id << 16) | ATI_PCI_VENDOR_ID);
|
WRITE_DWORD_BE_A(&this->pci_cfg[0], (dev_id << 16) | ATI_PCI_VENDOR_ID);
|
||||||
WRITE_DWORD_BE_A(&this->pci_cfg[8], 0x0300005C);
|
WRITE_DWORD_BE_A(&this->pci_cfg[8], 0x0300005C);
|
||||||
WRITE_DWORD_BE_A(&this->pci_cfg[0x3C], 0x00080100);
|
WRITE_DWORD_BE_A(&this->pci_cfg[0x3C], 0x00080100);
|
||||||
|
|
||||||
|
this->disp_id = new DisplayID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ATIRage::~ATIRage()
|
||||||
|
{
|
||||||
|
delete (this->disp_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ATIRage::size_dep_read(uint8_t *buf, uint32_t size)
|
uint32_t ATIRage::size_dep_read(uint8_t *buf, uint32_t size)
|
||||||
{
|
{
|
||||||
switch(size) {
|
switch (size) {
|
||||||
case 4:
|
case 4:
|
||||||
return READ_DWORD_LE_A(buf);
|
return READ_DWORD_LE_A(buf);
|
||||||
break;
|
break;
|
||||||
@@ -65,23 +74,53 @@ void ATIRage::size_dep_write(uint8_t *buf, uint32_t value, uint32_t size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATIRage::write_reg(uint32_t offset, uint32_t value, uint32_t size)
|
const char* ATIRage::get_reg_name(uint32_t reg_offset)
|
||||||
{
|
{
|
||||||
const char* reg_name;
|
const char* reg_name;
|
||||||
|
|
||||||
/* size-dependent endian conversion */
|
switch (reg_offset & ~3) {
|
||||||
size_dep_write(&this->block_io_regs[offset], value, size);
|
case ATI_CRTC_H_TOTAL_DISP:
|
||||||
|
reg_name = "CRTC_H_TOTAL_DISP";
|
||||||
switch (offset & ~3) {
|
break;
|
||||||
|
case ATI_CRTC_H_SYNC_STRT_WID:
|
||||||
|
reg_name = "CRTC_H_SYNC_STRT_WID";
|
||||||
|
break;
|
||||||
|
case ATI_CRTC_V_TOTAL_DISP:
|
||||||
|
reg_name = "CRTC_V_TOTAL_DISP";
|
||||||
|
break;
|
||||||
|
case ATI_CRTC_V_SYNC_STRT_WID:
|
||||||
|
reg_name = "CRTC_V_SYNC_STRT_WID";
|
||||||
|
break;
|
||||||
|
case ATI_CRTC_OFF_PITCH:
|
||||||
|
reg_name = "CRTC_OFF_PITCH";
|
||||||
|
break;
|
||||||
case ATI_CRTC_INT_CNTL:
|
case ATI_CRTC_INT_CNTL:
|
||||||
reg_name = "CRTC_INT_CNTL";
|
reg_name = "CRTC_INT_CNTL";
|
||||||
break;
|
break;
|
||||||
case ATI_CRTC_GEN_CNTL:
|
case ATI_CRTC_GEN_CNTL:
|
||||||
reg_name = "CRTC_GEN_CNTL";
|
reg_name = "CRTC_GEN_CNTL";
|
||||||
break;
|
break;
|
||||||
|
case ATI_DSP_CONFIG:
|
||||||
|
reg_name = "DSP_CONFIG";
|
||||||
|
break;
|
||||||
|
case ATI_DSP_ON_OFF:
|
||||||
|
reg_name = "DSP_ON_OFF";
|
||||||
|
break;
|
||||||
case ATI_MEM_ADDR_CFG:
|
case ATI_MEM_ADDR_CFG:
|
||||||
reg_name = "MEM_ADDR_CFG";
|
reg_name = "MEM_ADDR_CFG";
|
||||||
break;
|
break;
|
||||||
|
case ATI_OVR_CLR:
|
||||||
|
reg_name = "OVR_CLR";
|
||||||
|
break;
|
||||||
|
case ATI_OVR_WID_LEFT_RIGHT:
|
||||||
|
reg_name = "OVR_WID_LEFT_RIGHT";
|
||||||
|
break;
|
||||||
|
case ATI_OVR_WID_TOP_BOTTOM:
|
||||||
|
reg_name = "OVR_WID_TOP_BOTTOM";
|
||||||
|
break;
|
||||||
|
case ATI_GP_IO:
|
||||||
|
reg_name = "GP_IO";
|
||||||
|
break;
|
||||||
case ATI_CLOCK_CNTL:
|
case ATI_CLOCK_CNTL:
|
||||||
reg_name = "CLOCK_CNTL";
|
reg_name = "CLOCK_CNTL";
|
||||||
break;
|
break;
|
||||||
@@ -100,6 +139,9 @@ void ATIRage::write_reg(uint32_t offset, uint32_t value, uint32_t size)
|
|||||||
case ATI_GEN_TEST_CNTL:
|
case ATI_GEN_TEST_CNTL:
|
||||||
reg_name = "GEN_TEST_CNTL";
|
reg_name = "GEN_TEST_CNTL";
|
||||||
break;
|
break;
|
||||||
|
case ATI_CFG_CHIP_ID:
|
||||||
|
reg_name = "CONFIG_CHIP_ID";
|
||||||
|
break;
|
||||||
case ATI_CFG_STAT0:
|
case ATI_CFG_STAT0:
|
||||||
reg_name = "CONFIG_STAT0";
|
reg_name = "CONFIG_STAT0";
|
||||||
break;
|
break;
|
||||||
@@ -107,8 +149,49 @@ void ATIRage::write_reg(uint32_t offset, uint32_t value, uint32_t size)
|
|||||||
reg_name = "unknown";
|
reg_name = "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_F(INFO, "ATI Rage: %s register at 0x%X set to 0x%X", reg_name,
|
return reg_name;
|
||||||
offset & ~3, READ_DWORD_LE_A(&this->block_io_regs[offset & ~3]));
|
}
|
||||||
|
|
||||||
|
uint32_t ATIRage::read_reg(uint32_t offset, uint32_t size)
|
||||||
|
{
|
||||||
|
uint32_t res;
|
||||||
|
|
||||||
|
switch (offset & ~3) {
|
||||||
|
case ATI_GP_IO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_F(INFO, "ATI Rage: read I/O reg %s at 0x%X, size=%d, val=0x%X",
|
||||||
|
get_reg_name(offset), offset, size,
|
||||||
|
size_dep_read(&this->block_io_regs[offset], size));
|
||||||
|
}
|
||||||
|
|
||||||
|
res = size_dep_read(&this->block_io_regs[offset], size);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATIRage::write_reg(uint32_t offset, uint32_t value, uint32_t size)
|
||||||
|
{
|
||||||
|
uint32_t gpio_val;
|
||||||
|
uint16_t gpio_dir;
|
||||||
|
|
||||||
|
/* size-dependent endian conversion */
|
||||||
|
size_dep_write(&this->block_io_regs[offset], value, size);
|
||||||
|
|
||||||
|
switch (offset & ~3) {
|
||||||
|
case ATI_GP_IO:
|
||||||
|
if (offset < (ATI_GP_IO + 2)) {
|
||||||
|
gpio_val = READ_DWORD_LE_A(&this->block_io_regs[ATI_GP_IO]);
|
||||||
|
gpio_dir = (gpio_val >> 16) & 0x3FFF;
|
||||||
|
WRITE_WORD_LE_A(&this->block_io_regs[ATI_GP_IO],
|
||||||
|
this->disp_id->read_monitor_sense(gpio_val, gpio_dir));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_F(INFO, "ATI Rage: %s register at 0x%X set to 0x%X",
|
||||||
|
get_reg_name(offset), offset & ~3,
|
||||||
|
READ_DWORD_LE_A(&this->block_io_regs[offset & ~3]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -174,7 +257,7 @@ bool ATIRage::io_access_allowed(uint32_t offset, uint32_t *p_io_base)
|
|||||||
|
|
||||||
uint32_t io_base = READ_DWORD_LE_A(&this->pci_cfg[CFG_REG_BAR1]) & ~3;
|
uint32_t io_base = READ_DWORD_LE_A(&this->pci_cfg[CFG_REG_BAR1]) & ~3;
|
||||||
|
|
||||||
if (offset < io_base || offset >(io_base + 0x100)) {
|
if (offset < io_base || offset > (io_base + 0x100)) {
|
||||||
LOG_F(WARNING, "Rage: I/O out of range, base=0x%X, offset=0x%X", io_base, offset);
|
LOG_F(WARNING, "Rage: I/O out of range, base=0x%X, offset=0x%X", io_base, offset);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -193,9 +276,7 @@ bool ATIRage::pci_io_read(uint32_t offset, uint32_t size, uint32_t *res)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*res = size_dep_read(&this->block_io_regs[offset - io_base], size);
|
*res = this->read_reg(offset - io_base, size);
|
||||||
|
|
||||||
LOG_F(INFO, "ATI Rage I/O space read, offset=0x%X, size=%d, val=0x%X", offset, size, *res);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,30 @@
|
|||||||
|
/*
|
||||||
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||||
|
Copyright (C) 2018-20 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef ATI_RAGE_H
|
#ifndef ATI_RAGE_H
|
||||||
#define ATI_RAGE_H
|
#define ATI_RAGE_H
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include "pcidevice.h"
|
#include "pcidevice.h"
|
||||||
|
#include "displayid.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@@ -19,10 +41,11 @@ enum {
|
|||||||
ATI_CRTC_H_SYNC_STRT_WID = 0x0004,
|
ATI_CRTC_H_SYNC_STRT_WID = 0x0004,
|
||||||
ATI_CRTC_V_TOTAL_DISP = 0x0008,
|
ATI_CRTC_V_TOTAL_DISP = 0x0008,
|
||||||
ATI_CRTC_V_SYNC_STRT_WID = 0x000C,
|
ATI_CRTC_V_SYNC_STRT_WID = 0x000C,
|
||||||
|
ATI_CRTC_OFF_PITCH = 0x0014,
|
||||||
ATI_CRTC_INT_CNTL = 0x0018,
|
ATI_CRTC_INT_CNTL = 0x0018,
|
||||||
ATI_CRTC_GEN_CNTL = 0x001C,
|
ATI_CRTC_GEN_CNTL = 0x001C,
|
||||||
ATI_DSP_CONFIG = 0x0020,
|
ATI_DSP_CONFIG = 0x0020,
|
||||||
ATI_DSP_TOGGLE = 0x0024,
|
ATI_DSP_ON_OFF = 0x0024,
|
||||||
ATI_TIMER_CFG = 0x0028,
|
ATI_TIMER_CFG = 0x0028,
|
||||||
ATI_MEM_BUF_CNTL = 0x002C,
|
ATI_MEM_BUF_CNTL = 0x002C,
|
||||||
ATI_MEM_ADDR_CFG = 0x0034,
|
ATI_MEM_ADDR_CFG = 0x0034,
|
||||||
@@ -83,7 +106,7 @@ class ATIRage : public PCIDevice
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ATIRage(uint16_t dev_id);
|
ATIRage(uint16_t dev_id);
|
||||||
~ATIRage() = default;
|
~ATIRage();
|
||||||
|
|
||||||
/* MMIODevice methods */
|
/* MMIODevice methods */
|
||||||
uint32_t read(uint32_t reg_start, uint32_t offset, int size);
|
uint32_t read(uint32_t reg_start, uint32_t offset, int size);
|
||||||
@@ -104,7 +127,9 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
uint32_t size_dep_read(uint8_t* buf, uint32_t size);
|
uint32_t size_dep_read(uint8_t* buf, uint32_t size);
|
||||||
void size_dep_write(uint8_t* buf, uint32_t val, uint32_t size);
|
void size_dep_write(uint8_t* buf, uint32_t val, uint32_t size);
|
||||||
|
const char* get_reg_name(uint32_t reg_offset);
|
||||||
bool io_access_allowed(uint32_t offset, uint32_t* p_io_base);
|
bool io_access_allowed(uint32_t offset, uint32_t* p_io_base);
|
||||||
|
uint32_t read_reg(uint32_t offset, uint32_t size);
|
||||||
void write_reg(uint32_t offset, uint32_t value, uint32_t size);
|
void write_reg(uint32_t offset, uint32_t value, uint32_t size);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -116,5 +141,7 @@ private:
|
|||||||
uint8_t block_io_regs[256] = { 0 };
|
uint8_t block_io_regs[256] = { 0 };
|
||||||
|
|
||||||
uint8_t pci_cfg[256] = { 0 }; /* PCI configuration space */
|
uint8_t pci_cfg[256] = { 0 }; /* PCI configuration space */
|
||||||
|
|
||||||
|
DisplayID* disp_id;
|
||||||
};
|
};
|
||||||
#endif /* ATI_RAGE_H */
|
#endif /* ATI_RAGE_H */
|
||||||
|
226
devices/displayid.cpp
Normal file
226
devices/displayid.cpp
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||||
|
Copyright (C) 2018-20 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <thirdparty/loguru/loguru.hpp>
|
||||||
|
#include "displayid.h"
|
||||||
|
|
||||||
|
DisplayID::DisplayID()
|
||||||
|
{
|
||||||
|
/* Initialize Apple monitor codes */
|
||||||
|
this->std_sense_code = 6;
|
||||||
|
this->ext_sense_code = 0x2B;
|
||||||
|
|
||||||
|
/* Initialize DDC I2C bus */
|
||||||
|
this->next_state = I2CState::STOP;
|
||||||
|
this->prev_state = I2CState::STOP;
|
||||||
|
this->last_sda = 1;
|
||||||
|
this->last_scl = 1;
|
||||||
|
this->data_out = 0x3000;
|
||||||
|
this->data_ptr = 0;
|
||||||
|
|
||||||
|
/* DDC sense mode is on by default */
|
||||||
|
this->i2c_on = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t DisplayID::set_result(uint8_t sda, uint8_t scl)
|
||||||
|
{
|
||||||
|
this->last_sda = sda;
|
||||||
|
this->last_scl = scl;
|
||||||
|
|
||||||
|
if (scl) {
|
||||||
|
this->data_out |= 0x1000;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->data_out &= ~0x1000U;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sda) {
|
||||||
|
this->data_out |= 0x2000;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->data_out &= ~0x2000U;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->data_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t DisplayID::read_monitor_sense(uint16_t data, uint16_t dirs)
|
||||||
|
{
|
||||||
|
uint8_t scl, sda;
|
||||||
|
uint16_t result;
|
||||||
|
|
||||||
|
if ((dirs & 0x3100) == 0 && (data & 0x3100) == 0x3100) {
|
||||||
|
LOG_F(WARNING, "DisplayID: Hackish Monitor ID Switch activated!");
|
||||||
|
this->i2c_on = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->i2c_on) {
|
||||||
|
/* if GPIO pins are in the output mode, pick up their values
|
||||||
|
In the input mode, GPIO pins will be read "high" */
|
||||||
|
scl = (dirs & 0x1000) ? !!(data & 0x1000) : 1;
|
||||||
|
sda = (dirs & 0x2000) ? !!(data & 0x2000) : 1;
|
||||||
|
|
||||||
|
return update_ddc_i2c(sda, scl);
|
||||||
|
}
|
||||||
|
else { /* Apple legacy monitor codes (see Technical Note HW30) */
|
||||||
|
switch (dirs & 0x3100) {
|
||||||
|
case 0:
|
||||||
|
result = ((this->std_sense_code & 6) << 11) |
|
||||||
|
((this->std_sense_code & 1) << 8);
|
||||||
|
break;
|
||||||
|
case 0x2000: /* Sense line 2 is low */
|
||||||
|
result = ((this->ext_sense_code & 0x20) << 7) |
|
||||||
|
((this->ext_sense_code & 0x10) << 4);
|
||||||
|
break;
|
||||||
|
case 0x1000: /* Sense line 1 is low */
|
||||||
|
result = ((this->ext_sense_code & 8) << 10) |
|
||||||
|
((this->ext_sense_code & 4) << 6);
|
||||||
|
break;
|
||||||
|
case 0x100: /* Sense line 0 is low */
|
||||||
|
result = ((this->ext_sense_code & 2) << 12) |
|
||||||
|
((this->ext_sense_code & 1) << 12);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = 0x3100U;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t DisplayID::update_ddc_i2c(uint8_t sda, uint8_t scl)//(uint16_t data, uint16_t dirs)
|
||||||
|
{
|
||||||
|
bool clk_gone_high = false;
|
||||||
|
|
||||||
|
if (scl != this->last_scl) {
|
||||||
|
this->last_scl = scl;
|
||||||
|
if (scl) {
|
||||||
|
clk_gone_high = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sda != this->last_sda) {
|
||||||
|
/* START = SDA goes high to low while SCL is high */
|
||||||
|
/* STOP = SDA goes low to high while SCL is high */
|
||||||
|
if (this->last_scl) {
|
||||||
|
if (!sda) {
|
||||||
|
LOG_F(9, "DDC-I2C: START condition detected!");
|
||||||
|
this->next_state = I2CState::DEV_ADDR;
|
||||||
|
this->bit_count = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_F(9, "DDC-I2C: STOP condition detected!");
|
||||||
|
this->next_state = I2CState::STOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set_result(sda, scl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clk_gone_high) {
|
||||||
|
return set_result(sda, scl);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this->next_state) {
|
||||||
|
case I2CState::STOP:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case I2CState::ACK:
|
||||||
|
this->bit_count = 0;
|
||||||
|
this->byte = 0;
|
||||||
|
switch (this->prev_state) {
|
||||||
|
case I2CState::DEV_ADDR:
|
||||||
|
if ((dev_addr & 0xFE) == 0xA0) {
|
||||||
|
sda = 0; /* send ACK */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_F(ERROR, "DDC-I2C: unknown device address 0x%X", this->dev_addr);
|
||||||
|
sda = 1; /* send NACK */
|
||||||
|
}
|
||||||
|
if (this->dev_addr & 1) {
|
||||||
|
this->next_state = I2CState::DATA;
|
||||||
|
this->data_ptr = this->edid;
|
||||||
|
this->byte = *(this->data_ptr++);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->next_state = I2CState::REG_ADDR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case I2CState::REG_ADDR:
|
||||||
|
this->next_state = I2CState::DATA;
|
||||||
|
if (!this->reg_addr) {
|
||||||
|
sda = 0; /* send ACK */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_F(ERROR, "DDC-I2C: unknown register address 0x%X", this->reg_addr);
|
||||||
|
sda = 1; /* send NACK */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case I2CState::DATA:
|
||||||
|
this->next_state = I2CState::DATA;
|
||||||
|
if (dev_addr & 1) {
|
||||||
|
if (!sda) {
|
||||||
|
/* load next data byte */
|
||||||
|
this->byte = *(this->data_ptr++);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_F(ERROR, "DDC-I2C: Oops! NACK received");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sda = 0; /* send ACK */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case I2CState::DEV_ADDR:
|
||||||
|
case I2CState::REG_ADDR:
|
||||||
|
this->byte = (this->byte << 1) | this->last_sda;
|
||||||
|
if (this->bit_count++ >= 7) {
|
||||||
|
this->bit_count = 0;
|
||||||
|
this->prev_state = this->next_state;
|
||||||
|
this->next_state = I2CState::ACK;
|
||||||
|
if (this->prev_state == I2CState::DEV_ADDR) {
|
||||||
|
LOG_F(9, "DDC-I2C: device address received, addr=0x%X", this->byte);
|
||||||
|
this->dev_addr = this->byte;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_F(9, "DDC-I2C: register address received, addr=0x%X", this->byte);
|
||||||
|
this->reg_addr = this->byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case I2CState::DATA:
|
||||||
|
sda = (this->byte >> (7 - this->bit_count)) & 1;
|
||||||
|
if (this->bit_count++ >= 7) {
|
||||||
|
this->bit_count = 0;
|
||||||
|
this->prev_state = this->next_state;
|
||||||
|
this->next_state = I2CState::ACK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return set_result(sda, scl);
|
||||||
|
}
|
110
devices/displayid.h
Normal file
110
devices/displayid.h
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||||
|
Copyright (C) 2018-20 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @file DisplayID class definitions.
|
||||||
|
|
||||||
|
DisplayID is a special purpose class for handling display
|
||||||
|
identification (aka Monitor Plug-n-Play) as required by
|
||||||
|
video cards.
|
||||||
|
|
||||||
|
DisplayID provides two methods for display identification:
|
||||||
|
- Apple monitor sense codes as described in the Technical Note HW30
|
||||||
|
- Display Data Channel (DDC) standardized by VESA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISPLAY_ID_H
|
||||||
|
#define DISPLAY_ID_H
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
/** I2C bus states. */
|
||||||
|
enum I2CState : uint8_t {
|
||||||
|
STOP = 0, /* transaction started */
|
||||||
|
START = 1, /* transaction ended (idle) */
|
||||||
|
DEV_ADDR = 2, /* receiving device address */
|
||||||
|
REG_ADDR = 3, /* receiving register address */
|
||||||
|
DATA = 4, /* sending/receiving data */
|
||||||
|
ACK = 5, /* sending/receiving acknowledge */
|
||||||
|
NACK = 6 /* no acknowledge (error) */
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayID {
|
||||||
|
public:
|
||||||
|
DisplayID();
|
||||||
|
~DisplayID() = default;
|
||||||
|
|
||||||
|
uint16_t read_monitor_sense(uint16_t data, uint16_t dirs);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint16_t set_result(uint8_t sda, uint8_t scl);
|
||||||
|
uint16_t update_ddc_i2c(uint8_t sda, uint8_t scl);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool i2c_on;
|
||||||
|
|
||||||
|
uint8_t std_sense_code;
|
||||||
|
uint8_t ext_sense_code;
|
||||||
|
|
||||||
|
/* DDC I2C variables. */
|
||||||
|
uint8_t next_state;
|
||||||
|
uint8_t prev_state;
|
||||||
|
uint8_t last_sda;
|
||||||
|
uint8_t last_scl;
|
||||||
|
uint16_t data_out;
|
||||||
|
int bit_count; /* number of bits processed so far */
|
||||||
|
uint8_t byte; /* byte value being currently transferred */
|
||||||
|
uint8_t dev_addr; /* current device address */
|
||||||
|
uint8_t reg_addr; /* current register address */
|
||||||
|
uint8_t *data_ptr; /* ptr to data byte to be transferred next */
|
||||||
|
|
||||||
|
uint8_t edid[128] = {
|
||||||
|
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
|
||||||
|
0x06, 0x10, 0x02, 0x9d, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x08, 0x09, 0x01, 0x01, 0x68, 0x20, 0x18, 0x28,
|
||||||
|
0xe8, 0x04, 0x89, 0xa0, 0x57, 0x4a, 0x9b, 0x26,
|
||||||
|
0x12, 0x48, 0x4c, 0x31, 0x2b, 0x80, 0x31, 0x59,
|
||||||
|
0x45, 0x59, 0x61, 0x59, 0xa9, 0x40, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x60, 0x16,
|
||||||
|
0x40, 0x40, 0x31, 0x70, 0x2b, 0x20, 0x20, 0x40,
|
||||||
|
0x23, 0x00, 0x38, 0xea, 0x10, 0x00, 0x00, 0x18,
|
||||||
|
0x48, 0x3f, 0x40, 0x32, 0x62, 0xb0, 0x32, 0x40,
|
||||||
|
0x40, 0xc2, 0x13, 0x00, 0x38, 0xea, 0x10, 0x00,
|
||||||
|
0x00, 0x18, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x30,
|
||||||
|
0xa0, 0x1e, 0x55, 0x10, 0x00, 0x0a, 0x20, 0x20,
|
||||||
|
0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc,
|
||||||
|
0x00, 0x53, 0x74, 0x75, 0x64, 0x69, 0x6f, 0x44,
|
||||||
|
0x73, 0x70, 0x6c, 0x79, 0x31, 0x37, 0x00, 0x19
|
||||||
|
};
|
||||||
|
|
||||||
|
/* More EDID:
|
||||||
|
00ff ffff ffff ff00 5a63 5151 0341 0000
|
||||||
|
240a 0102 1f28 1eb3 e850 69a7 5148 9b24
|
||||||
|
0e48 4cff ff80 3159 4559 6159 714f 8140
|
||||||
|
8199 a940 a94f 0000 00ff 0053 5a30 3336
|
||||||
|
3136 3634 330a 2020 0000 00fd 0032 b41e
|
||||||
|
61ff 000a 2020 2020 2020 0000 00fc 0056
|
||||||
|
6965 7753 6f6e 6963 2047 3831 0000 00fc
|
||||||
|
0030 2d34 4d0a 2020 2020 2020 2020 00f7
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DISPLAY_ID_H */
|
Reference in New Issue
Block a user