From db5eaabe57ea75349f4e7d1107f198f2f05e8852 Mon Sep 17 00:00:00 2001 From: spicyjpeg Date: Sat, 1 Jun 2024 01:49:37 +0200 Subject: [PATCH] Add hardware test suite, fix up IDE driver again --- CMakeLists.txt | 1 + assets/app.strings.json | 96 ++++++++ src/common/file9660.cpp | 8 +- src/common/filefat.cpp | 4 +- src/common/ide.cpp | 272 +++++++++++++---------- src/common/ide.hpp | 39 ++-- src/common/spu.cpp | 6 +- src/common/spu.hpp | 14 +- src/launcher/main.cpp | 4 +- src/main/app/app.cpp | 1 + src/main/app/app.hpp | 95 +++++--- src/main/app/main.cpp | 8 + src/main/app/main.hpp | 1 + src/main/app/misc.cpp | 6 +- src/main/app/miscworkers.cpp | 1 - src/main/app/tests.cpp | 416 +++++++++++++++++++++++++++++++++++ src/main/app/tests.hpp | 72 ++++++ 17 files changed, 856 insertions(+), 188 deletions(-) create mode 100644 src/main/app/tests.cpp create mode 100644 src/main/app/tests.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 457996f..c1d2f56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ addExecutable( src/main/app/modals.cpp src/main/app/romactions.cpp src/main/app/romworkers.cpp + src/main/app/tests.cpp src/vendor/ff.c src/vendor/ffunicode.c src/vendor/miniz.c diff --git a/assets/app.strings.json b/assets/app.strings.json index a180bc9..66a6892 100644 --- a/assets/app.strings.json +++ b/assets/app.strings.json @@ -111,6 +111,20 @@ } }, + "AudioTestScreen": { + "title": "{RIGHT_ARROW} Audio output test", + "prompt": "Note that the speaker amplifier and analog audio passthrough are disabled by default and will be disabled again once this screen is closed.", + "itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back", + + "playLeft": "Play sound on left channel", + "playRight": "Play sound on right channel", + "playBoth": "Play sound on both channels", + "enableAmp": "Turn on built-in speaker amplifier", + "disableAmp": "Turn off built-in speaker amplifier", + "enableCDDA": "Unmute CD-DA/MP3 analog audio passthrough", + "disableCDDA": "Mute CD-DA/MP3 analog audio passthrough" + }, + "AutobootScreen": { "title": "Note", "body": "A valid boot executable has been found and will be launched shortly. You may disable automatic booting by turning off DIP switch 1 or creating a file named noboot.txt in the root of the filesystem.\n\nFile: %s", @@ -262,6 +276,16 @@ "description": "All checksums are computed using the standard CRC32 algorithm and parameters (polynomial 04C11DB7, initial value FFFFFFFF, input and output reflected, output bits negated)." }, + "ColorIntensityScreen": { + "title": "{RIGHT_ARROW} Monitor color intensity test", + "prompt": "{RIGHT_ARROW} Press {START_BUTTON} to go back.", + + "white": "White", + "red": "Red", + "green": "Green", + "blue": "Blue" + }, + "ConfirmScreen": { "title": "Confirm operation", "no": "No, go back", @@ -288,6 +312,11 @@ "noFilesError": "No files or directories have been found in the root of the selected drive's filesystem." }, + "GeometryScreen": { + "title": "{RIGHT_ARROW} Monitor geometry test", + "prompt": "{RIGHT_ARROW} Press {START_BUTTON} to go back." + }, + "HexdumpScreen": { "title": "{RIGHT_ARROW} Cartridge dump", "prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back." @@ -328,6 +357,47 @@ } }, + "JAMMATestScreen": { + "title": "{RIGHT_ARROW} JAMMA input test", + "prompt": "{RIGHT_ARROW} Press and hold {START_BUTTON} to go back.", + + "noInputs": "No button is currently held down. If a button does not appear here when pressed, make sure the JAMMA harness is wired up correctly and the button is not damaged.\n", + "inputs": "The following buttons are currently held down:\n", + "inputsNote": "\nNote that in DDR cabinets each player's up and right inputs may appear to be stuck due to the presence of the stage I/O boards, which use the joystick inputs as a communication bus.\n", + + "p1": { + "left": " Player 1 joystick left\t\tJAMMA pin 20\n", + "right": " Player 1 joystick right\t\tJAMMA pin 21\n", + "up": " Player 1 joystick up\t\tJAMMA pin 18\n", + "down": " Player 1 joystick down\t\tJAMMA pin 19\n", + "button1": " Player 1 button 1\t\tJAMMA pin 22\n", + "button2": " Player 1 button 2\t\tJAMMA pin 23\n", + "button3": " Player 1 button 3\t\tJAMMA pin 24\n", + "button4": " Player 1 button 4\t\tJAMMA pin 25\n", + "button5": " Player 1 button 5\t\tJAMMA pin 26\n", + "button6": " Player 1 button 6\n", + "start": " Player 1 start button ({START_BUTTON})\tJAMMA pin 17\n" + }, + "p2": { + "left": " Player 2 joystick left\t\tJAMMA pin X\n", + "right": " Player 2 joystick right\t\tJAMMA pin Y\n", + "up": " Player 2 joystick up\t\tJAMMA pin V\n", + "down": " Player 2 joystick down\t\tJAMMA pin W\n", + "button1": " Player 2 button 1\t\tJAMMA pin Z\n", + "button2": " Player 2 button 2\t\tJAMMA pin a\n", + "button3": " Player 2 button 3\t\tJAMMA pin b\n", + "button4": " Player 2 button 4\t\tJAMMA pin c\n", + "button5": " Player 2 button 5\t\tJAMMA pin d\n", + "button6": " Player 2 button 6\n", + "start": " Player 2 start button ({START_BUTTON})\tJAMMA pin U\n" + }, + + "coin1": " Coin switch 1\t\t\tJAMMA pin 16\n", + "coin2": " Coin switch 2\t\t\tJAMMA pin T\n", + "test": " Test button\t\t\tJAMMA pin 15\n", + "service": " Service button\t\t\tJAMMA pin R\n" + }, + "KeyEntryScreen": { "title": "Enter unlocking key", "body": "Enter the 8-byte key this cartridge was last locked with.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move the cursor, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted digit.", @@ -360,6 +430,10 @@ "name": "Set RTC date and time", "prompt": "Adjust the current date and time. Note that the time will not persist after a power cycle if the RTC's internal battery is empty." }, + "testMenu": { + "name": "Hardware test suite", + "prompt": "Display monitor test patterns, check if all inputs are functional or test the system and cabinet's hardware." + }, "setResolution": { "name": "Change screen resolution", "prompt": "Switch to a different screen resolution and aspect ratio. Some monitors and upscalers may not support all resolutions." @@ -549,6 +623,28 @@ "ok": "Confirm" }, + "TestMenuScreen": { + "title": "{RIGHT_ARROW} Hardware test suite", + "itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back", + + "jammaTest": { + "name": "Test JAMMA inputs", + "prompt": "Show the state of all buttons and inputs wired to the system's JAMMA connector." + }, + "audioTest": { + "name": "Test audio output and speaker amplifier", + "prompt": "Play a sound on either audio output channel and test the system's onboard speaker amplification circuitry." + }, + "colorIntensity": { + "name": "Check monitor color intensity levels", + "prompt": "Display a series of undithered and dithered color bars to help with monitor color level adjustment and calibration." + }, + "geometry": { + "name": "Check monitor geometry and overscan", + "prompt": "Display a grid to help with monitor geometry adjustment. The grid's aspect ratio can be changed by switching to a different screen resolution from the main menu." + } + }, + "UnlockKeyScreen": { "title": "{RIGHT_ARROW} Select unlocking key", "prompt": "If the cartridge has been converted before, select the game it was last converted to. If it is currently blank, select 00-00-00-00-00-00-00-00.", diff --git a/src/common/file9660.cpp b/src/common/file9660.cpp index 5099f32..79c3018 100644 --- a/src/common/file9660.cpp +++ b/src/common/file9660.cpp @@ -98,7 +98,7 @@ bool ISOVolumeDesc::validateMagic(void) const { bool ISO9660File::_loadSector(uint32_t lba) { if (lba == _bufferedLBA) return true; - if (_device->read(_sectorBuffer, lba, 1)) + if (_device->readData(_sectorBuffer, lba, 1)) return false; _bufferedLBA = lba; @@ -131,7 +131,7 @@ size_t ISO9660File::read(void *output, size_t length) { auto spanLength = numSectors * ide::ATAPI_SECTOR_SIZE; if (numSectors > 0) { - if (_device->read(currentPtr, lba, numSectors)) + if (_device->readData(currentPtr, lba, numSectors)) return false; offset += spanLength; @@ -204,7 +204,7 @@ bool ISO9660Provider::_readData( ) { if (!output.allocate(numSectors * ide::ATAPI_SECTOR_SIZE)) return false; - if (_device->read(output.ptr, lba, numSectors)) + if (_device->readData(output.ptr, lba, numSectors)) return false; return true; @@ -272,7 +272,7 @@ bool ISO9660Provider::init(int drive) { for ( uint32_t lba = _VOLUME_DESC_START_LBA; lba < _VOLUME_DESC_END_LBA; lba++ ) { - if (_device->read(&pvd, lba, 1)) + if (_device->readData(&pvd, lba, 1)) return false; if (!pvd.validateMagic()) { LOG("invalid ISO descriptor, lba=0x%x", lba); diff --git a/src/common/filefat.cpp b/src/common/filefat.cpp index 16fbbb8..c044378 100644 --- a/src/common/filefat.cpp +++ b/src/common/filefat.cpp @@ -298,7 +298,7 @@ extern "C" DRESULT disk_read( if (!(dev.flags & ide::DEVICE_READY)) return RES_NOTRDY; - if (dev.read(data, lba, count)) + if (dev.readData(data, lba, count)) return RES_ERROR; return RES_OK; @@ -313,7 +313,7 @@ extern "C" DRESULT disk_write( return RES_NOTRDY; if (dev.flags & ide::DEVICE_READ_ONLY) return RES_WRPRT; - if (dev.write(data, lba, count)) + if (dev.writeData(data, lba, count)) return RES_ERROR; return RES_OK; diff --git a/src/common/ide.cpp b/src/common/ide.cpp index d3473d3..2154057 100644 --- a/src/common/ide.cpp +++ b/src/common/ide.cpp @@ -22,10 +22,12 @@ namespace ide { -static constexpr int _WAIT_TIMEOUT = 30000000; -static constexpr int _DETECT_TIMEOUT = 500000; -static constexpr int _DMA_TIMEOUT = 10000; -static constexpr int _SRST_DELAY = 5000; +static constexpr int _COMMAND_TIMEOUT = 30000000; +static constexpr int _REQUEST_SENSE_TIMEOUT = 500000; +static constexpr int _DETECT_DRIVE_TIMEOUT = 500000; + +static constexpr int _DMA_TIMEOUT = 10000; +static constexpr int _SRST_DELAY = 5000; static const char *const _SENSE_KEY_NAMES[]{ "NO_SENSE", @@ -52,6 +54,8 @@ const char *const DEVICE_ERROR_NAMES[]{ "NO_DRIVE", "STATUS_TIMEOUT", "DRIVE_ERROR", + "DISC_ERROR", + "DISC_CHANGED", "INCOMPLETE_DATA", "CHECKSUM_MISMATCH" }; @@ -120,42 +124,19 @@ int IdentifyBlock::getHighestPIOMode(void) const { Device devices[2]{ (DEVICE_PRIMARY), (DEVICE_SECONDARY) }; -void Device::_setLBA(uint64_t lba, size_t count) { - if (flags & DEVICE_HAS_LBA48) { - //assert(lba < (1ULL << 48)); - //assert(count <= (1 << 16)); - - _select(CS0_DEVICE_SEL_LBA); - - _write(CS0_COUNT, (count >> 8) & 0xff); - _write(CS0_SECTOR, (lba >> 24) & 0xff); - _write(CS0_CYLINDER_L, (lba >> 32) & 0xff); - _write(CS0_CYLINDER_H, (lba >> 40) & 0xff); - } else { - //assert(lba < (1ULL << 28)); - //assert(count <= (1 << 8)); - - _select(CS0_DEVICE_SEL_LBA | ((lba >> 24) & 15)); - } - - _write(CS0_COUNT, (count >> 0) & 0xff); - _write(CS0_SECTOR, (lba >> 0) & 0xff); - _write(CS0_CYLINDER_L, (lba >> 8) & 0xff); - _write(CS0_CYLINDER_H, (lba >> 16) & 0xff); -} - DeviceError Device::_waitForStatus( uint8_t mask, uint8_t value, int timeout, bool ignoreErrors ) { if (!timeout) - timeout = _WAIT_TIMEOUT; + timeout = _COMMAND_TIMEOUT; for (; timeout > 0; timeout -= 10) { uint8_t status = _read(CS0_STATUS); if (!ignoreErrors && (status & CS0_STATUS_ERR)) { LOG( - "IDE error, stat=0x%02x, err=0x%02x", _read(CS0_STATUS), + "drive %d error, stat=0x%02x, err=0x%02x", + (flags / DEVICE_SECONDARY) & 1, _read(CS0_STATUS), _read(CS0_ERROR) ); @@ -173,26 +154,55 @@ DeviceError Device::_waitForStatus( } LOG( - "IDE timeout, stat=0x%02x, err=0x%02x", _read(CS0_STATUS), - _read(CS0_ERROR) + "drive %d timeout, stat=0x%02x, err=0x%02x", + (flags / DEVICE_SECONDARY) & 1, _read(CS0_STATUS), _read(CS0_ERROR) ); _write(CS0_COMMAND, ATA_DEVICE_RESET); return STATUS_TIMEOUT; } -DeviceError Device::_command( - uint8_t cmd, uint8_t status, int timeout, bool ignoreErrors +DeviceError Device::_select( + uint8_t devSelFlags, int timeout, bool ignoreErrors ) { - auto error = _waitForStatus( - CS0_STATUS_BSY | status, status, timeout, ignoreErrors - ); + if (flags & DEVICE_SECONDARY) + _write(CS0_DEVICE_SEL, devSelFlags | CS0_DEVICE_SEL_SECONDARY); + else + _write(CS0_DEVICE_SEL, devSelFlags | CS0_DEVICE_SEL_PRIMARY); - if (error) - return error; + return _waitForStatus(CS0_STATUS_BSY, 0, timeout, ignoreErrors); +} - _write(CS0_COMMAND, cmd); - return _waitForStatus(CS0_STATUS_BSY, 0, timeout); +DeviceError Device::_setLBA(uint64_t lba, size_t count, int timeout) { + if (flags & DEVICE_HAS_LBA48) { + //assert(lba < (1ULL << 48)); + //assert(count <= (1 << 16)); + + auto error = _select(CS0_DEVICE_SEL_LBA, timeout); + + if (error) + return error; + + _write(CS0_COUNT, (count >> 8) & 0xff); + _write(CS0_SECTOR, (lba >> 24) & 0xff); + _write(CS0_CYLINDER_L, (lba >> 32) & 0xff); + _write(CS0_CYLINDER_H, (lba >> 40) & 0xff); + } else { + //assert(lba < (1ULL << 28)); + //assert(count <= (1 << 8)); + + auto error = _select(CS0_DEVICE_SEL_LBA | ((lba >> 24) & 15), timeout); + + if (error) + return error; + } + + _write(CS0_COUNT, (count >> 0) & 0xff); + _write(CS0_SECTOR, (lba >> 0) & 0xff); + _write(CS0_CYLINDER_L, (lba >> 8) & 0xff); + _write(CS0_CYLINDER_H, (lba >> 16) & 0xff); + + return NO_ERROR; } DeviceError Device::_detectDrive(void) { @@ -202,7 +212,11 @@ DeviceError Device::_detectDrive(void) { _write(CS1_DEVICE_CTRL, CS1_DEVICE_CTRL_IEN); delayMicroseconds(_SRST_DELAY); - _select(); + if (_select(0, _DETECT_DRIVE_TIMEOUT, true)) { + LOG("drive %d select timeout", (flags / DEVICE_SECONDARY) & 1); + return NO_DRIVE; + } + #ifndef ENABLE_FULL_IDE_DRIVER io::clearWatchdog(); #endif @@ -211,7 +225,7 @@ DeviceError Device::_detectDrive(void) { // the written value. This should not fail even if the drive is busy. uint8_t pattern = 0x55; - for (int timeout = _DETECT_TIMEOUT; timeout > 0; timeout -= 10) { + for (int timeout = _DETECT_DRIVE_TIMEOUT; timeout > 0; timeout -= 100) { _write(CS0_COUNT, pattern); // Note that ATA drives will also assert DRDY when ready, but ATAPI @@ -223,7 +237,7 @@ DeviceError Device::_detectDrive(void) { if (!(pattern & 1)) pattern |= 1 << 7; - delayMicroseconds(10); + delayMicroseconds(100); #ifndef ENABLE_FULL_IDE_DRIVER io::clearWatchdog(); #endif @@ -233,10 +247,14 @@ DeviceError Device::_detectDrive(void) { return NO_DRIVE; } -DeviceError Device::_readPIO(void *data, size_t length, int timeout) { +DeviceError Device::_readPIO( + void *data, size_t length, int timeout, bool ignoreErrors +) { util::assertAligned(data); - auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout); + auto error = _waitForStatus( + CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout + ); if (error) return error; @@ -249,10 +267,14 @@ DeviceError Device::_readPIO(void *data, size_t length, int timeout) { return NO_ERROR; } -DeviceError Device::_writePIO(const void *data, size_t length, int timeout) { +DeviceError Device::_writePIO( + const void *data, size_t length, int timeout, bool ignoreErrors +) { util::assertAligned(data); - auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout); + auto error = _waitForStatus( + CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout + ); if (error) return error; @@ -265,12 +287,16 @@ DeviceError Device::_writePIO(const void *data, size_t length, int timeout) { return NO_ERROR; } -DeviceError Device::_readDMA(void *data, size_t length, int timeout) { +DeviceError Device::_readDMA( + void *data, size_t length, int timeout, bool ignoreErrors +) { length /= 4; util::assertAligned(data); - auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout); + auto error = _waitForStatus( + CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout + ); if (error) return error; @@ -291,12 +317,16 @@ DeviceError Device::_readDMA(void *data, size_t length, int timeout) { return NO_ERROR; } -DeviceError Device::_writeDMA(const void *data, size_t length, int timeout) { +DeviceError Device::_writeDMA( + const void *data, size_t length, int timeout, bool ignoreErrors +) { length /= 4; util::assertAligned(data); - auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout); + auto error = _waitForStatus( + CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout + ); if (error) return error; @@ -334,12 +364,13 @@ DeviceError Device::_ideReadWrite( while (count) { size_t chunkLength = util::min(count, maxLength); - _setLBA(lba, chunkLength); - auto error = _command(cmd, CS0_STATUS_DRDY); + auto error = _setLBA(lba, chunkLength); if (error) return error; + _write(CS0_COMMAND, cmd); + // Data must be transferred one sector at a time as the drive may // deassert DRQ between sectors. for (size_t i = chunkLength; i; i--) { @@ -359,7 +390,7 @@ DeviceError Device::_ideReadWrite( } error = _waitForStatus( - CS0_STATUS_BSY | CS0_STATUS_DRDY, CS0_STATUS_DRDY + CS0_STATUS_DRDY | CS0_STATUS_BSY, CS0_STATUS_DRDY ); if (error) @@ -376,7 +407,7 @@ DeviceError Device::_atapiRead(uintptr_t ptr, uint32_t lba, size_t count) { Packet packet; packet.setRead(lba, count); - auto error = atapiPacket(packet); + auto error = atapiPacket(packet, ATAPI_SECTOR_SIZE); if (error) return error; @@ -384,10 +415,8 @@ DeviceError Device::_atapiRead(uintptr_t ptr, uint32_t lba, size_t count) { // Data must be transferred one sector at a time as the drive may deassert // DRQ between sectors. for (; count; count--) { - error = _readPIO(reinterpret_cast(ptr), ATAPI_SECTOR_SIZE); - - if (error) - return error; + if (_readPIO(reinterpret_cast(ptr), ATAPI_SECTOR_SIZE)) + return atapiPoll(); ptr += ATAPI_SECTOR_SIZE; } @@ -411,16 +440,18 @@ DeviceError Device::enumerate(void) { // to prevent blocking for too long. IdentifyBlock block; + //_select(); + if ((_read(CS0_CYLINDER_L) == 0x14) && (_read(CS0_CYLINDER_H) == 0xeb)) { flags |= DEVICE_ATAPI; - if (_command(ATA_IDENTIFY_PACKET, 0, _DETECT_TIMEOUT)) - return NO_DRIVE; - if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_TIMEOUT)) - return NO_DRIVE; + _write(CS0_COMMAND, ATA_IDENTIFY_PACKET); + if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_DRIVE_TIMEOUT)) + return NO_DRIVE; if (!block.validateChecksum()) return CHECKSUM_MISMATCH; + if ( (block.deviceFlags & IDENTIFY_DEV_ATAPI_TYPE_BITMASK) == IDENTIFY_DEV_ATAPI_TYPE_CDROM @@ -432,13 +463,13 @@ DeviceError Device::enumerate(void) { ) flags |= DEVICE_HAS_PACKET16; } else { - if (_command(ATA_IDENTIFY, CS0_STATUS_DRDY, _DETECT_TIMEOUT)) - return NO_DRIVE; - if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_TIMEOUT)) - return NO_DRIVE; + _write(CS0_COMMAND, ATA_IDENTIFY); + if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_DRIVE_TIMEOUT)) + return NO_DRIVE; if (!block.validateChecksum()) return CHECKSUM_MISMATCH; + if (block.commandSetFlags[1] & (1 << 10)) { flags |= DEVICE_HAS_LBA48; capacity = block.getSectorCountExt(); @@ -462,8 +493,9 @@ DeviceError Device::enumerate(void) { _write(CS0_FEATURES, FEATURE_TRANSFER_MODE); _write(CS0_COUNT, (1 << 3) | mode); + _write(CS0_COMMAND, ATA_SET_FEATURES); - error = _command(ATA_SET_FEATURES, 0); + error = _waitForStatus(CS0_STATUS_BSY, 0); if (error) return error; @@ -479,22 +511,29 @@ DeviceError Device::atapiPacket(const Packet &packet, size_t transferLength) { if (!(flags & DEVICE_ATAPI)) return UNSUPPORTED_OP; - _select(); + auto error = _select(0); + + if (error) + return atapiPoll(); _write(CS0_CYLINDER_L, (transferLength >> 0) & 0xff); _write(CS0_CYLINDER_H, (transferLength >> 8) & 0xff); + _write(CS0_COMMAND, ATA_PACKET); - auto error = _command(ATA_PACKET, 0); + error = _writePIO(&packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12); - if (!error) - error = _writePIO(&packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12); - if (!error) - return _waitForStatus(CS0_STATUS_BSY, 0); + if (error) + return atapiPoll(); - return atapiPoll(); + return _waitForStatus(CS0_STATUS_BSY, 0); } DeviceError Device::atapiPoll(void) { + if (!(flags & DEVICE_READY)) + return NO_DRIVE; + if (!(flags & DEVICE_ATAPI)) + return UNSUPPORTED_OP; + Packet packet; SenseData data; @@ -502,14 +541,23 @@ DeviceError Device::atapiPoll(void) { // If an error occurs, the error flag in the status register will be set but // the drive will still accept a request sense command. - auto error = _command(ATA_PACKET, 0, 0, true); + auto error = _select(0, _REQUEST_SENSE_TIMEOUT, true); + if (error) + return error; + + _write(CS0_CYLINDER_L, (sizeof(data) >> 0) & 0xff); + _write(CS0_CYLINDER_H, (sizeof(data) >> 8) & 0xff); + _write(CS0_COMMAND, ATA_PACKET); + + error = _writePIO( + &packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12, _REQUEST_SENSE_TIMEOUT + ); + + //if (!error) + //error = _waitForStatus(CS0_STATUS_BSY, 0, _REQUEST_SENSE_TIMEOUT); if (!error) - error = _writePIO(&packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12); - if (!error) - error = _waitForStatus(CS0_STATUS_BSY, 0); - if (!error) - error = _readPIO(&data, sizeof(data)); + error = _readPIO(&data, sizeof(data), _REQUEST_SENSE_TIMEOUT); int senseKey; @@ -526,10 +574,11 @@ DeviceError Device::atapiPoll(void) { ); } - LOG("%s (%d)", _SENSE_KEY_NAMES[senseKey], senseKey); + LOG("sense key: %s (%d)", _SENSE_KEY_NAMES[senseKey], senseKey); switch (senseKey) { case SENSE_KEY_NO_SENSE: + case SENSE_KEY_RECOVERED_ERROR: return NO_ERROR; case SENSE_KEY_NOT_READY: @@ -545,31 +594,23 @@ DeviceError Device::atapiPoll(void) { } } -DeviceError Device::read(void *data, uint64_t lba, size_t count) { +DeviceError Device::readData(void *data, uint64_t lba, size_t count) { util::assertAligned(data); if (!(flags & DEVICE_READY)) return NO_DRIVE; - if (flags & DEVICE_ATAPI) { - auto error = _atapiRead( + if (flags & DEVICE_ATAPI) + return _atapiRead( reinterpret_cast(data), static_cast(lba), count ); - -#ifdef ENABLE_FULL_IDE_DRIVER - if (error) - error = atapiPoll(); -#endif - - return error; - } else { + else return _ideReadWrite( reinterpret_cast(data), lba, count, false ); - } } -DeviceError Device::write(const void *data, uint64_t lba, size_t count) { +DeviceError Device::writeData(const void *data, uint64_t lba, size_t count) { util::assertAligned(data); if (!(flags & DEVICE_READY)) @@ -590,25 +631,17 @@ DeviceError Device::goIdle(bool standby) { packet.setStartStopUnit(START_STOP_MODE_STOP_DISC); return atapiPacket(packet); } else { - _select(); + auto error = _select(CS0_DEVICE_SEL_LBA); - auto error = _command( - standby ? ATA_STANDBY_IMMEDIATE : ATA_IDLE_IMMEDIATE, - CS0_STATUS_DRDY - ); + if (error) + return error; -#if 0 - if (error) { - // If the immediate command failed, fall back to setting the - // inactivity timeout to the lowest allowed value (5 seconds). - // FIXME: the original timeout would have to be restored once the - // drive is accessed again - _write(CS0_COUNT, 1); - error = _command(standby ? ATA_STANDBY : ATA_IDLE, CS0_STATUS_DRDY); - } -#endif + if (standby) + _write(CS0_COMMAND, ATA_STANDBY_IMMEDIATE); + else + _write(CS0_COMMAND, ATA_IDLE_IMMEDIATE); - return error; + return _waitForStatus(CS0_STATUS_BSY, 0); } } @@ -619,12 +652,17 @@ DeviceError Device::flushCache(void) { return NO_ERROR; //return UNSUPPORTED_OP; - _select(); + auto error = _select(CS0_DEVICE_SEL_LBA); - return _command( - (flags & DEVICE_HAS_LBA48) ? ATA_FLUSH_CACHE_EXT : ATA_FLUSH_CACHE, - CS0_STATUS_DRDY - ); + if (error) + return error; + + if (flags & DEVICE_HAS_LBA48) + _write(CS0_COMMAND, ATA_FLUSH_CACHE_EXT); + else + _write(CS0_COMMAND, ATA_FLUSH_CACHE); + + return _waitForStatus(CS0_STATUS_DRDY | CS0_STATUS_BSY, CS0_STATUS_DRDY); } } diff --git a/src/common/ide.hpp b/src/common/ide.hpp index 3edc896..15559cf 100644 --- a/src/common/ide.hpp +++ b/src/common/ide.hpp @@ -370,26 +370,29 @@ private: SYS573_IDE_CS1_BASE[reg] = value; } - inline void _select(uint8_t regFlags = 0) { - if (flags & DEVICE_SECONDARY) - _write(CS0_DEVICE_SEL, regFlags | CS0_DEVICE_SEL_SECONDARY); - else - _write(CS0_DEVICE_SEL, regFlags | CS0_DEVICE_SEL_PRIMARY); - } - - void _setLBA(uint64_t lba, size_t count); DeviceError _waitForStatus( uint8_t mask, uint8_t value, int timeout = 0, bool ignoreErrors = false ); - DeviceError _command( - uint8_t cmd, uint8_t status, int timeout = 0, bool ignoreErrors = false + DeviceError _select( + uint8_t devSelFlags, int timeout = 0, bool ignoreErrors = false ); + DeviceError _setLBA(uint64_t lba, size_t count, int timeout = 0); DeviceError _detectDrive(void); - DeviceError _readPIO(void *data, size_t length, int timeout = 0); - DeviceError _writePIO(const void *data, size_t length, int timeout = 0); - DeviceError _readDMA(void *data, size_t length, int timeout = 0); - DeviceError _writeDMA(const void *data, size_t length, int timeout = 0); + DeviceError _readPIO( + void *data, size_t length, int timeout = 0, bool ignoreErrors = false + ); + DeviceError _writePIO( + const void *data, size_t length, int timeout = 0, + bool ignoreErrors = false + ); + DeviceError _readDMA( + void *data, size_t length, int timeout = 0, bool ignoreErrors = false + ); + DeviceError _writeDMA( + const void *data, size_t length, int timeout = 0, + bool ignoreErrors = false + ); DeviceError _ideReadWrite( uintptr_t ptr, uint64_t lba, size_t count, bool write @@ -421,13 +424,11 @@ public: } DeviceError enumerate(void); - DeviceError atapiPacket( - const Packet &packet, size_t transferLength = ATAPI_SECTOR_SIZE - ); + DeviceError atapiPacket(const Packet &packet, size_t transferLength = 0); DeviceError atapiPoll(void); - DeviceError read(void *data, uint64_t lba, size_t count); - DeviceError write(const void *data, uint64_t lba, size_t count); + DeviceError readData(void *data, uint64_t lba, size_t count); + DeviceError writeData(const void *data, uint64_t lba, size_t count); DeviceError goIdle(bool standby = false); DeviceError flushCache(void); }; diff --git a/src/common/spu.cpp b/src/common/spu.cpp index 9047ead..92fa8a7 100644 --- a/src/common/spu.cpp +++ b/src/common/spu.cpp @@ -165,14 +165,14 @@ bool Sound::initFromVAGHeader(const VAGHeader *header, uint32_t ramOffset) { return true; } -Channel Sound::play(uint16_t volume, Channel ch) const { +Channel Sound::play(uint16_t left, uint16_t right, Channel ch) const { if ((ch < 0) || (ch >= NUM_CHANNELS)) return -1; if (!offset) return -1; - SPU_CH_VOL_L(ch) = volume; - SPU_CH_VOL_R(ch) = volume; + SPU_CH_VOL_L(ch) = left; + SPU_CH_VOL_R(ch) = right; SPU_CH_FREQ (ch) = sampleRate; SPU_CH_ADDR (ch) = offset; SPU_CH_ADSR1(ch) = 0x00ff; diff --git a/src/common/spu.hpp b/src/common/spu.hpp index 762a6b7..c471787 100644 --- a/src/common/spu.hpp +++ b/src/common/spu.hpp @@ -24,12 +24,12 @@ static inline void setMasterVolume(uint16_t master, uint16_t reverb = 0) { SPU_REVERB_VOL_R = reverb; } -static inline void setChannelVolume(Channel ch, uint16_t volume) { +static inline void setChannelVolume(Channel ch, uint16_t left, uint16_t right) { if ((ch < 0) || (ch >= NUM_CHANNELS)) return; - SPU_CH_VOL_L(ch) = volume; - SPU_CH_VOL_R(ch) = volume; + SPU_CH_VOL_L(ch) = left; + SPU_CH_VOL_R(ch) = right; } void init(void); @@ -54,12 +54,14 @@ public: inline Sound(void) : offset(0), length(0) {} - inline Channel play(uint16_t volume = MAX_VOLUME) const { - return play(volume, getFreeChannel()); + inline Channel play( + uint16_t left = MAX_VOLUME, uint16_t right = MAX_VOLUME + ) const { + return play(left, right, getFreeChannel()); } bool initFromVAGHeader(const VAGHeader *header, uint32_t ramOffset); - Channel play(uint16_t volume, Channel ch) const; + Channel play(uint16_t left, uint16_t right, Channel ch) const; }; } diff --git a/src/launcher/main.cpp b/src/launcher/main.cpp index b29ed7a..49b2f94 100644 --- a/src/launcher/main.cpp +++ b/src/launcher/main.cpp @@ -16,7 +16,7 @@ int main(int argc, const char **argv) { args.parseArgument(*(argv++)); #ifdef ENABLE_LOGGING - util::logger.setupSyslog(launcher.args.baudRate); + util::logger.setupSyslog(args.baudRate); #endif if (!args.entryPoint || !args.loadAddress || !args.numFragments) @@ -52,7 +52,7 @@ int main(int argc, const char **argv) { length -= skipSectors; } - if (dev.read(reinterpret_cast(ptr), lba, length)) + if (dev.readData(reinterpret_cast(ptr), lba, length)) return 3; io::clearWatchdog(); diff --git a/src/main/app/app.cpp b/src/main/app/app.cpp index 331bf15..3150b1b 100644 --- a/src/main/app/app.cpp +++ b/src/main/app/app.cpp @@ -342,6 +342,7 @@ void App::_interruptHandler(void) { _runWorker(&App::_ideInitWorker, _warningScreen); _setupInterrupts(); + _ctx.sounds[ui::SOUND_STARTUP].play(); for (;;) { util::Date date; diff --git a/src/main/app/app.hpp b/src/main/app/app.hpp index 8b4701a..8c3223a 100644 --- a/src/main/app/app.hpp +++ b/src/main/app/app.hpp @@ -12,6 +12,7 @@ #include "main/app/misc.hpp" #include "main/app/modals.hpp" #include "main/app/romactions.hpp" +#include "main/app/tests.hpp" #include "main/cart.hpp" #include "main/cartdata.hpp" #include "main/cartio.hpp" @@ -55,7 +56,7 @@ private: public: file::Provider *ide[util::countOf(ide::devices)]; file::ZIPProvider resource; -#ifndef NDEBUG +#ifdef ENABLE_PCDRV file::HostProvider host; #endif file::VFSProvider vfs; @@ -81,54 +82,84 @@ class App { friend class ConfirmScreen; friend class FilePickerScreen; friend class FileBrowserScreen; - friend class AutobootScreen; + friend class WarningScreen; + friend class AutobootScreen; friend class ButtonMappingScreen; friend class MainMenuScreen; - friend class StorageInfoScreen; - friend class StorageActionsScreen; - friend class IDEInfoScreen; - friend class RTCTimeScreen; - friend class ResolutionScreen; - friend class AboutScreen; + friend class CartInfoScreen; friend class UnlockKeyScreen; friend class KeyEntryScreen; + friend class CartActionsScreen; friend class QRCodeScreen; friend class HexdumpScreen; friend class ReflashGameScreen; friend class SystemIDEntryScreen; + + friend class StorageInfoScreen; + friend class StorageActionsScreen; friend class CardSizeScreen; friend class ChecksumScreen; + friend class TestMenuScreen; + friend class JAMMATestScreen; + friend class AudioTestScreen; + friend class TestPatternScreen; + friend class ColorIntensityScreen; + friend class GeometryScreen; + + friend class IDEInfoScreen; + friend class RTCTimeScreen; + friend class ResolutionScreen; + friend class AboutScreen; + private: - WorkerStatusScreen _workerStatusScreen; - MessageScreen _messageScreen; - ConfirmScreen _confirmScreen; - FilePickerScreen _filePickerScreen; - FileBrowserScreen _fileBrowserScreen; - AutobootScreen _autobootScreen; - WarningScreen _warningScreen; - ButtonMappingScreen _buttonMappingScreen; - MainMenuScreen _mainMenuScreen; + // modals.cpp + WorkerStatusScreen _workerStatusScreen; + MessageScreen _messageScreen; + ConfirmScreen _confirmScreen; + FilePickerScreen _filePickerScreen; + FileBrowserScreen _fileBrowserScreen; + + // main.cpp + WarningScreen _warningScreen; + AutobootScreen _autobootScreen; + ButtonMappingScreen _buttonMappingScreen; + MainMenuScreen _mainMenuScreen; + + // cartunlock.cpp + CartInfoScreen _cartInfoScreen; + UnlockKeyScreen _unlockKeyScreen; + KeyEntryScreen _keyEntryScreen; + + // cartactions.cpp + CartActionsScreen _cartActionsScreen; + QRCodeScreen _qrCodeScreen; + HexdumpScreen _hexdumpScreen; + ReflashGameScreen _reflashGameScreen; + SystemIDEntryScreen _systemIDEntryScreen; + + // romactions.cpp StorageInfoScreen _storageInfoScreen; StorageActionsScreen _storageActionsScreen; - IDEInfoScreen _ideInfoScreen; - RTCTimeScreen _rtcTimeScreen; - ResolutionScreen _resolutionScreen; - AboutScreen _aboutScreen; - CartInfoScreen _cartInfoScreen; - UnlockKeyScreen _unlockKeyScreen; - KeyEntryScreen _keyEntryScreen; - CartActionsScreen _cartActionsScreen; - QRCodeScreen _qrCodeScreen; - HexdumpScreen _hexdumpScreen; - ReflashGameScreen _reflashGameScreen; - SystemIDEntryScreen _systemIDEntryScreen; CardSizeScreen _cardSizeScreen; ChecksumScreen _checksumScreen; + // tests.cpp + TestMenuScreen _testMenuScreen; + JAMMATestScreen _jammaTestScreen; + AudioTestScreen _audioTestScreen; + ColorIntensityScreen _colorIntensityScreen; + GeometryScreen _geometryScreen; + + // misc.cpp + IDEInfoScreen _ideInfoScreen; + RTCTimeScreen _rtcTimeScreen; + ResolutionScreen _resolutionScreen; + AboutScreen _aboutScreen; + #ifdef ENABLE_LOG_BUFFER util::LogBuffer _logBuffer; ui::LogOverlay _logOverlay; @@ -166,6 +197,9 @@ private: bool playSound = false ); + void _worker(void); + void _interruptHandler(void); + // cartworkers.cpp bool _cartDetectWorker(void); bool _cartUnlockWorker(void); @@ -189,9 +223,6 @@ private: bool _atapiEjectWorker(void); bool _rebootWorker(void); - void _worker(void); - void _interruptHandler(void); - public: App(ui::Context &ctx); ~App(void); diff --git a/src/main/app/main.cpp b/src/main/app/main.cpp index 88b5e26..bbb8fb8 100644 --- a/src/main/app/main.cpp +++ b/src/main/app/main.cpp @@ -154,6 +154,10 @@ static const MenuEntry _MENU_ENTRIES[]{ .name = "MainMenuScreen.setRTCTime.name"_h, .prompt = "MainMenuScreen.setRTCTime.prompt"_h, .target = &MainMenuScreen::setRTCTime + }, { + .name = "MainMenuScreen.testMenu.name"_h, + .prompt = "MainMenuScreen.testMenu.prompt"_h, + .target = &MainMenuScreen::testMenu }, { .name = "MainMenuScreen.setResolution.name"_h, .prompt = "MainMenuScreen.setResolution.prompt"_h, @@ -215,6 +219,10 @@ void MainMenuScreen::setRTCTime(ui::Context &ctx) { ctx.show(APP->_rtcTimeScreen, false, true); } +void MainMenuScreen::testMenu(ui::Context &ctx) { + ctx.show(APP->_testMenuScreen, false, true); +} + void MainMenuScreen::setResolution(ui::Context &ctx) { ctx.show(APP->_resolutionScreen, false, true); } diff --git a/src/main/app/main.hpp b/src/main/app/main.hpp index b84fde1..b1d3eaa 100644 --- a/src/main/app/main.hpp +++ b/src/main/app/main.hpp @@ -48,6 +48,7 @@ public: void ideInfo(ui::Context &ctx); void runExecutable(ui::Context &ctx); void setRTCTime(ui::Context &ctx); + void testMenu(ui::Context &ctx); void setResolution(ui::Context &ctx); void about(ui::Context &ctx); void ejectCD(ui::Context &ctx); diff --git a/src/main/app/misc.cpp b/src/main/app/misc.cpp index a6ffacc..aadb7c4 100644 --- a/src/main/app/misc.cpp +++ b/src/main/app/misc.cpp @@ -277,7 +277,7 @@ void AboutScreen::show(ui::Context &ctx, bool goBack) { ctx.time, 0, _LOOP_FADE_IN_VOLUME, ctx.gpuCtx.refreshRate * _LOOP_FADE_IN_TIME ); - _loopChannel = ctx.sounds[ui::SOUND_ABOUT_SCREEN].play(0); + _loopChannel = ctx.sounds[ui::SOUND_ABOUT_SCREEN].play(0, 0); } void AboutScreen::hide(ui::Context &ctx, bool goBack) { @@ -290,7 +290,9 @@ void AboutScreen::hide(ui::Context &ctx, bool goBack) { void AboutScreen::update(ui::Context &ctx) { TextScreen::update(ctx); - spu::setChannelVolume(_loopChannel, _loopVolume.getValue(ctx.time)); + + auto volume = _loopVolume.getValue(ctx.time); + spu::setChannelVolume(_loopChannel, volume, volume); if (ctx.buttons.pressed(ui::BTN_START)) ctx.show(APP->_mainMenuScreen, true, true); diff --git a/src/main/app/miscworkers.cpp b/src/main/app/miscworkers.cpp index 31fb737..69334f1 100644 --- a/src/main/app/miscworkers.cpp +++ b/src/main/app/miscworkers.cpp @@ -59,7 +59,6 @@ bool App::_ideInitWorker(void) { } #endif - _ctx.sounds[ui::SOUND_STARTUP].play(); return true; } diff --git a/src/main/app/tests.cpp b/src/main/app/tests.cpp new file mode 100644 index 0000000..6f5f03f --- /dev/null +++ b/src/main/app/tests.cpp @@ -0,0 +1,416 @@ + +#include "common/gpu.hpp" +#include "common/io.hpp" +#include "common/spu.hpp" +#include "common/util.hpp" +#include "main/app/app.hpp" +#include "main/app/tests.hpp" +#include "main/uibase.hpp" +#include "main/uicommon.hpp" +#include "tests.hpp" + +/* Top-level test menu */ + +struct TestMenuEntry { +public: + util::Hash name, prompt; + void (TestMenuScreen::*target)(ui::Context &ctx); +}; + +static const TestMenuEntry _TEST_MENU_ENTRIES[]{ + { + .name = "TestMenuScreen.jammaTest.name"_h, + .prompt = "TestMenuScreen.jammaTest.prompt"_h, + .target = &TestMenuScreen::jammaTest + }, { + .name = "TestMenuScreen.audioTest.name"_h, + .prompt = "TestMenuScreen.audioTest.prompt"_h, + .target = &TestMenuScreen::audioTest + }, { + .name = "TestMenuScreen.colorIntensity.name"_h, + .prompt = "TestMenuScreen.colorIntensity.prompt"_h, + .target = &TestMenuScreen::colorIntensity + }, { + .name = "TestMenuScreen.geometry.name"_h, + .prompt = "TestMenuScreen.geometry.prompt"_h, + .target = &TestMenuScreen::geometry + } +}; + +const char *TestMenuScreen::_getItemName(ui::Context &ctx, int index) const { + return STRH(_TEST_MENU_ENTRIES[index].name); +} + +void TestMenuScreen::jammaTest(ui::Context &ctx) { + ctx.show(APP->_jammaTestScreen, false, true); +} + +void TestMenuScreen::audioTest(ui::Context &ctx) { + ctx.show(APP->_audioTestScreen, false, true); +} + +void TestMenuScreen::colorIntensity(ui::Context &ctx) { + ctx.show(APP->_colorIntensityScreen, false, true); +} + +void TestMenuScreen::geometry(ui::Context &ctx) { + ctx.show(APP->_geometryScreen, false, true); +} + +void TestMenuScreen::show(ui::Context &ctx, bool goBack) { + _title = STR("TestMenuScreen.title"); + _prompt = STRH(_TEST_MENU_ENTRIES[0].prompt); + _itemPrompt = STR("TestMenuScreen.itemPrompt"); + + _listLength = util::countOf(_TEST_MENU_ENTRIES); + + ListScreen::show(ctx, goBack); +} + +void TestMenuScreen::update(ui::Context &ctx) { + auto &entry = _TEST_MENU_ENTRIES[_activeItem]; + _prompt = STRH(entry.prompt); + + ListScreen::update(ctx); + + if (ctx.buttons.pressed(ui::BTN_START)) { + if (ctx.buttons.held(ui::BTN_LEFT) || ctx.buttons.held(ui::BTN_RIGHT)) + ctx.show(APP->_mainMenuScreen, true, true); + else + (this->*entry.target)(ctx); + } +} + +/* Test submenus */ + +static const util::Hash _JAMMA_INPUT_NAMES[]{ + "JAMMATestScreen.p2.left"_h, // io::JAMMA_P2_LEFT + "JAMMATestScreen.p2.right"_h, // io::JAMMA_P2_RIGHT + "JAMMATestScreen.p2.up"_h, // io::JAMMA_P2_UP + "JAMMATestScreen.p2.down"_h, // io::JAMMA_P2_DOWN + "JAMMATestScreen.p2.button1"_h, // io::JAMMA_P2_BUTTON1 + "JAMMATestScreen.p2.button2"_h, // io::JAMMA_P2_BUTTON2 + "JAMMATestScreen.p2.button3"_h, // io::JAMMA_P2_BUTTON3 + "JAMMATestScreen.p2.start"_h, // io::JAMMA_P2_START + "JAMMATestScreen.p1.left"_h, // io::JAMMA_P1_LEFT + "JAMMATestScreen.p1.right"_h, // io::JAMMA_P1_RIGHT + "JAMMATestScreen.p1.up"_h, // io::JAMMA_P1_UP + "JAMMATestScreen.p1.down"_h, // io::JAMMA_P1_DOWN + "JAMMATestScreen.p1.button1"_h, // io::JAMMA_P1_BUTTON1 + "JAMMATestScreen.p1.button2"_h, // io::JAMMA_P1_BUTTON2 + "JAMMATestScreen.p1.button3"_h, // io::JAMMA_P1_BUTTON3 + "JAMMATestScreen.p1.start"_h, // io::JAMMA_P1_START + "JAMMATestScreen.p1.button4"_h, // io::JAMMA_P1_BUTTON4 + "JAMMATestScreen.p1.button5"_h, // io::JAMMA_P1_BUTTON5 + "JAMMATestScreen.test"_h, // io::JAMMA_TEST + "JAMMATestScreen.p1.button6"_h, // io::JAMMA_P1_BUTTON6 + "JAMMATestScreen.p2.button4"_h, // io::JAMMA_P2_BUTTON4 + "JAMMATestScreen.p2.button5"_h, // io::JAMMA_P2_BUTTON5 + 0, // io::JAMMA_RAM_LAYOUT + "JAMMATestScreen.p2.button6"_h, // io::JAMMA_P2_BUTTON6 + "JAMMATestScreen.coin1"_h, // io::JAMMA_COIN1 + "JAMMATestScreen.coin2"_h, // io::JAMMA_COIN2 + 0, // io::JAMMA_PCMCIA_CD1 + 0, // io::JAMMA_PCMCIA_CD2 + "JAMMATestScreen.service"_h // io::JAMMA_SERVICE +}; + +#define _PRINT(...) (ptr += snprintf(ptr, end - ptr __VA_OPT__(,) __VA_ARGS__)) +#define _PRINTLN() (*(ptr++) = '\n') + +void JAMMATestScreen::show(ui::Context &ctx, bool goBack) { + _title = STR("JAMMATestScreen.title"); + _body = _bodyText; + _prompt = STR("JAMMATestScreen.prompt"); + + _bodyText[0] = 0; + + TextScreen::show(ctx, goBack); +} + +void JAMMATestScreen::update(ui::Context &ctx) { + char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)]; + + auto inputs = io::getJAMMAInputs(); + + if (inputs) { + _PRINT(STR("JAMMATestScreen.inputs")); + + for (auto name : _JAMMA_INPUT_NAMES) { + if ((inputs & 1) && name) + _PRINT(STRH(name)); + + inputs >>= 1; + } + + _PRINT(STR("JAMMATestScreen.inputsNote")); + } else { + _PRINT(STR("JAMMATestScreen.noInputs")); + } + + *(--ptr) = 0; + //LOG("remaining=%d", end - ptr); + + //TextScreen::update(ctx); + + if (ctx.buttons.longPressed(ui::BTN_START)) + ctx.show(APP->_testMenuScreen, true, true); +} + +struct AudioTestEntry { +public: + util::Hash name; + void (AudioTestScreen::*target)(ui::Context &ctx); +}; + +static const AudioTestEntry _AUDIO_TEST_ENTRIES[]{ + { + .name = "AudioTestScreen.playLeft"_h, + .target = &AudioTestScreen::playLeft + }, { + .name = "AudioTestScreen.playRight"_h, + .target = &AudioTestScreen::playRight + }, { + .name = "AudioTestScreen.playBoth"_h, + .target = &AudioTestScreen::playBoth + }, { + .name = "AudioTestScreen.enableAmp"_h, + .target = &AudioTestScreen::enableAmp + }, { + .name = "AudioTestScreen.disableAmp"_h, + .target = &AudioTestScreen::disableAmp + }, { + .name = "AudioTestScreen.enableCDDA"_h, + .target = &AudioTestScreen::enableCDDA + }, { + .name = "AudioTestScreen.disableCDDA"_h, + .target = &AudioTestScreen::disableCDDA + } +}; + +const char *AudioTestScreen::_getItemName(ui::Context &ctx, int index) const { + return STRH(_AUDIO_TEST_ENTRIES[index].name); +} + +void AudioTestScreen::playLeft(ui::Context &ctx) { + ctx.sounds[ui::SOUND_STARTUP].play(spu::MAX_VOLUME, 0); +} + +void AudioTestScreen::playRight(ui::Context &ctx) { + ctx.sounds[ui::SOUND_STARTUP].play(0, spu::MAX_VOLUME); +} + +void AudioTestScreen::playBoth(ui::Context &ctx) { + ctx.sounds[ui::SOUND_STARTUP].play(); +} + +void AudioTestScreen::enableAmp(ui::Context &ctx) { + io::setMiscOutput(io::MISC_AMP_ENABLE, true); +} + +void AudioTestScreen::disableAmp(ui::Context &ctx) { + io::setMiscOutput(io::MISC_AMP_ENABLE, false); +} + +void AudioTestScreen::enableCDDA(ui::Context &ctx) { + io::setMiscOutput(io::MISC_CDDA_ENABLE, true); +} + +void AudioTestScreen::disableCDDA(ui::Context &ctx) { + io::setMiscOutput(io::MISC_CDDA_ENABLE, false); +} + +void AudioTestScreen::show(ui::Context &ctx, bool goBack) { + _title = STR("AudioTestScreen.title"); + _prompt = STR("AudioTestScreen.prompt"); + _itemPrompt = STR("AudioTestScreen.itemPrompt"); + + _listLength = util::countOf(_AUDIO_TEST_ENTRIES); + + ListScreen::show(ctx, goBack); +} + +void AudioTestScreen::update(ui::Context &ctx) { + ListScreen::update(ctx); + + if (ctx.buttons.pressed(ui::BTN_START)) { + if (ctx.buttons.held(ui::BTN_LEFT) || ctx.buttons.held(ui::BTN_RIGHT)) { + io::setMiscOutput(io::MISC_AMP_ENABLE, false); + io::setMiscOutput(io::MISC_CDDA_ENABLE, false); + + ctx.show(APP->_testMenuScreen, true, true); + } else { + (this->*_AUDIO_TEST_ENTRIES[_activeItem].target)(ctx); + } + } +} + +/* Base test pattern screen class */ + +static constexpr gpu::Color _BACKGROUND_COLOR = 0x000000; +static constexpr gpu::Color _FOREGROUND_COLOR = 0xffffff; + +void TestPatternScreen::_drawTextOverlay( + ui::Context &ctx, const char *title, const char *prompt +) const { + int screenWidth = ctx.gpuCtx.width - ui::SCREEN_MARGIN_X * 2; + int screenHeight = ctx.gpuCtx.height - ui::SCREEN_MARGIN_Y * 2; + + _newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height); + + gpu::RectWH backdropRect; + + backdropRect.x = ui::SCREEN_MARGIN_X - ui::SHADOW_OFFSET; + backdropRect.y = ui::SCREEN_MARGIN_Y - ui::SHADOW_OFFSET; + backdropRect.w = ui::SHADOW_OFFSET * 2 + screenWidth; + backdropRect.h = ui::SHADOW_OFFSET * 2 + ctx.font.metrics.lineHeight; + ctx.gpuCtx.drawRect(backdropRect, ctx.colors[ui::COLOR_SHADOW], true); + + backdropRect.y += screenHeight - ui::SCREEN_PROMPT_HEIGHT_MIN; + ctx.gpuCtx.drawRect(backdropRect, ctx.colors[ui::COLOR_SHADOW], true); + + gpu::Rect textRect; + + textRect.x1 = ui::SCREEN_MARGIN_X; + textRect.y1 = ui::SCREEN_MARGIN_Y; + textRect.x2 = textRect.x1 + screenWidth; + textRect.y2 = textRect.y1 + ctx.font.metrics.lineHeight; + ctx.font.draw(ctx.gpuCtx, title, textRect, ctx.colors[ui::COLOR_TITLE]); + + textRect.y1 += screenHeight - ui::SCREEN_PROMPT_HEIGHT_MIN; + textRect.y2 += textRect.y1; + ctx.font.draw( + ctx.gpuCtx, prompt, textRect, ctx.colors[ui::COLOR_TEXT1], true + ); +} + +void TestPatternScreen::draw(ui::Context &ctx, bool active) const { + _newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height); + ctx.gpuCtx.drawRect( + 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height, _BACKGROUND_COLOR + ); +} + +void TestPatternScreen::update(ui::Context &ctx) { + if (ctx.buttons.pressed(ui::BTN_START)) + ctx.show(APP->_testMenuScreen, true, true); +} + +/* Color intensity test screen */ + +struct IntensityBar { +public: + util::Hash name; + gpu::Color color; +}; + +static constexpr int _INTENSITY_BAR_NAME_WIDTH = 32; +static constexpr int _INTENSITY_BAR_WIDTH = 256; +static constexpr int _INTENSITY_BAR_HEIGHT = 32; + +static const IntensityBar _INTENSITY_BARS[]{ + { + .name = "ColorIntensityScreen.white"_h, + .color = 0xffffff + }, { + .name = "ColorIntensityScreen.red"_h, + .color = 0x0000ff + }, { + .name = "ColorIntensityScreen.green"_h, + .color = 0x00ff00 + }, { + .name = "ColorIntensityScreen.blue"_h, + .color = 0xff0000 + } +}; + +void ColorIntensityScreen::draw(ui::Context &ctx, bool active) const { + TestPatternScreen::draw(ctx, active); + + int barWidth = _INTENSITY_BAR_NAME_WIDTH + _INTENSITY_BAR_WIDTH; + int barHeight = _INTENSITY_BAR_HEIGHT * util::countOf(_INTENSITY_BARS); + int offsetX = (ctx.gpuCtx.width - barWidth) / 2; + int offsetY = (ctx.gpuCtx.height - barHeight) / 2; + + gpu::RectWH textRect, barRect; + + textRect.x = offsetX; + textRect.y = + offsetY + (_INTENSITY_BAR_HEIGHT - ctx.font.metrics.lineHeight) / 2; + textRect.w = _INTENSITY_BAR_NAME_WIDTH; + textRect.h = ctx.font.metrics.lineHeight; + + barRect.x = offsetX + _INTENSITY_BAR_NAME_WIDTH; + barRect.y = offsetY; + barRect.w = _INTENSITY_BAR_WIDTH; + barRect.h = _INTENSITY_BAR_HEIGHT / 2; + + for (auto &bar : _INTENSITY_BARS) { + ctx.font.draw( + ctx.gpuCtx, STRH(bar.name), textRect, ctx.colors[ui::COLOR_TEXT1] + ); + textRect.y += _INTENSITY_BAR_HEIGHT; + + ctx.gpuCtx.setTexturePage(0, false); + ctx.gpuCtx.drawGradientRectH(barRect, _BACKGROUND_COLOR, bar.color); + barRect.y += _INTENSITY_BAR_HEIGHT / 2; + + ctx.gpuCtx.setTexturePage(0, true); + ctx.gpuCtx.drawGradientRectH(barRect, _BACKGROUND_COLOR, bar.color); + barRect.y += _INTENSITY_BAR_HEIGHT / 2; + } + + char value[2]{ 0, 0 }; + + textRect.x = barRect.x + 1; + textRect.y = offsetY - ctx.font.metrics.lineHeight; + textRect.w = _INTENSITY_BAR_WIDTH / 32; + + for (int i = 0; i < 32; i++, textRect.x += textRect.w) { + value[0] = util::HEX_CHARSET[i & 15]; + ctx.font.draw(ctx.gpuCtx, value, textRect, ctx.colors[ui::COLOR_TEXT2]); + } + + _drawTextOverlay( + ctx, STR("ColorIntensityScreen.title"), + STR("ColorIntensityScreen.prompt") + ); +} + +/* Geometry test screen */ + +static constexpr int _GRID_CELL_SIZE = 16; + +void GeometryScreen::draw(ui::Context &ctx, bool active) const { + TestPatternScreen::draw(ctx, active); + + for (int x = -1; x < ctx.gpuCtx.width; x += _GRID_CELL_SIZE) + ctx.gpuCtx.drawRect( + x, 0, 2, ctx.gpuCtx.height, ctx.colors[ui::COLOR_TEXT1] + ); + for (int y = -1; y < ctx.gpuCtx.height; y += _GRID_CELL_SIZE) + ctx.gpuCtx.drawRect( + 0, y, ctx.gpuCtx.width, 2, ctx.colors[ui::COLOR_TEXT1] + ); + + int offset = (_GRID_CELL_SIZE / 2) - 1; + int rightOffset = ctx.gpuCtx.width - (offset + 2); + int bottomOffset = ctx.gpuCtx.height - (offset + 2); + + for (int x = offset; x <= rightOffset; x += _GRID_CELL_SIZE) { + for (int y = offset; y <= bottomOffset; y += _GRID_CELL_SIZE) { + auto color = ( + (x == offset) || (y == offset) || (x == rightOffset) || + (y == bottomOffset) + ) + ? ctx.colors[ui::COLOR_ACCENT1] + : _FOREGROUND_COLOR; + + ctx.gpuCtx.drawRect(x, y, 2, 2, color); + } + } + + _drawTextOverlay( + ctx, STR("GeometryScreen.title"), STR("GeometryScreen.prompt") + ); +} diff --git a/src/main/app/tests.hpp b/src/main/app/tests.hpp new file mode 100644 index 0000000..a69a35b --- /dev/null +++ b/src/main/app/tests.hpp @@ -0,0 +1,72 @@ + +#pragma once + +#include "main/uibase.hpp" +#include "main/uicommon.hpp" + +/* Top-level test menu */ + +class TestMenuScreen : public ui::ListScreen { +protected: + const char *_getItemName(ui::Context &ctx, int index) const; + +public: + void jammaTest(ui::Context &ctx); + void audioTest(ui::Context &ctx); + void colorIntensity(ui::Context &ctx); + void geometry(ui::Context &ctx); + + void show(ui::Context &ctx, bool goBack = false); + void update(ui::Context &ctx); +}; + +/* Test submenus */ + +class JAMMATestScreen : public ui::TextScreen { +private: + char _bodyText[2048]; + +public: + void show(ui::Context &ctx, bool goBack = false); + void update(ui::Context &ctx); +}; + +class AudioTestScreen : public ui::ListScreen { +protected: + const char *_getItemName(ui::Context &ctx, int index) const; + +public: + void playLeft(ui::Context &ctx); + void playRight(ui::Context &ctx); + void playBoth(ui::Context &ctx); + void enableAmp(ui::Context &ctx); + void disableAmp(ui::Context &ctx); + void enableCDDA(ui::Context &ctx); + void disableCDDA(ui::Context &ctx); + + void show(ui::Context &ctx, bool goBack = false); + void update(ui::Context &ctx); +}; + +/* Test pattern screens */ + +class TestPatternScreen : public ui::AnimatedScreen { +protected: + void _drawTextOverlay( + ui::Context &ctx, const char *title, const char *prompt + ) const; + +public: + virtual void draw(ui::Context &ctx, bool active) const; + virtual void update(ui::Context &ctx); +}; + +class ColorIntensityScreen : public TestPatternScreen { +public: + void draw(ui::Context &ctx, bool active) const; +}; + +class GeometryScreen : public TestPatternScreen { +public: + void draw(ui::Context &ctx, bool active) const; +};