Basic support for display identification.

This commit is contained in:
Maxim Poliakovski 2020-04-14 01:04:37 +02:00
parent 925bcdfe9f
commit 54a86972cd
4 changed files with 459 additions and 15 deletions

View File

@ -21,21 +21,30 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <atirage.h>
#include <cstdint>
#include <thirdparty/loguru/loguru.hpp>
#include "endianswap.h"
#include "memreadwrite.h"
#include "pcidevice.h"
#include <thirdparty/loguru/loguru.hpp>
#include "displayid.h"
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[8], 0x0300005C);
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)
{
switch(size) {
switch (size) {
case 4:
return READ_DWORD_LE_A(buf);
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;
/* size-dependent endian conversion */
size_dep_write(&this->block_io_regs[offset], value, size);
switch (offset & ~3) {
switch (reg_offset & ~3) {
case ATI_CRTC_H_TOTAL_DISP:
reg_name = "CRTC_H_TOTAL_DISP";
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:
reg_name = "CRTC_INT_CNTL";
break;
case ATI_CRTC_GEN_CNTL:
reg_name = "CRTC_GEN_CNTL";
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:
reg_name = "MEM_ADDR_CFG";
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:
reg_name = "CLOCK_CNTL";
break;
@ -100,6 +139,9 @@ void ATIRage::write_reg(uint32_t offset, uint32_t value, uint32_t size)
case ATI_GEN_TEST_CNTL:
reg_name = "GEN_TEST_CNTL";
break;
case ATI_CFG_CHIP_ID:
reg_name = "CONFIG_CHIP_ID";
break;
case ATI_CFG_STAT0:
reg_name = "CONFIG_STAT0";
break;
@ -107,8 +149,49 @@ void ATIRage::write_reg(uint32_t offset, uint32_t value, uint32_t size)
reg_name = "unknown";
}
LOG_F(INFO, "ATI Rage: %s register at 0x%X set to 0x%X", reg_name,
offset & ~3, READ_DWORD_LE_A(&this->block_io_regs[offset & ~3]));
return reg_name;
}
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;
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);
return false;
}
@ -193,9 +276,7 @@ bool ATIRage::pci_io_read(uint32_t offset, uint32_t size, uint32_t *res)
return false;
}
*res = size_dep_read(&this->block_io_regs[offset - io_base], size);
LOG_F(INFO, "ATI Rage I/O space read, offset=0x%X, size=%d, val=0x%X", offset, size, *res);
*res = this->read_reg(offset - io_base, size);
return true;
}

View File

@ -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
#define ATI_RAGE_H
#include <cinttypes>
#include "pcidevice.h"
#include "displayid.h"
using namespace std;
@ -19,10 +41,11 @@ enum {
ATI_CRTC_H_SYNC_STRT_WID = 0x0004,
ATI_CRTC_V_TOTAL_DISP = 0x0008,
ATI_CRTC_V_SYNC_STRT_WID = 0x000C,
ATI_CRTC_OFF_PITCH = 0x0014,
ATI_CRTC_INT_CNTL = 0x0018,
ATI_CRTC_GEN_CNTL = 0x001C,
ATI_DSP_CONFIG = 0x0020,
ATI_DSP_TOGGLE = 0x0024,
ATI_DSP_ON_OFF = 0x0024,
ATI_TIMER_CFG = 0x0028,
ATI_MEM_BUF_CNTL = 0x002C,
ATI_MEM_ADDR_CFG = 0x0034,
@ -83,7 +106,7 @@ class ATIRage : public PCIDevice
{
public:
ATIRage(uint16_t dev_id);
~ATIRage() = default;
~ATIRage();
/* MMIODevice methods */
uint32_t read(uint32_t reg_start, uint32_t offset, int size);
@ -104,7 +127,9 @@ public:
protected:
uint32_t size_dep_read(uint8_t* buf, 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);
uint32_t read_reg(uint32_t offset, uint32_t size);
void write_reg(uint32_t offset, uint32_t value, uint32_t size);
private:
@ -116,5 +141,7 @@ private:
uint8_t block_io_regs[256] = { 0 };
uint8_t pci_cfg[256] = { 0 }; /* PCI configuration space */
DisplayID* disp_id;
};
#endif /* ATI_RAGE_H */

226
devices/displayid.cpp Normal file
View 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
View 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 */