2020-03-27 17:54:25 +00:00
|
|
|
/*
|
|
|
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
2023-06-02 08:23:21 +00:00
|
|
|
Copyright (C) 2018-24 divingkatae and maximum
|
2020-03-27 17:54:25 +00:00
|
|
|
(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/>.
|
|
|
|
*/
|
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
#include <core/bitops.h>
|
2022-01-26 15:45:21 +00:00
|
|
|
#include <devices/common/hwcomponent.h>
|
2021-10-23 18:17:47 +00:00
|
|
|
#include <devices/common/pci/pcidevice.h>
|
2022-07-18 09:48:23 +00:00
|
|
|
#include <devices/deviceregistry.h>
|
2021-10-23 18:17:47 +00:00
|
|
|
#include <devices/video/atirage.h>
|
|
|
|
#include <devices/video/displayid.h>
|
|
|
|
#include <endianswap.h>
|
|
|
|
#include <loguru.hpp>
|
|
|
|
#include <memaccess.h>
|
|
|
|
|
2021-02-04 21:55:58 +00:00
|
|
|
#include <map>
|
2020-04-13 23:04:37 +00:00
|
|
|
|
2021-02-08 01:20:32 +00:00
|
|
|
/* Mach64 post dividers. */
|
|
|
|
static const int mach64_post_div[8] = {
|
|
|
|
1, 2, 4, 8, // standard post dividers
|
|
|
|
3, 5, 6, 12 // alternate post dividers
|
|
|
|
};
|
|
|
|
|
2021-02-04 21:55:58 +00:00
|
|
|
/* Human readable Mach64 HW register names for easier debugging. */
|
|
|
|
static const std::map<uint16_t, std::string> mach64_reg_names = {
|
2024-02-27 06:48:04 +00:00
|
|
|
#define one_reg_name(x) {ATI_ ## x, #x}
|
|
|
|
one_reg_name(CRTC_H_TOTAL_DISP),
|
|
|
|
one_reg_name(CRTC_H_SYNC_STRT_WID),
|
|
|
|
one_reg_name(CRTC_V_TOTAL_DISP),
|
|
|
|
one_reg_name(CRTC_V_SYNC_STRT_WID),
|
|
|
|
one_reg_name(CRTC_VLINE_CRNT_VLINE),
|
|
|
|
one_reg_name(CRTC_OFF_PITCH),
|
|
|
|
one_reg_name(CRTC_INT_CNTL),
|
|
|
|
one_reg_name(CRTC_GEN_CNTL),
|
|
|
|
one_reg_name(DSP_CONFIG),
|
|
|
|
one_reg_name(DSP_ON_OFF),
|
|
|
|
one_reg_name(MEM_BUF_CNTL),
|
|
|
|
one_reg_name(MEM_ADDR_CFG),
|
|
|
|
one_reg_name(OVR_CLR),
|
|
|
|
one_reg_name(OVR_WID_LEFT_RIGHT),
|
|
|
|
one_reg_name(OVR_WID_TOP_BOTTOM),
|
|
|
|
one_reg_name(CUR_CLR0),
|
|
|
|
one_reg_name(CUR_CLR1),
|
|
|
|
one_reg_name(CUR_OFFSET),
|
|
|
|
one_reg_name(CUR_HORZ_VERT_POSN),
|
|
|
|
one_reg_name(CUR_HORZ_VERT_OFF),
|
|
|
|
one_reg_name(GP_IO),
|
|
|
|
one_reg_name(HW_DEBUG),
|
|
|
|
one_reg_name(SCRATCH_REG0),
|
|
|
|
one_reg_name(SCRATCH_REG1),
|
|
|
|
one_reg_name(SCRATCH_REG2),
|
|
|
|
one_reg_name(SCRATCH_REG3),
|
|
|
|
one_reg_name(CLOCK_CNTL),
|
|
|
|
one_reg_name(BUS_CNTL),
|
|
|
|
one_reg_name(EXT_MEM_CNTL),
|
|
|
|
one_reg_name(MEM_CNTL),
|
|
|
|
one_reg_name(DAC_REGS),
|
|
|
|
one_reg_name(DAC_CNTL),
|
|
|
|
one_reg_name(GEN_TEST_CNTL),
|
|
|
|
one_reg_name(CUSTOM_MACRO_CNTL),
|
|
|
|
one_reg_name(CONFIG_CHIP_ID),
|
|
|
|
one_reg_name(CONFIG_STAT0),
|
|
|
|
one_reg_name(SRC_CNTL),
|
|
|
|
one_reg_name(SCALE_3D_CNTL),
|
|
|
|
one_reg_name(FIFO_STAT),
|
|
|
|
one_reg_name(GUI_STAT),
|
|
|
|
one_reg_name(MPP_CONFIG),
|
|
|
|
one_reg_name(MPP_STROBE_SEQ),
|
|
|
|
one_reg_name(MPP_ADDR),
|
|
|
|
one_reg_name(MPP_DATA),
|
|
|
|
one_reg_name(TVO_CNTL),
|
|
|
|
one_reg_name(SETUP_CNTL),
|
2021-02-04 21:55:58 +00:00
|
|
|
};
|
|
|
|
|
2022-07-18 09:48:23 +00:00
|
|
|
ATIRage::ATIRage(uint16_t dev_id)
|
2024-04-23 12:14:46 +00:00
|
|
|
: PCIDevice("ati-rage"), VideoCtrlBase()
|
2021-11-09 12:40:13 +00:00
|
|
|
{
|
2021-02-04 18:00:23 +00:00
|
|
|
uint8_t asic_id;
|
|
|
|
|
2022-01-26 15:45:21 +00:00
|
|
|
supports_types(HWCompType::MMIO_DEV | HWCompType::PCI_DEV);
|
|
|
|
|
2022-07-18 09:48:23 +00:00
|
|
|
this->vram_size = GET_INT_PROP("gfxmem_size") << 20; // convert MBs to bytes
|
2020-05-28 21:47:20 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// allocate video RAM
|
2021-11-17 22:30:43 +00:00
|
|
|
this->vram_ptr = std::unique_ptr<uint8_t[]> (new uint8_t[this->vram_size]);
|
2020-05-28 21:47:20 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// ATI Rage driver needs to know ASIC ID (manufacturer's internal chip code)
|
|
|
|
// to operate properly
|
2021-02-04 18:00:23 +00:00
|
|
|
switch (dev_id) {
|
2021-09-11 19:02:46 +00:00
|
|
|
case ATI_RAGE_GT_DEV_ID:
|
|
|
|
asic_id = 0x9A; // GT-B2U3 fabricated by UMC
|
2023-10-09 17:18:55 +00:00
|
|
|
this->cmd_fifo_size = 48;
|
2021-09-11 19:02:46 +00:00
|
|
|
break;
|
2021-02-04 18:00:23 +00:00
|
|
|
case ATI_RAGE_PRO_DEV_ID:
|
|
|
|
asic_id = 0x5C; // R3B/D/P-A4 fabricated by UMC
|
2023-10-09 17:18:55 +00:00
|
|
|
this->cmd_fifo_size = 128;
|
2021-02-04 18:00:23 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
asic_id = 0xDD;
|
|
|
|
LOG_F(WARNING, "ATI Rage: bogus ASIC ID assigned!");
|
|
|
|
}
|
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// set up PCI configuration space header
|
2022-03-13 19:58:31 +00:00
|
|
|
this->vendor_id = PCI_VENDOR_ATI;
|
|
|
|
this->device_id = dev_id;
|
|
|
|
this->subsys_vndr = PCI_VENDOR_ATI;
|
|
|
|
this->subsys_id = 0x6987; // adapter ID
|
|
|
|
this->class_rev = (0x030000 << 8) | asic_id;
|
|
|
|
this->min_gnt = 8;
|
|
|
|
this->irq_pin = 1;
|
2024-02-27 10:43:45 +00:00
|
|
|
for (int i = 0; i < this->aperture_count; i++) {
|
|
|
|
this->bars_cfg[i] = (uint32_t)(-this->aperture_size[i] | this->aperture_flag[i]);
|
|
|
|
}
|
|
|
|
this->finish_config_bars();
|
2022-03-13 19:58:31 +00:00
|
|
|
|
|
|
|
this->pci_notify_bar_change = [this](int bar_num) {
|
|
|
|
this->notify_bar_change(bar_num);
|
|
|
|
};
|
2020-04-13 23:04:37 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// stuff default values into chip registers
|
2024-02-27 00:29:32 +00:00
|
|
|
this->regs[ATI_CONFIG_CHIP_ID] = (asic_id << ATI_CFG_CHIP_MAJOR) | (dev_id << ATI_CFG_CHIP_TYPE);
|
2021-02-04 18:00:23 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// initialize display identification
|
2021-12-05 19:07:22 +00:00
|
|
|
this->disp_id = std::unique_ptr<DisplayID> (new DisplayID());
|
2023-04-01 17:05:34 +00:00
|
|
|
|
|
|
|
uint8_t mon_code = this->disp_id->read_monitor_sense(0, 0);
|
|
|
|
|
2023-04-10 23:23:40 +00:00
|
|
|
this->regs[ATI_GP_IO] = ((mon_code & 6) << 11) | ((mon_code & 1) << 8);
|
2024-02-27 11:01:40 +00:00
|
|
|
insert_bits<uint32_t>(this->regs[ATI_GUI_STAT], 32, ATI_FIFO_CNT, ATI_FIFO_CNT_size);
|
2024-02-27 11:03:32 +00:00
|
|
|
set_bit(regs[ATI_CRTC_GEN_CNTL], ATI_CRTC_DISPLAY_DIS); // because blank_on is true
|
2020-03-27 17:54:25 +00:00
|
|
|
}
|
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
void ATIRage::change_one_bar(uint32_t &aperture, uint32_t aperture_size,
|
|
|
|
uint32_t aperture_new, int bar_num) {
|
2024-02-27 10:43:45 +00:00
|
|
|
if (aperture != aperture_new) {
|
|
|
|
if (aperture)
|
2024-03-29 20:21:58 +00:00
|
|
|
this->host_instance->pci_unregister_mmio_region(aperture,
|
|
|
|
aperture_size, this);
|
2024-02-27 10:43:45 +00:00
|
|
|
|
|
|
|
aperture = aperture_new;
|
|
|
|
if (aperture)
|
|
|
|
this->host_instance->pci_register_mmio_region(aperture, aperture_size, this);
|
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(INFO, "%s: aperture[%d] set to 0x%08X", this->name.c_str(),
|
|
|
|
bar_num, aperture);
|
2024-02-27 10:43:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-13 19:58:31 +00:00
|
|
|
void ATIRage::notify_bar_change(int bar_num)
|
|
|
|
{
|
|
|
|
switch (bar_num) {
|
|
|
|
case 0:
|
2024-03-29 20:21:58 +00:00
|
|
|
change_one_bar(this->aperture_base[bar_num],
|
|
|
|
this->aperture_size[bar_num] - this->vram_size,
|
|
|
|
this->bars[bar_num] & ~15, bar_num);
|
2022-03-13 19:58:31 +00:00
|
|
|
break;
|
|
|
|
case 2:
|
2024-03-29 20:21:58 +00:00
|
|
|
change_one_bar(this->aperture_base[bar_num],
|
|
|
|
this->aperture_size[bar_num],
|
|
|
|
this->bars[bar_num] & ~15, bar_num);
|
2024-02-27 10:43:45 +00:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
this->aperture_base[1] = this->bars[bar_num] & ~3;
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(INFO, "%s: I/O space address set to 0x%08X", this->name.c_str(),
|
|
|
|
this->aperture_base[1]);
|
2022-03-13 19:58:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fix PCI config r/w of byte and word and unaligned.
dingusppc could not read bytes from offset 1,2,3 or words from offset 2.
dingusppc did not read words from offset 1,3 and longs from offset 1,2,3 in the same way as a real Power Mac 8600 or B&W G3.
This commit fixes those issues.
- Added pci_cfg_rev_read. It takes a 32 bit value from offset 0 and returns a value of the specified size using bytes starting from the specified offset. Offsets 4,5, & 6 wrap around to 0,1, & 2 respectively. The result bytes are in flipped order as required by the read method (so a value of 0x12345678 is returned as 0x78563412)
A real Power Mac 8600 might return a random byte for offset 4, 5, 6 for vci0 but usually not for pci1. A B&W G3 seems to always wrap around correctly. We won't read random bytes, and we won't read a default such as 00 or FF. We'll do the wrap around which makes the most sense because writing 0x12345678 to any offset and reading from the same offset should produce the value that was written.
- Added pci_cfg_rev_write. It takes a 32 bit value from offset 0, and modifies a specified number of bytes starting at a specified offset with the offset wrapping around to 0 if it exceeds 3. The modified bytes take their new values from the flipped bytes passed to pci_cfg_write. When size is 4, the original value is not used since all bytes will be modified.
Basically, those two functions handle all the sizes and all the offsets and replace calls to BYTESWAP_32, read_mem or read_mem_rev, and write_mem or write_mem_rev.
read_mem_rev, as it was used by pcidevice and some other places, could read beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always read the wrong byte or word if they were not at offset 0. Same for read_mem as used by mpc106.
write_mem_rev, as it was used by pcidevice and some other places, could write beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always write the wrong byte or word if they were not at offset 0. Same for write_mem as used by mpc106.
pcidevice:
- The logging macros should be used to handle all config register access logging.
- Unaligned PCI config register accesses will be output as ERROR instead of WARNING.
- The logging macros include the offset and size. They also include the value for named registers or for writes.
- Added MMIODevice read and write methods so that PCIDevice is not abstract if a PCIDevice doesn't override the read and write method since some PCIDevices don't have MMIO.
pcihost:
- Added pci_find_device stub for handling PCI bridges in future commit.
bandit and mpc106:
- PCI host controllers will handle all PCI config access alignment and sizing. A PCIDevice will always access config registers as 32 bits on a 4 byte boundary. The AccessDetails passed to a PCIDevice config read or write method is there only for logging purposes.
bandit:
- Common MMIO code is moved to new BanditHost class so both Bandit and Chaos can use it. PCI related code is moved to new BanditPCI class.
- Simplify IDSEL to/from PCI device number conversion by removing the shift or subtract.
- Remove BANDIT_ID_SEL check. The IDSEL conversion to PCI device number can find the bandit PCI device.
- For logging, make best guess of PCI device number from invalid IDSEL - the result is always reasonable for device 0x00 to 0x0A when accessing config register 0x00 (as one would do when scanning for PCI devices like lspci does).
mpc106:
- Common config space code is put in cfg_setup. It handles extracting the offset.
- Added code to log access to unimplemented config registers of grackle.
- Don't call setup_ram when writing to config registers that setup_ram doesn't use.
- pci_cfg_read calls READ_DWORD_LE_A and pci_cfg_write calls WRITE_DWORD_LE_A. When reading or writing memory that is organized as little endian dwords, such as my_pci_cfg_hdr of mpc106, the function should explicitly state that it's little endian so that the emulator may be ported one day to a CPU architecture that is not little endian.
atirage:
- The changes correctly place user_cfg at byte 0x40 instead of 0x43 and writes the correct byte depending on size and offset.
2022-09-02 10:32:28 +00:00
|
|
|
uint32_t ATIRage::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
|
2022-03-13 19:58:31 +00:00
|
|
|
{
|
|
|
|
if (reg_offs < 64) {
|
Fix PCI config r/w of byte and word and unaligned.
dingusppc could not read bytes from offset 1,2,3 or words from offset 2.
dingusppc did not read words from offset 1,3 and longs from offset 1,2,3 in the same way as a real Power Mac 8600 or B&W G3.
This commit fixes those issues.
- Added pci_cfg_rev_read. It takes a 32 bit value from offset 0 and returns a value of the specified size using bytes starting from the specified offset. Offsets 4,5, & 6 wrap around to 0,1, & 2 respectively. The result bytes are in flipped order as required by the read method (so a value of 0x12345678 is returned as 0x78563412)
A real Power Mac 8600 might return a random byte for offset 4, 5, 6 for vci0 but usually not for pci1. A B&W G3 seems to always wrap around correctly. We won't read random bytes, and we won't read a default such as 00 or FF. We'll do the wrap around which makes the most sense because writing 0x12345678 to any offset and reading from the same offset should produce the value that was written.
- Added pci_cfg_rev_write. It takes a 32 bit value from offset 0, and modifies a specified number of bytes starting at a specified offset with the offset wrapping around to 0 if it exceeds 3. The modified bytes take their new values from the flipped bytes passed to pci_cfg_write. When size is 4, the original value is not used since all bytes will be modified.
Basically, those two functions handle all the sizes and all the offsets and replace calls to BYTESWAP_32, read_mem or read_mem_rev, and write_mem or write_mem_rev.
read_mem_rev, as it was used by pcidevice and some other places, could read beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always read the wrong byte or word if they were not at offset 0. Same for read_mem as used by mpc106.
write_mem_rev, as it was used by pcidevice and some other places, could write beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always write the wrong byte or word if they were not at offset 0. Same for write_mem as used by mpc106.
pcidevice:
- The logging macros should be used to handle all config register access logging.
- Unaligned PCI config register accesses will be output as ERROR instead of WARNING.
- The logging macros include the offset and size. They also include the value for named registers or for writes.
- Added MMIODevice read and write methods so that PCIDevice is not abstract if a PCIDevice doesn't override the read and write method since some PCIDevices don't have MMIO.
pcihost:
- Added pci_find_device stub for handling PCI bridges in future commit.
bandit and mpc106:
- PCI host controllers will handle all PCI config access alignment and sizing. A PCIDevice will always access config registers as 32 bits on a 4 byte boundary. The AccessDetails passed to a PCIDevice config read or write method is there only for logging purposes.
bandit:
- Common MMIO code is moved to new BanditHost class so both Bandit and Chaos can use it. PCI related code is moved to new BanditPCI class.
- Simplify IDSEL to/from PCI device number conversion by removing the shift or subtract.
- Remove BANDIT_ID_SEL check. The IDSEL conversion to PCI device number can find the bandit PCI device.
- For logging, make best guess of PCI device number from invalid IDSEL - the result is always reasonable for device 0x00 to 0x0A when accessing config register 0x00 (as one would do when scanning for PCI devices like lspci does).
mpc106:
- Common config space code is put in cfg_setup. It handles extracting the offset.
- Added code to log access to unimplemented config registers of grackle.
- Don't call setup_ram when writing to config registers that setup_ram doesn't use.
- pci_cfg_read calls READ_DWORD_LE_A and pci_cfg_write calls WRITE_DWORD_LE_A. When reading or writing memory that is organized as little endian dwords, such as my_pci_cfg_hdr of mpc106, the function should explicitly state that it's little endian so that the emulator may be ported one day to a CPU architecture that is not little endian.
atirage:
- The changes correctly place user_cfg at byte 0x40 instead of 0x43 and writes the correct byte depending on size and offset.
2022-09-02 10:32:28 +00:00
|
|
|
return PCIDevice::pci_cfg_read(reg_offs, details);
|
2022-03-13 19:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (reg_offs) {
|
|
|
|
case 0x40:
|
2022-09-02 23:24:06 +00:00
|
|
|
return this->user_cfg;
|
2022-03-13 19:58:31 +00:00
|
|
|
default:
|
Fix PCI config r/w of byte and word and unaligned.
dingusppc could not read bytes from offset 1,2,3 or words from offset 2.
dingusppc did not read words from offset 1,3 and longs from offset 1,2,3 in the same way as a real Power Mac 8600 or B&W G3.
This commit fixes those issues.
- Added pci_cfg_rev_read. It takes a 32 bit value from offset 0 and returns a value of the specified size using bytes starting from the specified offset. Offsets 4,5, & 6 wrap around to 0,1, & 2 respectively. The result bytes are in flipped order as required by the read method (so a value of 0x12345678 is returned as 0x78563412)
A real Power Mac 8600 might return a random byte for offset 4, 5, 6 for vci0 but usually not for pci1. A B&W G3 seems to always wrap around correctly. We won't read random bytes, and we won't read a default such as 00 or FF. We'll do the wrap around which makes the most sense because writing 0x12345678 to any offset and reading from the same offset should produce the value that was written.
- Added pci_cfg_rev_write. It takes a 32 bit value from offset 0, and modifies a specified number of bytes starting at a specified offset with the offset wrapping around to 0 if it exceeds 3. The modified bytes take their new values from the flipped bytes passed to pci_cfg_write. When size is 4, the original value is not used since all bytes will be modified.
Basically, those two functions handle all the sizes and all the offsets and replace calls to BYTESWAP_32, read_mem or read_mem_rev, and write_mem or write_mem_rev.
read_mem_rev, as it was used by pcidevice and some other places, could read beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always read the wrong byte or word if they were not at offset 0. Same for read_mem as used by mpc106.
write_mem_rev, as it was used by pcidevice and some other places, could write beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always write the wrong byte or word if they were not at offset 0. Same for write_mem as used by mpc106.
pcidevice:
- The logging macros should be used to handle all config register access logging.
- Unaligned PCI config register accesses will be output as ERROR instead of WARNING.
- The logging macros include the offset and size. They also include the value for named registers or for writes.
- Added MMIODevice read and write methods so that PCIDevice is not abstract if a PCIDevice doesn't override the read and write method since some PCIDevices don't have MMIO.
pcihost:
- Added pci_find_device stub for handling PCI bridges in future commit.
bandit and mpc106:
- PCI host controllers will handle all PCI config access alignment and sizing. A PCIDevice will always access config registers as 32 bits on a 4 byte boundary. The AccessDetails passed to a PCIDevice config read or write method is there only for logging purposes.
bandit:
- Common MMIO code is moved to new BanditHost class so both Bandit and Chaos can use it. PCI related code is moved to new BanditPCI class.
- Simplify IDSEL to/from PCI device number conversion by removing the shift or subtract.
- Remove BANDIT_ID_SEL check. The IDSEL conversion to PCI device number can find the bandit PCI device.
- For logging, make best guess of PCI device number from invalid IDSEL - the result is always reasonable for device 0x00 to 0x0A when accessing config register 0x00 (as one would do when scanning for PCI devices like lspci does).
mpc106:
- Common config space code is put in cfg_setup. It handles extracting the offset.
- Added code to log access to unimplemented config registers of grackle.
- Don't call setup_ram when writing to config registers that setup_ram doesn't use.
- pci_cfg_read calls READ_DWORD_LE_A and pci_cfg_write calls WRITE_DWORD_LE_A. When reading or writing memory that is organized as little endian dwords, such as my_pci_cfg_hdr of mpc106, the function should explicitly state that it's little endian so that the emulator may be ported one day to a CPU architecture that is not little endian.
atirage:
- The changes correctly place user_cfg at byte 0x40 instead of 0x43 and writes the correct byte depending on size and offset.
2022-09-02 10:32:28 +00:00
|
|
|
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
|
2022-03-13 19:58:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
Fix PCI config r/w of byte and word and unaligned.
dingusppc could not read bytes from offset 1,2,3 or words from offset 2.
dingusppc did not read words from offset 1,3 and longs from offset 1,2,3 in the same way as a real Power Mac 8600 or B&W G3.
This commit fixes those issues.
- Added pci_cfg_rev_read. It takes a 32 bit value from offset 0 and returns a value of the specified size using bytes starting from the specified offset. Offsets 4,5, & 6 wrap around to 0,1, & 2 respectively. The result bytes are in flipped order as required by the read method (so a value of 0x12345678 is returned as 0x78563412)
A real Power Mac 8600 might return a random byte for offset 4, 5, 6 for vci0 but usually not for pci1. A B&W G3 seems to always wrap around correctly. We won't read random bytes, and we won't read a default such as 00 or FF. We'll do the wrap around which makes the most sense because writing 0x12345678 to any offset and reading from the same offset should produce the value that was written.
- Added pci_cfg_rev_write. It takes a 32 bit value from offset 0, and modifies a specified number of bytes starting at a specified offset with the offset wrapping around to 0 if it exceeds 3. The modified bytes take their new values from the flipped bytes passed to pci_cfg_write. When size is 4, the original value is not used since all bytes will be modified.
Basically, those two functions handle all the sizes and all the offsets and replace calls to BYTESWAP_32, read_mem or read_mem_rev, and write_mem or write_mem_rev.
read_mem_rev, as it was used by pcidevice and some other places, could read beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always read the wrong byte or word if they were not at offset 0. Same for read_mem as used by mpc106.
write_mem_rev, as it was used by pcidevice and some other places, could write beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always write the wrong byte or word if they were not at offset 0. Same for write_mem as used by mpc106.
pcidevice:
- The logging macros should be used to handle all config register access logging.
- Unaligned PCI config register accesses will be output as ERROR instead of WARNING.
- The logging macros include the offset and size. They also include the value for named registers or for writes.
- Added MMIODevice read and write methods so that PCIDevice is not abstract if a PCIDevice doesn't override the read and write method since some PCIDevices don't have MMIO.
pcihost:
- Added pci_find_device stub for handling PCI bridges in future commit.
bandit and mpc106:
- PCI host controllers will handle all PCI config access alignment and sizing. A PCIDevice will always access config registers as 32 bits on a 4 byte boundary. The AccessDetails passed to a PCIDevice config read or write method is there only for logging purposes.
bandit:
- Common MMIO code is moved to new BanditHost class so both Bandit and Chaos can use it. PCI related code is moved to new BanditPCI class.
- Simplify IDSEL to/from PCI device number conversion by removing the shift or subtract.
- Remove BANDIT_ID_SEL check. The IDSEL conversion to PCI device number can find the bandit PCI device.
- For logging, make best guess of PCI device number from invalid IDSEL - the result is always reasonable for device 0x00 to 0x0A when accessing config register 0x00 (as one would do when scanning for PCI devices like lspci does).
mpc106:
- Common config space code is put in cfg_setup. It handles extracting the offset.
- Added code to log access to unimplemented config registers of grackle.
- Don't call setup_ram when writing to config registers that setup_ram doesn't use.
- pci_cfg_read calls READ_DWORD_LE_A and pci_cfg_write calls WRITE_DWORD_LE_A. When reading or writing memory that is organized as little endian dwords, such as my_pci_cfg_hdr of mpc106, the function should explicitly state that it's little endian so that the emulator may be ported one day to a CPU architecture that is not little endian.
atirage:
- The changes correctly place user_cfg at byte 0x40 instead of 0x43 and writes the correct byte depending on size and offset.
2022-09-02 10:32:28 +00:00
|
|
|
void ATIRage::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
|
2022-03-13 19:58:31 +00:00
|
|
|
{
|
|
|
|
if (reg_offs < 64) {
|
Fix PCI config r/w of byte and word and unaligned.
dingusppc could not read bytes from offset 1,2,3 or words from offset 2.
dingusppc did not read words from offset 1,3 and longs from offset 1,2,3 in the same way as a real Power Mac 8600 or B&W G3.
This commit fixes those issues.
- Added pci_cfg_rev_read. It takes a 32 bit value from offset 0 and returns a value of the specified size using bytes starting from the specified offset. Offsets 4,5, & 6 wrap around to 0,1, & 2 respectively. The result bytes are in flipped order as required by the read method (so a value of 0x12345678 is returned as 0x78563412)
A real Power Mac 8600 might return a random byte for offset 4, 5, 6 for vci0 but usually not for pci1. A B&W G3 seems to always wrap around correctly. We won't read random bytes, and we won't read a default such as 00 or FF. We'll do the wrap around which makes the most sense because writing 0x12345678 to any offset and reading from the same offset should produce the value that was written.
- Added pci_cfg_rev_write. It takes a 32 bit value from offset 0, and modifies a specified number of bytes starting at a specified offset with the offset wrapping around to 0 if it exceeds 3. The modified bytes take their new values from the flipped bytes passed to pci_cfg_write. When size is 4, the original value is not used since all bytes will be modified.
Basically, those two functions handle all the sizes and all the offsets and replace calls to BYTESWAP_32, read_mem or read_mem_rev, and write_mem or write_mem_rev.
read_mem_rev, as it was used by pcidevice and some other places, could read beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always read the wrong byte or word if they were not at offset 0. Same for read_mem as used by mpc106.
write_mem_rev, as it was used by pcidevice and some other places, could write beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always write the wrong byte or word if they were not at offset 0. Same for write_mem as used by mpc106.
pcidevice:
- The logging macros should be used to handle all config register access logging.
- Unaligned PCI config register accesses will be output as ERROR instead of WARNING.
- The logging macros include the offset and size. They also include the value for named registers or for writes.
- Added MMIODevice read and write methods so that PCIDevice is not abstract if a PCIDevice doesn't override the read and write method since some PCIDevices don't have MMIO.
pcihost:
- Added pci_find_device stub for handling PCI bridges in future commit.
bandit and mpc106:
- PCI host controllers will handle all PCI config access alignment and sizing. A PCIDevice will always access config registers as 32 bits on a 4 byte boundary. The AccessDetails passed to a PCIDevice config read or write method is there only for logging purposes.
bandit:
- Common MMIO code is moved to new BanditHost class so both Bandit and Chaos can use it. PCI related code is moved to new BanditPCI class.
- Simplify IDSEL to/from PCI device number conversion by removing the shift or subtract.
- Remove BANDIT_ID_SEL check. The IDSEL conversion to PCI device number can find the bandit PCI device.
- For logging, make best guess of PCI device number from invalid IDSEL - the result is always reasonable for device 0x00 to 0x0A when accessing config register 0x00 (as one would do when scanning for PCI devices like lspci does).
mpc106:
- Common config space code is put in cfg_setup. It handles extracting the offset.
- Added code to log access to unimplemented config registers of grackle.
- Don't call setup_ram when writing to config registers that setup_ram doesn't use.
- pci_cfg_read calls READ_DWORD_LE_A and pci_cfg_write calls WRITE_DWORD_LE_A. When reading or writing memory that is organized as little endian dwords, such as my_pci_cfg_hdr of mpc106, the function should explicitly state that it's little endian so that the emulator may be ported one day to a CPU architecture that is not little endian.
atirage:
- The changes correctly place user_cfg at byte 0x40 instead of 0x43 and writes the correct byte depending on size and offset.
2022-09-02 10:32:28 +00:00
|
|
|
PCIDevice::pci_cfg_write(reg_offs, value, details);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (reg_offs) {
|
|
|
|
case 0x40:
|
|
|
|
this->user_cfg = value;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
|
2022-03-13 19:58:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-27 06:48:04 +00:00
|
|
|
const char* ATIRage::get_reg_name(uint32_t reg_num) {
|
|
|
|
auto iter = mach64_reg_names.find(reg_num);
|
2021-02-04 21:55:58 +00:00
|
|
|
if (iter != mach64_reg_names.end()) {
|
|
|
|
return iter->second.c_str();
|
|
|
|
} else {
|
|
|
|
return "unknown Mach64 register";
|
2020-03-31 19:19:10 +00:00
|
|
|
}
|
2020-04-13 23:04:37 +00:00
|
|
|
}
|
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
uint32_t ATIRage::read_reg(uint32_t reg_offset, uint32_t size) {
|
2024-02-27 06:48:04 +00:00
|
|
|
uint32_t reg_num = reg_offset >> 2;
|
2023-04-01 17:05:34 +00:00
|
|
|
uint32_t offset = reg_offset & 3;
|
2024-02-27 06:48:04 +00:00
|
|
|
uint64_t result = this->regs[reg_num];
|
2020-04-13 23:04:37 +00:00
|
|
|
|
2024-02-27 06:48:04 +00:00
|
|
|
switch (reg_num) {
|
2021-02-05 00:10:56 +00:00
|
|
|
case ATI_CLOCK_CNTL:
|
2024-02-27 10:56:18 +00:00
|
|
|
if (offset <= 2 && offset + size > 2) {
|
2024-02-27 00:29:32 +00:00
|
|
|
uint8_t pll_addr = extract_bits<uint64_t>(result, ATI_PLL_ADDR, ATI_PLL_ADDR_size);
|
|
|
|
insert_bits<uint64_t>(result, this->plls[pll_addr], ATI_PLL_DATA, ATI_PLL_DATA_size);
|
2021-02-05 00:10:56 +00:00
|
|
|
}
|
|
|
|
break;
|
2020-06-10 23:30:10 +00:00
|
|
|
case ATI_DAC_REGS:
|
2023-04-01 17:05:34 +00:00
|
|
|
switch (reg_offset) {
|
|
|
|
case ATI_DAC_W_INDEX:
|
|
|
|
insert_bits<uint64_t>(result, this->dac_wr_index, 0, 8);
|
|
|
|
break;
|
|
|
|
case ATI_DAC_MASK:
|
|
|
|
insert_bits<uint64_t>(result, this->dac_mask, 16, 8);
|
|
|
|
break;
|
|
|
|
case ATI_DAC_R_INDEX:
|
|
|
|
insert_bits<uint64_t>(result, this->dac_rd_index, 24, 8);
|
|
|
|
break;
|
|
|
|
case ATI_DAC_DATA:
|
2023-04-01 10:08:25 +00:00
|
|
|
if (!this->comp_index) {
|
2023-04-01 17:05:34 +00:00
|
|
|
uint8_t alpha; // temp variable for unused alpha
|
2024-02-18 10:20:25 +00:00
|
|
|
get_palette_color(this->dac_rd_index, color_buf[0],
|
|
|
|
color_buf[1], color_buf[2], alpha);
|
2023-04-01 10:08:25 +00:00
|
|
|
}
|
2023-04-01 17:05:34 +00:00
|
|
|
insert_bits<uint64_t>(result, color_buf[this->comp_index], 8, 8);
|
2023-04-01 10:08:25 +00:00
|
|
|
if (++this->comp_index >= 3) {
|
2023-04-01 17:05:34 +00:00
|
|
|
this->dac_rd_index++; // auto-increment reading index
|
|
|
|
this->comp_index = 0; // reset color component index
|
2020-06-10 23:30:10 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-13 23:04:37 +00:00
|
|
|
break;
|
2023-10-09 17:18:55 +00:00
|
|
|
case ATI_GUI_STAT:
|
|
|
|
result = this->cmd_fifo_size << 16; // HACK: tell the guest the command FIFO is empty
|
|
|
|
break;
|
2020-04-13 23:04:37 +00:00
|
|
|
}
|
|
|
|
|
2024-02-27 06:48:04 +00:00
|
|
|
if (offset || size != 4) { // slow path
|
2023-04-01 17:05:34 +00:00
|
|
|
if ((offset + size) > 4) {
|
2024-02-27 06:48:04 +00:00
|
|
|
result |= (uint64_t)(this->regs[reg_num + 1]) << 32;
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
result = extract_bits<uint64_t>(result, offset * 8, size * 8);
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
|
|
|
|
return static_cast<uint32_t>(result);
|
2020-04-13 23:04:37 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 10:35:38 +00:00
|
|
|
#define WRITE_VALUE_AND_LOG(level) \
|
|
|
|
do { \
|
|
|
|
this->regs[reg_num] = new_value; \
|
|
|
|
if (reg_num != ATI_CRTC_INT_CNTL) { \
|
|
|
|
LOG_F(level, "%s: write %s %04x.%c = %0*x = %08x", this->name.c_str(), \
|
|
|
|
get_reg_name(reg_num), reg_offset, SIZE_ARG(size), size * 2, \
|
|
|
|
(uint32_t)extract_bits<uint64_t>(value, offset * 8, size * 8), new_value \
|
|
|
|
); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
void ATIRage::write_reg(uint32_t reg_offset, uint32_t value, uint32_t size) {
|
2024-02-27 06:48:04 +00:00
|
|
|
uint32_t reg_num = reg_offset >> 2;
|
2023-04-10 23:23:40 +00:00
|
|
|
uint32_t offset = reg_offset & 3;
|
2024-02-27 06:48:04 +00:00
|
|
|
uint32_t old_value = this->regs[reg_num];
|
|
|
|
uint32_t new_value;
|
2020-04-13 23:04:37 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
if (offset || size != 4) { // slow path
|
|
|
|
if ((offset + size) > 4) {
|
|
|
|
ABORT_F("%s: unaligned DWORD writes not implemented", this->name.c_str());
|
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
uint64_t val = old_value;
|
|
|
|
insert_bits<uint64_t>(val, value, offset * 8, size * 8);
|
|
|
|
value = static_cast<uint32_t>(val);
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
2020-04-13 23:04:37 +00:00
|
|
|
|
2024-02-27 06:48:04 +00:00
|
|
|
switch (reg_num) {
|
2023-04-01 17:05:34 +00:00
|
|
|
case ATI_CRTC_H_TOTAL_DISP:
|
2024-02-27 06:48:04 +00:00
|
|
|
new_value = value;
|
2023-04-01 17:05:34 +00:00
|
|
|
LOG_F(9, "%s: ATI_CRTC_H_TOTAL_DISP set to 0x%08X", this->name.c_str(), value);
|
|
|
|
break;
|
2024-02-27 11:50:46 +00:00
|
|
|
case ATI_CRTC_VLINE_CRNT_VLINE:
|
|
|
|
new_value = old_value;
|
|
|
|
insert_bits<uint32_t>(new_value, value, ATI_CRTC_VLINE, ATI_CRTC_VLINE_size);
|
|
|
|
break;
|
2021-09-11 19:02:46 +00:00
|
|
|
case ATI_CRTC_OFF_PITCH:
|
2024-02-27 06:48:04 +00:00
|
|
|
new_value = value;
|
2024-02-26 10:35:38 +00:00
|
|
|
WRITE_VALUE_AND_LOG(9);
|
|
|
|
this->crtc_update();
|
|
|
|
return;
|
2024-02-27 11:55:14 +00:00
|
|
|
case ATI_CRTC_INT_CNTL: {
|
|
|
|
uint32_t bits_read_only =
|
|
|
|
(1 << ATI_CRTC_VBLANK) |
|
|
|
|
(1 << ATI_CRTC_VLINE_SYNC) |
|
|
|
|
(1 << ATI_CRTC_FRAME) |
|
|
|
|
#if 1
|
|
|
|
#else
|
|
|
|
(1 << ATI_CRTC2_VBLANK) |
|
|
|
|
(1 << ATI_CRTC2_VLINE_SYNC) |
|
|
|
|
#endif
|
|
|
|
0;
|
|
|
|
|
|
|
|
uint32_t bits_AK =
|
|
|
|
(1 << ATI_CRTC_VBLANK_INT_AK) |
|
|
|
|
(1 << ATI_CRTC_VLINE_INT_AK) |
|
|
|
|
#if 1
|
|
|
|
(1 << ATI_VIDEOIN_EVEN_INT_AK) |
|
|
|
|
(1 << ATI_VIDEOIN_ODD_INT_AK) |
|
|
|
|
(1 << ATI_OVERLAY_EOF_INT_AK) |
|
|
|
|
(1 << ATI_VMC_EC_INT_AK) |
|
|
|
|
#else
|
|
|
|
(1 << ATI_SNAPSHOT_INT_AK) |
|
|
|
|
(1 << ATI_I2C_INT_AK) |
|
|
|
|
(1 << ATI_CRTC2_VBLANK_INT_AK) |
|
|
|
|
(1 << ATI_CRTC2_VLINE_INT_AK) |
|
|
|
|
(1 << ATI_CUPBUF0_INT_AK) |
|
|
|
|
(1 << ATI_CUPBUF1_INT_AK) |
|
|
|
|
(1 << ATI_OVERLAY_EOF_INT_AK) |
|
|
|
|
(1 << ATI_ONESHOT_CAP_INT_AK) |
|
|
|
|
(1 << ATI_BUSMASTER_EOL_INT_AK) |
|
|
|
|
(1 << ATI_GP_INT_AK) |
|
|
|
|
(1 << ATI_SNAPSHOT2_INT_AK) |
|
|
|
|
(1 << ATI_VBLANK_BIT2_INT_AK) |
|
|
|
|
#endif
|
|
|
|
0;
|
|
|
|
/*
|
|
|
|
uint32_t bits_EN =
|
|
|
|
(1 << ATI_CRTC_VBLANK_INT_EN) |
|
|
|
|
(1 << ATI_CRTC_VLINE_INT_EN) |
|
|
|
|
#if 1
|
|
|
|
(1 << ATI_VIDEOIN_EVEN_INT_EN) |
|
|
|
|
(1 << ATI_VIDEOIN_ODD_INT_EN) |
|
|
|
|
(1 << ATI_OVERLAY_EOF_INT_EN) |
|
|
|
|
(1 << ATI_VMC_EC_INT_EN) |
|
|
|
|
#else
|
|
|
|
(1 << ATI_SNAPSHOT_INT_EN) |
|
|
|
|
(1 << ATI_I2C_INT_EN) |
|
|
|
|
(1 << ATI_CRTC2_VBLANK_INT_EN) |
|
|
|
|
(1 << ATI_CRTC2_VLINE_INT_EN) |
|
|
|
|
(1 << ATI_CUPBUF0_INT_EN) |
|
|
|
|
(1 << ATI_CUPBUF1_INT_EN) |
|
|
|
|
(1 << ATI_OVERLAY_EOF_INT_EN) |
|
|
|
|
(1 << ATI_ONESHOT_CAP_INT_EN) |
|
|
|
|
(1 << ATI_BUSMASTER_EOL_INT_EN) |
|
|
|
|
(1 << ATI_GP_INT_EN) |
|
|
|
|
(1 << ATI_SNAPSHOT2_INT_EN) |
|
|
|
|
#endif
|
|
|
|
0;
|
|
|
|
*/
|
|
|
|
uint32_t bits_AKed = bits_AK & value; // AK bits that are to be AKed
|
|
|
|
uint32_t bits_not_AKed = bits_AK & ~value; // AK bits that are not to be AKed
|
|
|
|
|
|
|
|
new_value = value & ~bits_AKed; // clear the AKed bits
|
|
|
|
bits_read_only |= bits_not_AKed; // the not AKed bits will remain unchanged
|
|
|
|
|
|
|
|
new_value = (old_value & bits_read_only) | (new_value & ~bits_read_only);
|
|
|
|
break;
|
|
|
|
}
|
2021-02-08 01:20:32 +00:00
|
|
|
case ATI_CRTC_GEN_CNTL:
|
2024-02-27 06:48:04 +00:00
|
|
|
new_value = value;
|
|
|
|
if (bit_changed(old_value, new_value, ATI_CRTC_DISPLAY_DIS)) {
|
|
|
|
if (bit_set(new_value, ATI_CRTC_DISPLAY_DIS)) {
|
2023-04-01 17:05:34 +00:00
|
|
|
this->blank_on = true;
|
|
|
|
this->blank_display();
|
|
|
|
} else {
|
|
|
|
this->blank_on = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-27 06:48:04 +00:00
|
|
|
this->regs[reg_num] = new_value;
|
|
|
|
if (bit_changed(old_value, new_value, ATI_CRTC_ENABLE)) {
|
|
|
|
if (bit_set(new_value, ATI_CRTC_ENABLE) &&
|
|
|
|
!bit_set(new_value, ATI_CRTC_DISPLAY_DIS)) {
|
2023-04-01 17:05:34 +00:00
|
|
|
this->crtc_update();
|
|
|
|
}
|
2021-02-08 01:20:32 +00:00
|
|
|
}
|
|
|
|
break;
|
2024-01-26 07:41:34 +00:00
|
|
|
case ATI_CUR_CLR0:
|
|
|
|
case ATI_CUR_CLR1:
|
2024-03-20 14:32:28 +00:00
|
|
|
new_value = value;
|
|
|
|
this->cursor_dirty = true;
|
|
|
|
draw_fb = true;
|
2024-04-23 14:59:23 +00:00
|
|
|
WRITE_VALUE_AND_LOG(9);
|
2024-03-20 14:32:28 +00:00
|
|
|
return;
|
2024-01-26 07:41:34 +00:00
|
|
|
case ATI_CUR_OFFSET:
|
2024-03-20 14:32:28 +00:00
|
|
|
new_value = value;
|
|
|
|
if (old_value != new_value)
|
|
|
|
this->cursor_dirty = true;
|
|
|
|
draw_fb = true;
|
2024-04-23 14:59:23 +00:00
|
|
|
WRITE_VALUE_AND_LOG(9);
|
2024-03-20 14:32:28 +00:00
|
|
|
return;
|
2024-01-26 07:41:34 +00:00
|
|
|
case ATI_CUR_HORZ_VERT_OFF:
|
2024-03-20 14:32:28 +00:00
|
|
|
new_value = value;
|
|
|
|
if (
|
|
|
|
extract_bits<uint32_t>(new_value, ATI_CUR_VERT_OFF, ATI_CUR_VERT_OFF_size) !=
|
|
|
|
extract_bits<uint32_t>(old_value, ATI_CUR_VERT_OFF, ATI_CUR_VERT_OFF_size)
|
|
|
|
)
|
|
|
|
this->cursor_dirty = true;
|
|
|
|
draw_fb = true;
|
2024-04-23 14:59:23 +00:00
|
|
|
WRITE_VALUE_AND_LOG(9);
|
2024-03-20 14:32:28 +00:00
|
|
|
return;
|
|
|
|
case ATI_CUR_HORZ_VERT_POSN:
|
2024-01-26 07:41:34 +00:00
|
|
|
new_value = value;
|
|
|
|
draw_fb = true;
|
|
|
|
break;
|
2020-04-13 23:04:37 +00:00
|
|
|
case ATI_GP_IO:
|
2024-02-27 06:48:04 +00:00
|
|
|
new_value = value;
|
2024-02-27 10:56:18 +00:00
|
|
|
if (offset <= 1 && offset + size > 1) {
|
2024-02-27 06:48:04 +00:00
|
|
|
uint8_t gpio_levels = (new_value >> 8) & 0xFFU;
|
2021-11-30 00:26:32 +00:00
|
|
|
gpio_levels = ((gpio_levels & 0x30) >> 3) | (gpio_levels & 1);
|
2024-02-27 06:48:04 +00:00
|
|
|
uint8_t gpio_dirs = (new_value >> 24) & 0xFFU;
|
2023-04-01 17:05:34 +00:00
|
|
|
gpio_dirs = ((gpio_dirs & 0x30) >> 3) | (gpio_dirs & 1);
|
2021-11-30 00:26:32 +00:00
|
|
|
gpio_levels = this->disp_id->read_monitor_sense(gpio_levels, gpio_dirs);
|
2024-02-27 06:48:04 +00:00
|
|
|
insert_bits<uint32_t>(new_value,
|
2023-04-01 17:05:34 +00:00
|
|
|
((gpio_levels & 6) << 3) | (gpio_levels & 1), 8, 8);
|
2020-04-13 23:04:37 +00:00
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
break;
|
2024-03-29 20:21:58 +00:00
|
|
|
case ATI_CLOCK_CNTL: {
|
2024-02-27 12:19:30 +00:00
|
|
|
uint32_t bits_write_only =
|
|
|
|
(1 << ATI_CLOCK_STROBE);
|
|
|
|
|
|
|
|
new_value = value & ~bits_write_only; // clear the write only bits
|
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
uint8_t pll_addr = extract_bits<uint32_t>(new_value, ATI_PLL_ADDR,
|
|
|
|
ATI_PLL_ADDR_size);
|
2024-02-27 10:56:18 +00:00
|
|
|
if (offset <= 2 && offset + size > 2 && bit_set(new_value, ATI_PLL_WR_EN)) {
|
2024-03-29 20:21:58 +00:00
|
|
|
uint8_t pll_data = extract_bits<uint32_t>(new_value, ATI_PLL_DATA,
|
|
|
|
ATI_PLL_DATA_size);
|
2021-02-05 01:01:31 +00:00
|
|
|
this->plls[pll_addr] = pll_data;
|
2023-04-01 17:05:34 +00:00
|
|
|
LOG_F(9, "%s: PLL #%d set to 0x%02X", this->name.c_str(), pll_addr, pll_data);
|
2021-02-05 00:10:56 +00:00
|
|
|
}
|
2024-02-27 12:19:30 +00:00
|
|
|
else {
|
2024-03-29 20:21:58 +00:00
|
|
|
insert_bits<uint32_t>(new_value, this->plls[pll_addr], ATI_PLL_DATA,
|
|
|
|
ATI_PLL_DATA_size);
|
2024-02-27 12:19:30 +00:00
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
break;
|
2024-02-27 12:19:30 +00:00
|
|
|
}
|
2020-06-10 23:30:10 +00:00
|
|
|
case ATI_DAC_REGS:
|
2024-02-27 06:48:04 +00:00
|
|
|
new_value = old_value; // no change
|
|
|
|
switch (reg_offset) {
|
2020-06-10 23:30:10 +00:00
|
|
|
case ATI_DAC_W_INDEX:
|
2023-04-01 17:05:34 +00:00
|
|
|
this->dac_wr_index = value & 0xFFU;
|
|
|
|
this->comp_index = 0;
|
|
|
|
break;
|
|
|
|
case ATI_DAC_MASK:
|
|
|
|
this->dac_mask = (value >> 16) & 0xFFU;
|
|
|
|
break;
|
2020-06-10 23:30:10 +00:00
|
|
|
case ATI_DAC_R_INDEX:
|
2023-04-01 17:05:34 +00:00
|
|
|
this->dac_rd_index = (value >> 24) & 0xFFU;
|
2020-06-10 23:30:10 +00:00
|
|
|
this->comp_index = 0;
|
|
|
|
break;
|
|
|
|
case ATI_DAC_DATA:
|
2023-04-01 10:08:25 +00:00
|
|
|
this->color_buf[this->comp_index] = (value >> 8) & this->dac_mask;
|
|
|
|
if (++this->comp_index >= 3) {
|
|
|
|
this->set_palette_color(this->dac_wr_index, color_buf[0],
|
|
|
|
color_buf[1], color_buf[2], 0xFF);
|
|
|
|
this->dac_wr_index++; // auto-increment color index
|
|
|
|
this->comp_index = 0; // reset color component index
|
2024-04-20 07:35:59 +00:00
|
|
|
draw_fb = true;
|
2020-06-10 23:30:10 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
break;
|
2021-09-11 19:02:46 +00:00
|
|
|
case ATI_GEN_TEST_CNTL:
|
2024-02-27 06:48:04 +00:00
|
|
|
new_value = value;
|
|
|
|
if (bit_changed(old_value, new_value, ATI_GEN_CUR_ENABLE)) {
|
|
|
|
if (bit_set(new_value, ATI_GEN_CUR_ENABLE))
|
2024-03-20 14:32:28 +00:00
|
|
|
this->cursor_on = true;
|
2023-04-01 23:56:24 +00:00
|
|
|
else
|
|
|
|
this->cursor_on = false;
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = true;
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
if (bit_changed(old_value, new_value, ATI_GEN_GUI_RESETB)) {
|
|
|
|
if (!bit_set(new_value, ATI_GEN_GUI_RESETB))
|
2023-04-01 17:05:34 +00:00
|
|
|
LOG_F(9, "%s: reset GUI engine", this->name.c_str());
|
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
if (bit_changed(old_value, new_value, ATI_GEN_SOFT_RESET)) {
|
|
|
|
if (bit_set(new_value, ATI_GEN_SOFT_RESET))
|
2023-04-01 17:05:34 +00:00
|
|
|
LOG_F(9, "%s: reset memory controller", this->name.c_str());
|
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
if (new_value & 0xFFFFFC00) {
|
2023-04-01 17:05:34 +00:00
|
|
|
LOG_F(WARNING, "%s: unhandled GEN_TEST_CNTL state=0x%X",
|
2024-02-27 06:48:04 +00:00
|
|
|
this->name.c_str(), new_value);
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
2021-09-11 19:02:46 +00:00
|
|
|
break;
|
2023-04-01 17:05:34 +00:00
|
|
|
case ATI_CONFIG_CHIP_ID:
|
2024-02-27 06:48:04 +00:00
|
|
|
new_value = old_value; // prevent writes to this read-only register
|
|
|
|
break;
|
2024-02-27 11:52:36 +00:00
|
|
|
case ATI_CONFIG_STAT0:
|
|
|
|
{
|
|
|
|
uint32_t bits_read_only =
|
|
|
|
#if 1
|
|
|
|
#else
|
|
|
|
(1 << ATI_MACROVISION_ENABLE) |
|
|
|
|
(1 << ATI_ARITHMOS_ENABLE) |
|
|
|
|
#endif
|
|
|
|
0;
|
|
|
|
|
|
|
|
new_value = value;
|
|
|
|
new_value = (old_value & bits_read_only) | (new_value & ~bits_read_only);
|
|
|
|
break;
|
|
|
|
}
|
2024-02-27 06:48:04 +00:00
|
|
|
default:
|
|
|
|
new_value = value;
|
|
|
|
break;
|
2020-04-13 23:04:37 +00:00
|
|
|
}
|
2021-09-11 19:02:46 +00:00
|
|
|
|
2024-02-26 10:35:38 +00:00
|
|
|
WRITE_VALUE_AND_LOG(9);
|
2020-03-31 19:19:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-13 19:58:31 +00:00
|
|
|
bool ATIRage::io_access_allowed(uint32_t offset) {
|
2024-02-27 10:43:45 +00:00
|
|
|
if (offset >= this->aperture_base[1] && offset < (this->aperture_base[1] + 0x100)) {
|
2023-02-05 08:47:57 +00:00
|
|
|
if (this->command & 1) {
|
|
|
|
return true;
|
|
|
|
}
|
2020-04-01 01:24:39 +00:00
|
|
|
LOG_F(WARNING, "ATI I/O space disabled in the command reg");
|
|
|
|
}
|
2023-02-05 08:47:57 +00:00
|
|
|
return false;
|
2020-03-31 19:19:10 +00:00
|
|
|
}
|
|
|
|
|
2020-05-12 18:55:45 +00:00
|
|
|
bool ATIRage::pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) {
|
2022-03-13 19:58:31 +00:00
|
|
|
if (!this->io_access_allowed(offset)) {
|
2020-03-31 19:19:10 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-04-01 01:24:39 +00:00
|
|
|
|
2024-02-27 10:43:45 +00:00
|
|
|
*res = BYTESWAP_SIZED(this->read_reg(offset - this->aperture_base[1], size), size);
|
2020-04-01 01:24:39 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-05-12 18:55:45 +00:00
|
|
|
bool ATIRage::pci_io_write(uint32_t offset, uint32_t value, uint32_t size) {
|
2022-03-13 19:58:31 +00:00
|
|
|
if (!this->io_access_allowed(offset)) {
|
2020-03-31 19:19:10 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-02-27 10:43:45 +00:00
|
|
|
this->write_reg(offset - this->aperture_base[1], BYTESWAP_SIZED(value, size), size);
|
2020-03-31 19:19:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-22 10:16:31 +00:00
|
|
|
uint32_t ATIRage::read(uint32_t rgn_start, uint32_t offset, int size)
|
2020-03-31 16:25:58 +00:00
|
|
|
{
|
2024-02-27 10:43:45 +00:00
|
|
|
if (rgn_start == this->aperture_base[0] && offset < this->aperture_size[0]) {
|
|
|
|
if (offset < this->vram_size) { // little-endian VRAM region
|
|
|
|
return read_mem(&this->vram_ptr[offset], size);
|
|
|
|
}
|
|
|
|
if (offset >= BE_FB_OFFSET) { // big-endian VRAM region
|
|
|
|
return read_mem(&this->vram_ptr[offset - BE_FB_OFFSET], size);
|
|
|
|
}
|
|
|
|
//if (!bit_set(this->regs[ATI_BUS_CNTL], ATI_BUS_APER_REG_DIS)) {
|
|
|
|
if (offset >= MM_REGS_0_OFF) { // memory-mapped registers, block 0
|
|
|
|
return BYTESWAP_SIZED(this->read_reg(offset & 0x3FF, size), size);
|
|
|
|
}
|
|
|
|
if (offset >= MM_REGS_1_OFF
|
|
|
|
//&& bit_set(this->regs[ATI_BUS_CNTL], ATI_BUS_EXT_REG_EN)
|
|
|
|
) { // memory-mapped registers, block 1
|
|
|
|
return BYTESWAP_SIZED(this->read_reg((offset & 0x3FF) + 0x400, size), size);
|
|
|
|
}
|
|
|
|
//}
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(WARNING, "%s: read unmapped aperture[0] region %08x.%c",
|
|
|
|
this->name.c_str(), offset, SIZE_ARG(size));
|
2024-02-27 10:43:45 +00:00
|
|
|
return 0;
|
2020-05-28 21:47:20 +00:00
|
|
|
}
|
2024-02-27 10:43:45 +00:00
|
|
|
|
|
|
|
if (rgn_start == this->aperture_base[2] && offset < this->aperture_size[2]) {
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(WARNING, "%s: read unmapped aperture[2] region %08x.%c",
|
|
|
|
this->name.c_str(), offset, SIZE_ARG(size));
|
2024-02-27 10:43:45 +00:00
|
|
|
return 0;
|
2020-05-28 20:36:55 +00:00
|
|
|
}
|
|
|
|
|
2024-04-20 07:14:54 +00:00
|
|
|
// memory mapped expansion ROM region
|
|
|
|
if (rgn_start == this->exp_rom_addr) {
|
|
|
|
if (offset < this->exp_rom_size)
|
|
|
|
return read_mem(&this->exp_rom_data[offset], size);
|
|
|
|
LOG_F(WARNING, "%s: read unmapped ROM region %08x.%c",
|
|
|
|
this->name.c_str(), offset, SIZE_ARG(size));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(WARNING, "%s: read unmapped aperture region %08x.%c",
|
|
|
|
this->name.c_str(), offset, SIZE_ARG(size));
|
2020-03-31 16:25:58 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-08-22 10:16:31 +00:00
|
|
|
void ATIRage::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size)
|
2020-03-31 16:25:58 +00:00
|
|
|
{
|
2024-02-27 10:43:45 +00:00
|
|
|
if (rgn_start == this->aperture_base[0] && offset < this->aperture_size[0]) {
|
|
|
|
if (offset < this->vram_size) { // little-endian VRAM region
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = true;
|
2024-02-27 10:43:45 +00:00
|
|
|
return write_mem(&this->vram_ptr[offset], value, size);
|
|
|
|
}
|
|
|
|
if (offset >= BE_FB_OFFSET) { // big-endian VRAM region
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = true;
|
2024-02-27 10:43:45 +00:00
|
|
|
return write_mem(&this->vram_ptr[offset & (BE_FB_OFFSET - 1)], value, size);
|
|
|
|
}
|
|
|
|
//if (!bit_set(this->regs[ATI_BUS_CNTL], ATI_BUS_APER_REG_DIS)) {
|
|
|
|
if (offset >= MM_REGS_0_OFF) { // memory-mapped registers, block 0
|
|
|
|
return this->write_reg(offset & 0x3FF, BYTESWAP_SIZED(value, size), size);
|
|
|
|
}
|
|
|
|
if (offset >= MM_REGS_1_OFF
|
|
|
|
//&& bit_set(this->regs[ATI_BUS_CNTL], ATI_BUS_EXT_REG_EN)
|
|
|
|
) { // memory-mapped registers, block 1
|
2024-03-29 20:21:58 +00:00
|
|
|
return this->write_reg((offset & 0x3FF) + 0x400,
|
|
|
|
BYTESWAP_SIZED(value, size), size);
|
2024-02-27 10:43:45 +00:00
|
|
|
}
|
|
|
|
//}
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(WARNING, "%s: write unmapped aperture[0] region %08x.%c = %0*x",
|
|
|
|
this->name.c_str(), offset, SIZE_ARG(size), size * 2, value);
|
2024-02-27 10:43:45 +00:00
|
|
|
return;
|
2020-05-28 21:47:20 +00:00
|
|
|
}
|
2024-02-27 10:43:45 +00:00
|
|
|
|
|
|
|
if (rgn_start == this->aperture_base[2] && offset < this->aperture_size[2]) {
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(WARNING, "%s: write unmapped aperture[2] region %08x.%c = %0*x",
|
|
|
|
this->name.c_str(), offset, SIZE_ARG(size), size * 2, value);
|
2024-02-27 10:43:45 +00:00
|
|
|
return;
|
2020-05-28 20:36:55 +00:00
|
|
|
}
|
2024-02-27 10:43:45 +00:00
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
LOG_F(WARNING, "%s: write unmapped aperture region %08x.%c = %0*x",
|
|
|
|
this->name.c_str(), offset, SIZE_ARG(size), size * 2, value);
|
2020-03-31 16:25:58 +00:00
|
|
|
}
|
2021-02-08 01:20:32 +00:00
|
|
|
|
|
|
|
float ATIRage::calc_pll_freq(int scale, int fb_div) {
|
|
|
|
return (ATI_XTAL * scale * fb_div) / this->plls[PLL_REF_DIV];
|
|
|
|
}
|
|
|
|
|
|
|
|
void ATIRage::verbose_pixel_format(int crtc_index) {
|
|
|
|
if (crtc_index) {
|
|
|
|
LOG_F(ERROR, "CRTC2 not supported yet");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
uint32_t pix_fmt = extract_bits<uint32_t>(this->regs[ATI_CRTC_GEN_CNTL],
|
|
|
|
ATI_CRTC_PIX_WIDTH, ATI_CRTC_PIX_WIDTH_size);
|
2023-04-01 17:05:34 +00:00
|
|
|
|
2021-02-08 01:20:32 +00:00
|
|
|
const char* what = "Pixel format:";
|
|
|
|
|
2024-02-27 04:06:02 +00:00
|
|
|
switch (pix_fmt) {
|
2021-02-08 01:20:32 +00:00
|
|
|
case 1:
|
|
|
|
LOG_F(INFO, "%s 4 bpp with DAC palette", what);
|
|
|
|
break;
|
|
|
|
case 2:
|
2021-02-08 21:25:36 +00:00
|
|
|
// check the undocumented DAC_DIRECT bit
|
2024-02-27 00:29:32 +00:00
|
|
|
if (bit_set(this->regs[ATI_DAC_CNTL], ATI_DAC_DIRECT)) {
|
2024-02-27 00:29:49 +00:00
|
|
|
LOG_F(INFO, "%s 8 bpp direct color (RGB332)", what);
|
2021-02-08 21:25:36 +00:00
|
|
|
} else {
|
|
|
|
LOG_F(INFO, "%s 8 bpp with DAC palette", what);
|
|
|
|
}
|
2021-02-08 01:20:32 +00:00
|
|
|
break;
|
|
|
|
case 3:
|
2021-02-08 21:25:36 +00:00
|
|
|
LOG_F(INFO, "%s 15 bpp direct color (RGB555)", what);
|
2021-02-08 01:20:32 +00:00
|
|
|
break;
|
|
|
|
case 4:
|
2021-02-08 21:25:36 +00:00
|
|
|
LOG_F(INFO, "%s 16 bpp direct color (RGB565)", what);
|
2021-02-08 01:20:32 +00:00
|
|
|
break;
|
|
|
|
case 5:
|
2021-02-08 21:25:36 +00:00
|
|
|
LOG_F(INFO, "%s 24 bpp direct color (RGB888)", what);
|
2021-02-08 01:20:32 +00:00
|
|
|
break;
|
|
|
|
case 6:
|
2021-02-08 21:25:36 +00:00
|
|
|
LOG_F(INFO, "%s 32 bpp direct color (ARGB8888)", what);
|
2021-02-08 01:20:32 +00:00
|
|
|
break;
|
|
|
|
default:
|
2024-02-27 04:06:02 +00:00
|
|
|
LOG_F(ERROR, "%s: CRTC pixel format %d not supported", this->name.c_str(), pix_fmt);
|
2021-02-08 01:20:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
void ATIRage::crtc_update() {
|
|
|
|
uint32_t new_width, new_height, new_htotal, new_vtotal;
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// check for unsupported modes and fail early
|
2024-02-27 00:29:32 +00:00
|
|
|
if (!bit_set(this->regs[ATI_CRTC_GEN_CNTL], ATI_CRTC_EXT_DISP_EN))
|
2023-04-01 17:05:34 +00:00
|
|
|
ABORT_F("%s: VGA not supported", this->name.c_str());
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
if ((this->plls[PLL_VCLK_CNTL] & 3) != 3)
|
|
|
|
ABORT_F("%s: VLCK source != VPLL", this->name.c_str());
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
bool need_recalc = false;
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
new_width = (extract_bits<uint32_t>(this->regs[ATI_CRTC_H_TOTAL_DISP],
|
|
|
|
ATI_CRTC_H_DISP,
|
|
|
|
ATI_CRTC_H_DISP_size) + 1) * 8;
|
|
|
|
new_height = extract_bits<uint32_t>(this->regs[ATI_CRTC_V_TOTAL_DISP],
|
|
|
|
ATI_CRTC_V_DISP, ATI_CRTC_V_DISP_size) + 1;
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
if (new_width != this->active_width || new_height != this->active_height) {
|
|
|
|
this->create_display_window(new_width, new_height);
|
|
|
|
need_recalc = true;
|
|
|
|
}
|
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
new_htotal = (extract_bits<uint32_t>(this->regs[ATI_CRTC_H_TOTAL_DISP],
|
|
|
|
ATI_CRTC_H_TOTAL,
|
|
|
|
ATI_CRTC_H_TOTAL_size) + 1) * 8;
|
|
|
|
new_vtotal = extract_bits<uint32_t>(this->regs[ATI_CRTC_V_TOTAL_DISP],
|
|
|
|
ATI_CRTC_V_TOTAL, ATI_CRTC_V_TOTAL_size) + 1;
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
if (new_htotal != this->hori_total || new_vtotal != this->vert_total) {
|
|
|
|
this->hori_total = new_htotal;
|
|
|
|
this->vert_total = new_vtotal;
|
|
|
|
need_recalc = true;
|
2024-02-29 00:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t new_vert_blank = new_vtotal - new_height;
|
|
|
|
if (new_vert_blank != this->vert_blank) {
|
|
|
|
this->vert_blank = vert_blank;
|
|
|
|
need_recalc = true;
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
|
|
|
|
2024-03-29 20:21:58 +00:00
|
|
|
int new_pixel_format = extract_bits<uint32_t>(this->regs[ATI_CRTC_GEN_CNTL],
|
|
|
|
ATI_CRTC_PIX_WIDTH,
|
|
|
|
ATI_CRTC_PIX_WIDTH_size);
|
2024-02-27 13:21:25 +00:00
|
|
|
if (new_pixel_format != this->pixel_format) {
|
|
|
|
this->pixel_format = new_pixel_format;
|
|
|
|
need_recalc = true;
|
|
|
|
}
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2024-02-26 10:35:38 +00:00
|
|
|
static uint8_t bits_per_pixel[8] = {0, 4, 8, 16, 16, 24, 32, 0};
|
|
|
|
|
|
|
|
int new_fb_pitch = extract_bits<uint32_t>(this->regs[ATI_CRTC_OFF_PITCH],
|
|
|
|
ATI_CRTC_PITCH, ATI_CRTC_PITCH_size) * bits_per_pixel[this->pixel_format];
|
|
|
|
if (new_fb_pitch != this->fb_pitch) {
|
|
|
|
this->fb_pitch = new_fb_pitch;
|
|
|
|
need_recalc = true;
|
|
|
|
}
|
|
|
|
uint8_t* new_fb_ptr = &this->vram_ptr[extract_bits<uint32_t>(this->regs[ATI_CRTC_OFF_PITCH],
|
|
|
|
ATI_CRTC_OFFSET, ATI_CRTC_OFFSET_size) * 8];
|
|
|
|
if (new_fb_ptr != this->fb_ptr) {
|
|
|
|
this->fb_ptr = new_fb_ptr;
|
|
|
|
need_recalc = true;
|
|
|
|
}
|
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// look up which VPLL ouput is requested
|
2024-03-29 20:21:58 +00:00
|
|
|
int clock_sel = extract_bits<uint32_t>(this->regs[ATI_CLOCK_CNTL], ATI_CLOCK_SEL,
|
|
|
|
ATI_CLOCK_SEL_size);
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// calculate VPLL output frequency
|
|
|
|
float vpll_freq = calc_pll_freq(2, this->plls[VCLK0_FB_DIV + clock_sel]);
|
2021-02-08 01:20:32 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// calculate post divider's index
|
|
|
|
// NOTE: post divider's index has been extended by an additional
|
|
|
|
// bit in Rage Pro. This bit is resided in PLL_EXT_CNTL register.
|
|
|
|
int post_div_idx = ((this->plls[PLL_EXT_CNTL] >> (clock_sel + 2)) & 4) |
|
|
|
|
((this->plls[VCLK_POST_DIV] >> (clock_sel * 2)) & 3);
|
2022-03-28 23:54:36 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// pixel clock = source_freq / post_div
|
2024-02-27 13:21:25 +00:00
|
|
|
float new_pixel_clock = vpll_freq / mach64_post_div[post_div_idx];
|
|
|
|
if (new_pixel_clock != this->pixel_clock) {
|
|
|
|
this->pixel_clock = new_pixel_clock;
|
|
|
|
need_recalc = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!need_recalc)
|
|
|
|
return;
|
2022-03-28 23:54:36 +00:00
|
|
|
|
2024-04-20 07:35:59 +00:00
|
|
|
this->draw_fb = true;
|
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
// calculate display refresh rate
|
|
|
|
this->refresh_rate = pixel_clock / this->hori_total / this->vert_total;
|
2022-03-28 23:54:36 +00:00
|
|
|
|
2024-02-27 13:21:25 +00:00
|
|
|
if (this->refresh_rate < 24 || this->refresh_rate > 120) {
|
|
|
|
LOG_F(ERROR, "%s: Refresh rate is weird. Will try 60 Hz", this->name.c_str());
|
|
|
|
this->refresh_rate = 60;
|
|
|
|
this->pixel_clock = this->refresh_rate * this->hori_total / this->vert_total;
|
|
|
|
}
|
2023-04-01 17:05:34 +00:00
|
|
|
|
2024-02-27 13:21:25 +00:00
|
|
|
// set up frame buffer converter
|
|
|
|
switch (this->pixel_format) {
|
2024-02-29 00:46:54 +00:00
|
|
|
case 1:
|
2023-12-07 07:47:51 +00:00
|
|
|
this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) {
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = false;
|
2023-12-07 07:47:51 +00:00
|
|
|
this->convert_frame_4bpp_indexed(dst_buf, dst_pitch);
|
|
|
|
};
|
|
|
|
break;
|
2023-04-01 17:05:34 +00:00
|
|
|
case 2:
|
2024-02-27 00:29:32 +00:00
|
|
|
if (bit_set(this->regs[ATI_DAC_CNTL], ATI_DAC_DIRECT)) {
|
2023-12-07 07:47:51 +00:00
|
|
|
this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) {
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = false;
|
2023-12-07 07:47:51 +00:00
|
|
|
this->convert_frame_8bpp(dst_buf, dst_pitch);
|
|
|
|
};
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
2023-12-07 07:47:51 +00:00
|
|
|
else {
|
|
|
|
this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) {
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = false;
|
2023-12-07 07:47:51 +00:00
|
|
|
this->convert_frame_8bpp_indexed(dst_buf, dst_pitch);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) {
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = false;
|
2024-02-26 10:36:06 +00:00
|
|
|
this->convert_frame_15bpp_BE(dst_buf, dst_pitch);
|
2023-12-07 07:47:51 +00:00
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) {
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = false;
|
2023-12-07 07:47:51 +00:00
|
|
|
this->convert_frame_16bpp(dst_buf, dst_pitch);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) {
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = false;
|
2023-12-07 07:47:51 +00:00
|
|
|
this->convert_frame_24bpp(dst_buf, dst_pitch);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 6:
|
2021-12-07 21:47:25 +00:00
|
|
|
this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) {
|
2024-01-26 07:41:34 +00:00
|
|
|
draw_fb = false;
|
2024-02-26 10:36:06 +00:00
|
|
|
this->convert_frame_32bpp_BE(dst_buf, dst_pitch);
|
2021-12-07 21:47:25 +00:00
|
|
|
};
|
2023-04-01 17:05:34 +00:00
|
|
|
break;
|
|
|
|
default:
|
2024-02-27 13:21:25 +00:00
|
|
|
LOG_F(ERROR, "%s: unsupported pixel format %d", this->name.c_str(), this->pixel_format);
|
2023-04-01 17:05:34 +00:00
|
|
|
}
|
2021-12-07 21:47:25 +00:00
|
|
|
|
2023-04-01 17:05:34 +00:00
|
|
|
LOG_F(INFO, "%s: primary CRT controller enabled:", this->name.c_str());
|
|
|
|
LOG_F(INFO, "Video mode: %s",
|
2024-02-27 00:29:32 +00:00
|
|
|
bit_set(this->regs[ATI_CRTC_GEN_CNTL], ATI_CRTC_EXT_DISP_EN) ? "extended" : "VGA");
|
2023-04-01 17:05:34 +00:00
|
|
|
LOG_F(INFO, "Video width: %d px", this->active_width);
|
|
|
|
LOG_F(INFO, "Video height: %d px", this->active_height);
|
|
|
|
verbose_pixel_format(0);
|
|
|
|
LOG_F(INFO, "VPLL frequency: %f MHz", vpll_freq * 1e-6);
|
|
|
|
LOG_F(INFO, "Pixel (dot) clock: %f MHz", this->pixel_clock * 1e-6);
|
|
|
|
LOG_F(INFO, "Refresh rate: %f Hz", this->refresh_rate);
|
|
|
|
|
2023-07-31 01:46:16 +00:00
|
|
|
this->stop_refresh_task();
|
|
|
|
this->start_refresh_task();
|
2023-04-01 17:05:34 +00:00
|
|
|
|
2021-02-08 01:20:32 +00:00
|
|
|
this->crtc_on = true;
|
|
|
|
}
|
2021-09-11 19:02:46 +00:00
|
|
|
|
2024-03-20 12:12:32 +00:00
|
|
|
void ATIRage::draw_hw_cursor(uint8_t* dst_row, int dst_pitch) {
|
|
|
|
int vert_offset = extract_bits<uint32_t>(
|
|
|
|
this->regs[ATI_CUR_HORZ_VERT_OFF], ATI_CUR_VERT_OFF, ATI_CUR_VERT_OFF_size);
|
2021-09-11 19:02:46 +00:00
|
|
|
int cur_height = 64 - vert_offset;
|
|
|
|
|
2023-04-10 23:23:40 +00:00
|
|
|
uint32_t color0 = this->regs[ATI_CUR_CLR0] | 0x000000FFUL;
|
|
|
|
uint32_t color1 = this->regs[ATI_CUR_CLR1] | 0x000000FFUL;
|
2021-09-11 19:02:46 +00:00
|
|
|
|
2024-03-20 12:12:32 +00:00
|
|
|
uint64_t* src_row = (uint64_t*)&this->vram_ptr[this->regs[ATI_CUR_OFFSET] * 8];
|
|
|
|
dst_pitch -= 64 * 4;
|
2021-09-11 19:02:46 +00:00
|
|
|
|
2024-03-20 12:12:32 +00:00
|
|
|
for (int h = cur_height; h > 0; h--) {
|
|
|
|
for (int x = 2; x > 0; x--) {
|
|
|
|
uint64_t px = *src_row++;
|
|
|
|
for (int p = 32; p > 0; p--, px >>= 2, dst_row += 4) {
|
|
|
|
switch (px & 3) {
|
|
|
|
case 0: // cursor color 0
|
2021-09-11 19:02:46 +00:00
|
|
|
WRITE_DWORD_BE_A(dst_row, color0);
|
|
|
|
break;
|
2024-03-20 12:12:32 +00:00
|
|
|
case 1: // cursor color 1
|
2021-09-11 19:02:46 +00:00
|
|
|
WRITE_DWORD_BE_A(dst_row, color1);
|
|
|
|
break;
|
2024-03-20 12:12:32 +00:00
|
|
|
case 2: // transparent
|
2023-04-01 23:56:24 +00:00
|
|
|
WRITE_DWORD_BE_A(dst_row, 0);
|
2021-09-11 19:02:46 +00:00
|
|
|
break;
|
2024-03-20 12:12:32 +00:00
|
|
|
case 3: // 1's complement of display pixel
|
2024-02-26 20:57:14 +00:00
|
|
|
WRITE_DWORD_BE_A(dst_row, 0x0000007F);
|
2021-09-11 19:02:46 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-20 12:12:32 +00:00
|
|
|
dst_row += dst_pitch;
|
2021-09-11 19:02:46 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-18 09:48:23 +00:00
|
|
|
|
2023-04-01 23:56:24 +00:00
|
|
|
void ATIRage::get_cursor_position(int& x, int& y) {
|
2024-03-20 11:54:03 +00:00
|
|
|
x = extract_bits<uint32_t>(this->regs[ATI_CUR_HORZ_VERT_POSN], ATI_CUR_HORZ_POSN, ATI_CUR_HORZ_POSN_size) -
|
|
|
|
extract_bits<uint32_t>(this->regs[ATI_CUR_HORZ_VERT_OFF ], ATI_CUR_HORZ_OFF , ATI_CUR_HORZ_OFF_size );
|
2024-02-27 00:29:32 +00:00
|
|
|
y = extract_bits<uint32_t>(this->regs[ATI_CUR_HORZ_VERT_POSN], ATI_CUR_VERT_POSN, ATI_CUR_VERT_POSN_size);
|
2023-04-01 23:56:24 +00:00
|
|
|
}
|
|
|
|
|
2024-02-27 13:22:56 +00:00
|
|
|
int ATIRage::device_postinit()
|
|
|
|
{
|
|
|
|
this->vbl_cb = [this](uint8_t irq_line_state) {
|
|
|
|
insert_bits<uint32_t>(this->regs[ATI_CRTC_INT_CNTL], irq_line_state, ATI_CRTC_VBLANK, 1);
|
|
|
|
if (irq_line_state) {
|
|
|
|
set_bit(this->regs[ATI_CRTC_INT_CNTL], ATI_CRTC_VBLANK_INT);
|
|
|
|
set_bit(this->regs[ATI_CRTC_INT_CNTL], ATI_CRTC_VLINE_INT);
|
|
|
|
#if 1
|
|
|
|
#else
|
|
|
|
set_bit(this->regs[ATI_CRTC_GEN_CNTL], ATI_CRTC_VSYNC_INT);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool do_interrupt =
|
|
|
|
bit_set(this->regs[ATI_CRTC_INT_CNTL], ATI_CRTC_VBLANK_INT_EN) ||
|
|
|
|
bit_set(this->regs[ATI_CRTC_INT_CNTL], ATI_CRTC_VLINE_INT_EN) ||
|
|
|
|
#if 1
|
|
|
|
#else
|
|
|
|
bit_set(this->regs[ATI_CRTC_GEN_CNTL], ATI_CRTC_VSYNC_INT_EN) ||
|
|
|
|
#endif
|
|
|
|
0;
|
|
|
|
|
|
|
|
if (do_interrupt) {
|
|
|
|
this->pci_interrupt(irq_line_state);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-18 09:48:23 +00:00
|
|
|
static const PropMap AtiRage_Properties = {
|
|
|
|
{"gfxmem_size",
|
|
|
|
new IntProperty( 2, vector<uint32_t>({2, 4, 6}))},
|
|
|
|
{"mon_id",
|
|
|
|
new StrProperty("")},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const DeviceDescription AtiRageGT_Descriptor = {
|
|
|
|
ATIRage::create_gt, {}, AtiRage_Properties
|
|
|
|
};
|
|
|
|
|
|
|
|
static const DeviceDescription AtiRagePro_Descriptor = {
|
|
|
|
ATIRage::create_pro, {}, AtiRage_Properties
|
|
|
|
};
|
|
|
|
|
|
|
|
REGISTER_DEVICE(AtiRageGT, AtiRageGT_Descriptor);
|
|
|
|
REGISTER_DEVICE(AtiRagePro, AtiRagePro_Descriptor);
|