diff --git a/LICENSE b/LICENSE index 2290f59..8d42268 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Chris Hooper +Copyright (c) 2024 Chris Hooper Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index d0d75e8..bdf1e50 100644 --- a/Makefile +++ b/Makefile @@ -17,9 +17,9 @@ CFLAGS := -Wall -Wno-pointer-sign -Os CFLAGS += -fomit-frame-pointer -#LDFLAGS := -Xlinker -Map=$(PROG).map -noixemul -#LDFLAGS = -Xlinker -Map=$@.map -mcrt=clib2 -lnet -LDFLAGS = -Xlinker -Map=$@.map -Wa,-a > $@.lst -mcrt=clib2 +# clib2 crashes on exit under Kickstart 2.x +#LDFLAGS = -Xlinker -Map=$@.map -Wa,-a > $@.lst -mcrt=clib2 +LDFLAGS = -Xlinker -Map=$@.map -Wa,-a > $@.lst -noixemul #CFLAGS += -g #LDFLAGS += -g diff --git a/README.md b/README.md index ff331b8..2041354 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,44 @@ Amiga 3000 SDMAC and WD33C93A register test utility This is a small utility which can identify the Amiga 3000 SDMAC version -and talk through the SDMAC to read WD33C93A registers. +and talk through the SDMAC to read WD33C93A registers and identify that +chip as well. -Updated so this can be built using the [amigadev/crosstools](https://hub.docker.com/r/amigadev/crosstools) Docker container and VSCode \ No newline at end of file +It can be built using the [amigadev/crosstools](https://hub.docker.com/r/amigadev/crosstools) Docker container and VSCode or can be built using Bebbo's gcc Amiga cross-compiler from Linux. + +------------------------------------------------------- + +Example output + +
+9.OS322:> sdmac
+Memory controller:   Ramsey-07 $f
+Ramsey config:       1Mx4, 238 clock refresh
+SCSI DMA Controller: SDMAC-02
+SCSI Controller:     WD33C93A 00-08 microcode 09
+WDC Configuration:   14.3 MHz, Polled Mode, 245 msec timeout, Async
+
+Ramsey test:  PASS
+SDMAC test:   PASS
+WDC test:     PASS
+
+ +------------------------------------------------------- + +For more information on WD33C93 and compatible chips, including high resolution photos of chip packages, see the following:
+ + http://eebugs.com/scsi/wd33c93/ + + +Actual samples from my part stock +
+                 mcode datecode
+WD33C93    00-02 00    8849 115315200102
+WD33C93A   00-03 00    8909
+WD33C93A   00-04 00    9040 040315200102  9109 041816200102
+WD33C93A   00-06 08    9018 058564200302
+AM33C93A         08    9022 9009 1048EXA A   8950 1608EXA A
+WD33C93A   00-08 09    9209 F 25933A5-3503  9205 F 25890A2-3503
+WD33C93B   00-02 0d    1025 E 2513427-3702
+AIC-33C93B       0d    EBACA724
+
diff --git a/sdmac.c b/sdmac.c index f3af45a..437c98c 100644 --- a/sdmac.c +++ b/sdmac.c @@ -1,7 +1,7 @@ /* - * SDMAC Version 0.6 2024-07-26 + * SDMAC Version 0.7 2024-09-21 * ----------------------------- - * Utility to inspect and test an Amiga 3000's Super DMAC (SDMAC), + * Utility to inspect and test an Amiga 3000's Super DMAC (SDMAC) and * WD SCSI controller for correct configuration and operation. * * Copyright Chris Hooper. This program and source may be used and @@ -14,7 +14,7 @@ * THE AUTHOR ASSUMES NO LIABILITY FOR ANY DAMAGE ARISING OUT OF THE USE * OR MISUSE OF THIS UTILITY OR INFORMATION REPORTED BY THIS UTILITY. */ -const char *version = "\0$VER: SDMAC 0.6 ("__DATE__") © Chris Hooper"; +const char *version = "\0$VER: SDMAC 0.7 ("__DATE__") © Chris Hooper"; #include #include @@ -29,6 +29,7 @@ #include #include #include +#include #define ROM_BASE 0x00f80000 // Kickstart ROM base address @@ -63,39 +64,73 @@ #define RAMSEY_ACR_ALT (RAMSEY_ACR + 0x100) // Shadow of Ramsey ACR #define SDMAC_SSPBDAT_ALT (SDMAC_SSPBDAT + 0x100) // Shadow of SDMAC SSPBDAT -#define WDC_ADDR 0x00 // Write WDC Address Register -#define WDC_OWN_ID 0x00 // R/W Own ID -#define WDC_CONTROL 0x01 // R/W Control -#define WDC_TPERIOD 0x02 // R/W Timeout Period -#define WDC_SECTORS 0x03 // R/W Total Sectors -#define WDC_HEADS 0x04 // R/W Total Heads -#define WDC_CYLS_H 0x05 // R/W Total Cylinders (MSB) -#define WDC_CYLS_L 0x06 // R/W Total Cylinders (LSB) -#define WDC_LADDR3 0x07 // R/W Logical Address (MSB) -#define WDC_LADDR2 0x08 // R/W Logical Address (2nd) -#define WDC_LADDR1 0x09 // R/W Logical Address (3rd) -#define WDC_LADDR0 0x0a // R/W Logical Address (LSB) -#define WDC_SECTOR 0x0b // R/W Sector Number -#define WDC_HEAD 0x0c // R/W Head Number -#define WDC_CYL_H 0x0d // R/W Cylinder Number (MSB) -#define WDC_CYL_L 0x0e // R/W Cylinder Number (LSB) -#define WDC_LUN 0x0f // R/W Target LUN -#define WDC_CMDPHASE 0x10 // R/W Command Phase -#define WDC_SYNC_TX 0x11 // R/W Synchronous Transfer Reg -#define WDC_TCOUNT2 0x12 // R/W Transfer Count (MSB) -#define WDC_TCOUNT1 0x13 // R/W Transfer Count (2nd) -#define WDC_TCOUNT0 0x14 // R/W Transfer Count (LSB) -#define WDC_DST_ID 0x15 // R/W Destination ID -#define WDC_SRC_ID 0x16 // R/W Source ID -#define WDC_SCSI_STAT 0x17 // Read SCSI Status -#define WDC_CMD 0x18 // R/W Command -#define WDC_DATA 0x19 // R/W Data -#define WDC_QUETAG 0x1a // R/W Queue Tag (WD33C93B only) -#define WDC_AUXST 0x1f // Read Auxiliary Status +#define WDC_ADDR 0x00 // Write WDC Address Register +#define WDC_OWN_ID 0x00 // R/W Own ID +#define WDC_CONTROL 0x01 // R/W Control +#define WDC_TPERIOD 0x02 // R/W Timeout Period +#define WDC_CDB1 0x03 // R/W CDB1 start (12 bytes) +#define WDC_SECTORS 0x03 // R/W CDB1, Total Sectors +#define WDC_CDB2 0x04 // R/W CDB2 +#define WDC_HEADS 0x04 // R/W CDB2, Total Heads +#define WDC_CYLS_H 0x05 // R/W CDB3, Total Cylinders (MSB) +#define WDC_CYLS_L 0x06 // R/W CDB4, Total Cylinders (LSB) +#define WDC_LADDR3 0x07 // R/W CDB5, Logical Address (MSB) +#define WDC_LADDR2 0x08 // R/W CDB6, Logical Address (2nd) +#define WDC_LADDR1 0x09 // R/W CDB7, Logical Address (3rd) +#define WDC_LADDR0 0x0a // R/W CDB8, Logical Address (LSB) +#define WDC_SECTOR 0x0b // R/W CDB9, Sector Number +#define WDC_HEAD 0x0c // R/W CDB10, Head Number +#define WDC_CYL_H 0x0d // R/W CDB11, Cylinder Number (MSB) +#define WDC_CYL_L 0x0e // R/W CDB12, Cylinder Number (LSB) +#define WDC_LUN 0x0f // R/W Target LUN +#define WDC_CMDPHASE 0x10 // R/W Command Phase +#define WDC_SYNC_TX 0x11 // R/W Synchronous Transfer Reg +#define WDC_TCOUNT2 0x12 // R/W Transfer Count (MSB) +#define WDC_TCOUNT1 0x13 // R/W Transfer Count (2nd) +#define WDC_TCOUNT0 0x14 // R/W Transfer Count (LSB) +#define WDC_DST_ID 0x15 // R/W Destination ID +#define WDC_SRC_ID 0x16 // R/W Source ID +#define WDC_SCSI_STAT 0x17 // Read SCSI Status +#define WDC_CMD 0x18 // R/W Command +#define WDC_DATA 0x19 // R/W Data +#define WDC_QUETAG 0x1a // R/W Queue Tag (WD33C93B only) +#define WDC_AUXST 0x1f // Read Auxiliary Status #define WDC_INVALID_REG 0x1e // Not a real WD register -#define WDC_CMD_RESET 0x00 // Soft reset the WDC controller +#define WDC_CMD_RESET 0x00 // Soft reset the WDC controller +#define WDC_CMD_ABORT 0x01 // Abort +#define WDC_CMD_DISCONNECT 0x04 // Disconnect +#define WDC_CMD_SELECT_WITH_ATN 0x06 // Select with Attention +#define WDC_CMD_DISCONNECT_MSG 0x04 // Send Disconnect Message +#define WDC_CMD_TRANSFER_INFO 0x20 // Transfer Info +#define WDC_CMD_GET_REGISTER 0x44 // Read register (CDB1) into CDB2 +#define WDC_CMD_SET_REGISTER 0x45 // Write register (CDB1) from CDB2 + +#define WDC_CONTROL_IDI 0x04 // Intermediate Disconnect Interrupt +#define WDC_CONTROL_EDI 0x08 // Ending Disconnect Interrupt + +#define WDC_AUXST_DBR 0x01 // Data Buffer Ready +#define WDC_AUXST_PE 0x02 // Parity Error +#define WDC_AUXST_CIP 0x10 // Command in Progress (interpreting) +#define WDC_AUXST_BSY 0x20 // Busy (Level II command executing) +#define WDC_AUXST_LCI 0x40 // Last Command Ignored +#define WDC_AUXST_INT 0x80 // Interrupt Pending + +#define WDC_SSTAT_SEL_COMPLETE 0x11 // Select complete (initiator) +#define WDC_SSTAT_SEL_TIMEOUT 0x42 // Select timeout + +#define WDC_PHASE_DATA_OUT 0x00 +#define WDC_PHASE_DATA_IN 0x01 +#define WDC_PHASE_CMD 0x02 +#define WDC_PHASE_STATUS 0x03 +#define WDC_PHASE_BUS_FREE 0x04 +#define WDC_PHASE_ARB_SEL 0x05 +#define WDC_PHASE_MESG_OUT 0x06 +#define WDC_PHASE_MESG_IN 0x07 + +#define WDC_DST_ID_DPD 0x40 // Data phase direction is IN from SCSI +#define WDC_DST_ID_SCC 0x80 // Select Command Chain (send disconnect) /* Interrupt status register */ #define SDMAC_ISTR_FIFOE 0x01 // FIFO Empty @@ -126,6 +161,9 @@ /* Synchronous Serial Peripheral Bus Control Register */ +#define SBIC_CLK 14200 // About 14.2 MHz in A3000 +#define SBIC_TIMEOUT(val) ((((val) * (SBIC_CLK)) / 80000) + 1) + #define ADDR8(x) (volatile uint8_t *)(x) #define ADDR16(x) (volatile uint16_t *)(x) #define ADDR32(x) (volatile uint32_t *)(x) @@ -133,8 +171,11 @@ #define ARRAY_SIZE(x) ((sizeof (x) / sizeof ((x)[0]))) #define BIT(x) (1U << (x)) -#define INTERRUPTS_DISABLE() Disable() /* Disable Interrupts */ -#define INTERRUPTS_ENABLE() Enable() /* Enable Interrupts */ +/* These macros support nesting of interrupt disable state */ +#define INTERRUPTS_DISABLE() if (irq_disabled++ == 0) \ + Disable() /* Disable interrupts */ +#define INTERRUPTS_ENABLE() if (--irq_disabled == 0) \ + Enable() /* Enable Interrupts */ #define AMIGA_BERR_DSACK 0x00de0000 // Bit7=1 for BERR on timeout, else DSACK #define BERR_DSACK_SAVE() \ @@ -155,12 +196,21 @@ #define SUPERVISOR_STATE_EXIT() #endif +#define SCSI_TEST_UNIT_READY 0x00 +typedef struct scsi_test_unit_ready { + uint8_t opcode; + uint8_t byte2; + uint8_t reserved[3]; + uint8_t control; +} scsi_test_unit_ready_t; + extern struct ExecBase *SysBase; +struct Device *TimerBase = NULL; typedef unsigned int uint; +static uint8_t irq_disabled = 0; static uint8_t flag_debug = 0; -static uint8_t flag_force_test = 0; static const char *sdmac_fail_reason = ""; #define DUMP_WORDS_AND_LONGS @@ -236,14 +286,14 @@ typedef struct { static const reglist_t sdmac_reglist[] = { { RAMSEY_CTRL, BYTE, RW, "Ramsey_CTRL", "Ramsey Control" }, { RAMSEY_VER, BYTE, RW, "Ramsey_VER", "Ramsey Version" }, - { SDMAC_DAWR, BYTE, WO, "SDMAC_DAWR", "DACK width register (WO)" }, + { SDMAC_DAWR, BYTE, WO, "SDMAC_DAWR", "DACK width (WO)" }, { SDMAC_WTC, LONG, RW, "SDMAC_WTC", "Word Transfer Count" }, { SDMAC_CONTR, BYTE, RW, "SDMAC_CONTR", "Control Register" }, - { RAMSEY_ACR, LONG, RW, "Ramsey_ACR", "DMA Address Register" }, + { RAMSEY_ACR, LONG, RW, "Ramsey_ACR", "DMA Address" }, { SDMAC_ST_DMA, BYTE, WO, "SDMAC_ST_DMA", "Start DMA" }, { SDMAC_FLUSH, BYTE, WO, "SDMAC_FLUSH", "Flush DMA FIFO" }, { SDMAC_CLR_INT, BYTE, WO, "SDMAC_CLR_INT", "Clear Interrupts" }, - { SDMAC_ISTR, BYTE, RO, "SDMAC_ISTR", "Interrupt Status Register" }, + { SDMAC_ISTR, BYTE, RO, "SDMAC_ISTR", "Interrupt Status" }, { SDMAC_REVISION, LONG, RO, "SDMAC_REVISION", "ReSDMAC revision" }, { SDMAC_SP_DMA, BYTE, WO, "SDMAC_SP_DMA", "Stop DMA" }, { SDMAC_SASR_L, LONG, WO, "SDMAC_SASR_L", "WDC register index" }, @@ -254,29 +304,29 @@ static const reglist_t sdmac_reglist[] = { { SDMAC_CI, LONG, RW, "SDMAC_CI", "Coprocessor Interface Register" }, { SDMAC_CIDDR, LONG, RW, "SDMAC_CIDDR", - "Coprocessor Interface Data Direction Register" }, + "Coprocessor Interface Data Direction" }, { SDMAC_SSPBCTL, LONG, RW, "SDMAC_SSPBCTL", - "Synchronous Serial Peripheral Bus Control Register"}, + "Synchronous Serial Peripheral Bus Control"}, { SDMAC_SSPBDAT, LONG, RW, "SDMAC_SSPBDAT", - "Synchronous Serial Peripheral Bus Data Register "}, + "Synchronous Serial Peripheral Bus Data"}, }; static const reglist_t wd_reglist[] = { { WDC_OWN_ID, BYTE, RW, "WDC_OWN_ID", "Own ID" }, { WDC_CONTROL, BYTE, RW, "WDC_CONTROL", "Control" }, { WDC_TPERIOD, BYTE, RW, "WDC_TPERIOD", "Timeout Period" }, - { WDC_SECTORS, BYTE, RW, "WDC_SECTORS", "Total Sectors" }, - { WDC_HEADS, BYTE, RW, "WDC_HEADS", "Total Heads" }, - { WDC_CYLS_H, BYTE, RW, "WDC_CYLS_H", "Total Cylinders MSB" }, - { WDC_CYLS_L, BYTE, RW, "WDC_CYLS_L", "Total Cylinders LSB" }, - { WDC_LADDR3, BYTE, RW, "WDC_LADDR3", "Logical Address MSB" }, - { WDC_LADDR2, BYTE, RW, "WDC_LADDR2", "Logical Address 2nd" }, - { WDC_LADDR1, BYTE, RW, "WDC_LADDR1", "Logical Address 3rd" }, - { WDC_LADDR0, BYTE, RW, "WDC_LADDR0", "Logical Address LSB" }, - { WDC_SECTOR, BYTE, RW, "WDC_SECTOR", "Sector Number" }, - { WDC_HEAD, BYTE, RW, "WDC_HEAD", "Head Number" }, - { WDC_CYL_H, BYTE, RW, "WDC_CYL_H", "Cylinder Number MSB" }, - { WDC_CYL_L, BYTE, RW, "WDC_CYL_L", "Cylinder Number LSB" }, + { WDC_SECTORS, BYTE, RW, "WDC_SECTORS", "CDB1 Total Sectors" }, + { WDC_HEADS, BYTE, RW, "WDC_HEADS", "CDB2 Total Heads" }, + { WDC_CYLS_H, BYTE, RW, "WDC_CYLS_H", "CDB3 Total Cylinders MSB" }, + { WDC_CYLS_L, BYTE, RW, "WDC_CYLS_L", "CDB4 Total Cylinders LSB" }, + { WDC_LADDR3, BYTE, RW, "WDC_LADDR3", "CDB5 Logical Address MSB" }, + { WDC_LADDR2, BYTE, RW, "WDC_LADDR2", "CDB6 Logical Address 2nd" }, + { WDC_LADDR1, BYTE, RW, "WDC_LADDR1", "CDB7 Logical Address 3rd" }, + { WDC_LADDR0, BYTE, RW, "WDC_LADDR0", "CDB8 Logical Address LSB" }, + { WDC_SECTOR, BYTE, RW, "WDC_SECTOR", "CDB9 Sector Number" }, + { WDC_HEAD, BYTE, RW, "WDC_HEAD", "CDB10 Head Number" }, + { WDC_CYL_H, BYTE, RW, "WDC_CYL_H", "CDB11 Cylinder Number MSB" }, + { WDC_CYL_L, BYTE, RW, "WDC_CYL_L", "CDB12 Cylinder Number LSB" }, { WDC_LUN, BYTE, RW, "WDC_LUN", "Target LUN" }, { WDC_CMDPHASE, BYTE, RW, "WDC_CMDPHASE", "Command Phase" }, { WDC_SYNC_TX, BYTE, RW, "WDC_SYNC_TX", "Synchronous Transfer" }, @@ -292,6 +342,36 @@ static const reglist_t wd_reglist[] = { { WDC_AUXST, BYTE, RO, "WDC_AUXST", "Auxiliary Status" }, }; +BOOL __check_abort_enabled = 0; // Disable gcc clib2 ^C break handling +void __chkabort(void) { } // Disable gcc libnix ^C break handling + +/* + * is_user_abort + * ------------- + * Check for user break input (^C) + */ +static BOOL +is_user_abort(void) +{ + if (SetSignal(0, 0) & SIGBREAKF_CTRL_C) + return (1); + return (0); +} + +/* + * WD33C93B extended registers + * + * 0x50 live data pins D0-D7 (00 = none asserted) + * 0x53 live control pins (ff = none asserted) + * bit 0 - IO + * bit 1 - CD + * bit 2 - MSG + * 0x55 state machine + * bit 3 - REQ + * + * Haven't located ATN, BSY, RST, ACK + */ + static void set_wdc_index(uint8_t value) { @@ -330,6 +410,11 @@ get_wdc_reg(uint8_t reg) return (value); } +/* + * set_wdc_reg + * ----------- + * Writes an 8-bit WDC register value. + */ static void set_wdc_reg(uint8_t reg, uint8_t value) { @@ -344,6 +429,55 @@ set_wdc_reg(uint8_t reg, uint8_t value) INTERRUPTS_ENABLE(); } +/* + * set_wdc_reg24 + * ------------- + * Writes a 24-bit WDC register value. + */ +static void +set_wdc_reg24(uint8_t reg, uint value) +{ + uint8_t oindex; + INTERRUPTS_DISABLE(); + oindex = *ADDR8(SDMAC_SASR_B); + set_wdc_index(reg); + + *ADDR8(SDMAC_SCMD) = (uint8_t) (value >> 24); + *ADDR8(SDMAC_SCMD) = (uint8_t) (value >> 18); + *ADDR8(SDMAC_SCMD) = (uint8_t) value; + + set_wdc_index(oindex); + INTERRUPTS_ENABLE(); +} + +static const char * const sdmac_istr_bits[] = { + "FIFO Empty", + "FIFO Full", + "RSVD:Overrun", + "RSVD:Underrun", + "Interrupt Pending", + "DMA Done Interrupt", + "SCSI Interrupt", + "Interrupt Follow", +}; + +static void +decode_sdmac_istr(uint value) +{ + uint bit; + uint printed = 0; + + for (bit = 0; bit < ARRAY_SIZE(sdmac_istr_bits); bit++) { + if (value & BIT(bit)) { + if (printed++) + printf(","); + else + printf(":"); + printf(" %s", sdmac_istr_bits[bit]); + } + } +} + static const char * const scsi_mci_codes[] = { "Data Out", // 000 "Data In", // 001 @@ -366,6 +500,7 @@ static const char * const wdc_cmd_codes[] = { "Select-without-ATN", // 0x07 "Select-with-ATN-and-Transfer", // 0x08 "Select-without-ATN-and-Transfer", // 0x09 + /* read */ "Reselect-and-Receive-Data", // 0x0a "Reselect-and-Send-Data", // 0x0b "Wait-for-Select-and-Receive", // 0x0c @@ -391,6 +526,60 @@ static const char * const wdc_cmd_codes[] = { "Transfer Info", // 0x20 }; +typedef struct { + uint8_t phase; + const char *const name; +} phaselist_t; + +static const phaselist_t wdc_cmd_phases[] = { + { 0x00, "No SCSI bus selected: D" }, + { 0x10, "Target selected: I" }, + { 0x20, "Identify message sent to target" }, + { 0x21, "Tag message code sent to target" }, + { 0x22, "Queue tag sent to target" }, + { 0x30, "Command phase stated, %u bytes transferred" }, + { 0x41, "Save-Data-Pointer message received" }, + { 0x42, "Disconnect received; but not free" }, + { 0x43, "Target disconnected after message: D" }, + { 0x44, "Reselected by target: I" }, + { 0x45, "Received matching Identify from target" }, + { 0x46, "Data transfer completed" }, + { 0x47, "Target in Receive Status phase" }, + { 0x50, "Received Status byte is in LUN register" }, + { 0x60, "Received Command-Complete message" }, + { 0x61, "Linked Command Complete" }, + { 0x70, "Received Identify message" }, + { 0x71, "Received Simple-Queue Tag message" }, +}; + +static const char * const wdc_aux_status_bits[] = { + "Data Buffer Ready", + "Parity Error", + "RSVD2", + "RSVD3", + "Command in Progress", + "Busy", + "Last Command Ignored", + "Interrupt Pending" +}; + +static void +decode_wdc_aux_status(uint8_t stat) +{ + uint bit; + uint printed = 0; + + for (bit = 0; bit < ARRAY_SIZE(wdc_aux_status_bits); bit++) { + if (stat & BIT(bit)) { + if (printed++) + printf(","); + else + printf(":"); + printf(" %s", wdc_aux_status_bits[bit]); + } + } +} + static void decode_wdc_scsi_status(uint8_t statusreg) { @@ -398,44 +587,43 @@ decode_wdc_scsi_status(uint8_t statusreg) uint code = statusreg & 0xf; switch (statusreg >> 4) { case 0: - printf("Reset state, "); + printf("Reset"); switch (code) { case 0: // 0000 - printf("Reset"); break; case 1: // 0001 - printf("Reset with Advanced features"); + printf(" with Advanced features"); break; default: - printf("Unknown code %x", code); + printf(", Unknown code %x", code); break; } break; case 1: - printf("Command complete, "); + printf("Command success, "); switch (code) { case 0: // 0000 - printf("Reselect as target success"); + printf("Reselect as target"); break; case 1: // 0001 - printf("Reselect as initiator success"); + printf("Reselect as initiator"); break; case 3: // 0011 - printf("Success, no ATN"); + printf("no ATN"); break; case 4: // 0100 - printf("Success, ATN"); + printf("ATN"); break; case 5: // 0101 - printf("Translate Address success"); + printf("Translate Address"); break; case 6: // 0110 - printf("Select-and-Transfer success"); + printf("Select-and-Transfer"); break; default: if (code & 0x8) { // 1MCI - printf("Transfer Info succes: %s phase", - scsi_mci_codes[code & 0xf]); + printf("Transfer Info: %s phase", + scsi_mci_codes[code & 0x7]); } else { printf("Unknown code %x", code); } @@ -443,7 +631,7 @@ decode_wdc_scsi_status(uint8_t statusreg) } break; case 2: - printf("Command paused/aborted, "); + printf("Command pause/abort, "); switch (code) { case 0: // 0000 printf("Transfer Info, ACK"); @@ -503,12 +691,12 @@ decode_wdc_scsi_status(uint8_t statusreg) get_wdc_reg(WDC_DST_ID) & 3); break; case 7: // 0111 - printf("Status parity error during Select-and-Transfer"); + printf("Parity error during Select-and-Transfer"); break; default: if (code & 0x8) { // 1MCI printf("Unexpected change requested: %s phase", - scsi_mci_codes[code & 0xf]); + scsi_mci_codes[code & 0x7]); } else { printf("Unknown code %x", code); } @@ -542,7 +730,7 @@ decode_wdc_scsi_status(uint8_t statusreg) default: if (code & 0x8) { // 1MCI printf("REQ during WDC idle initiator: %s phase", - scsi_mci_codes[code & 0xf]); + scsi_mci_codes[code & 0x7]); } else { printf("Unknown code %x", code); } @@ -559,18 +747,82 @@ static void decode_wdc_command(uint8_t lastcmd) { printf(": "); - if ((lastcmd >= ARRAY_SIZE(wdc_cmd_codes)) || - (wdc_cmd_codes[lastcmd] == NULL)) { - printf("Unknown %02x", lastcmd); - } else { + if ((lastcmd < ARRAY_SIZE(wdc_cmd_codes)) && + (wdc_cmd_codes[lastcmd] != NULL)) { printf("%s", wdc_cmd_codes[lastcmd]); + } else if (lastcmd == WDC_CMD_GET_REGISTER) { + printf("Get Register"); + } else if (lastcmd == WDC_CMD_SET_REGISTER) { + printf("Set Register"); + } else { + printf("Unknown %02x", lastcmd); } } static void -show_regs(void) +decode_wdc_cmd_phase(uint8_t phase) { - int pos; + uint pos; + printf(": "); + for (pos = 0; pos < ARRAY_SIZE(wdc_cmd_phases); pos++) { + if ((phase >= 0x30) && (phase < 0x3f) && + (wdc_cmd_phases[pos].phase == 0x30)) { + printf(wdc_cmd_phases[pos].name, phase & 0xf); + } else if (wdc_cmd_phases[pos].phase == phase) { + printf("%s", wdc_cmd_phases[pos].name); + break; + } + } +} + +static void +show_wdc_pos(uint pos, uint value) +{ + printf(" %02x ", wd_reglist[pos].addr & 0xff); + if (value > 0xff) + printf("%.*s", wd_reglist[pos].width * 2, "--------"); + else + printf("%0*x", wd_reglist[pos].width * 2, value); + printf("%*s %-14s %s", + 8 - wd_reglist[pos].width * 2, "", + wd_reglist[pos].name, wd_reglist[pos].desc); + if (value <= 0xff) { + switch (wd_reglist[pos].addr) { + case WDC_CMD: + decode_wdc_command(value); + break; + case WDC_SCSI_STAT: + decode_wdc_scsi_status(value); + break; + case WDC_AUXST: + decode_wdc_aux_status(value); + break; + case WDC_CMDPHASE: + decode_wdc_cmd_phase(value); + break; + } + } + printf("\n"); +} + + +static void +show_wdc_reg(uint addr, uint value) +{ + uint pos; + for (pos = 0; pos < ARRAY_SIZE(wd_reglist); pos++) { + if (wd_reglist[pos].addr == addr) { + show_wdc_pos(pos, value); + return; + } + } + printf(" %02x %02x\n", addr, value); +} + +static void +show_regs(uint extended) +{ + uint pos; uint32_t value; printf("\nREG VALUE NAME DESCRIPTION\n"); for (pos = 0; pos < ARRAY_SIZE(sdmac_reglist); pos++) { @@ -590,32 +842,46 @@ show_regs(void) break; } SUPERVISOR_STATE_EXIT(); // Needed for RAMSEY_VER register - printf(" %02x %0*x%*s %-14s %s\n", + printf(" %02x %0*x%*s %-14s %s", sdmac_reglist[pos].addr & 0xff, sdmac_reglist[pos].width * 2, value, 8 - sdmac_reglist[pos].width * 2, "", sdmac_reglist[pos].name, sdmac_reglist[pos].desc); + switch (sdmac_reglist[pos].addr) { + case SDMAC_ISTR: + decode_sdmac_istr(value); + break; + } + printf("\n"); } printf("REG VALUE NAME DESCRIPTION\n"); for (pos = 0; pos < ARRAY_SIZE(wd_reglist); pos++) { - if (wd_reglist[pos].type == WO) - continue; // Skip this register - value = get_wdc_reg(wd_reglist[pos].addr); - printf(" %02x %0*x%*s %-14s %s", - wd_reglist[pos].addr & 0xff, - wd_reglist[pos].width * 2, value, - 8 - wd_reglist[pos].width * 2, "", - wd_reglist[pos].name, wd_reglist[pos].desc); - switch (wd_reglist[pos].addr) { - case WDC_CMD: - decode_wdc_command(value); - break; - case WDC_SCSI_STAT: - decode_wdc_scsi_status(value); - break; + INTERRUPTS_DISABLE(); + if ((wd_reglist[pos].type == WO) || + (wd_reglist[pos].addr == WDC_DATA)) { + value = 0x100; // Skip this register + } else if ((extended == 0) && + (wd_reglist[pos].addr == WDC_SCSI_STAT) && + (get_wdc_reg(WDC_AUXST) & WDC_AUXST_INT)) { + /* + * Skip reading this register when an interrupt is pending + * because the read has a side-effect of clearing that + * interrupt pending status. + */ + value = 0x100; // Skip this register when interrupt pending + } else { + value = get_wdc_reg(wd_reglist[pos].addr); + } + INTERRUPTS_ENABLE(); + show_wdc_pos(pos, value); + } + if (extended) { + uint addr; + for (addr = 0x1b; addr <= 0x1e; addr++) { + value = get_wdc_reg(addr); + show_wdc_reg(addr, value); } - printf("\n"); } } @@ -665,36 +931,180 @@ cia_spin(unsigned int ticks) } } +static uint +scsi_wait(uint8_t cond, uint wait_for_set) +{ + uint timeout; + uint8_t auxst; + + for (timeout = 25000; timeout > 0; timeout--) { // up to 500ms + cia_spin(10); + auxst = get_wdc_reg(WDC_AUXST); + if (wait_for_set && ((auxst & cond) != 0)) + return (auxst); + else if ((wait_for_set == 0) && ((auxst & cond) == 0)) + return (auxst); + } + return (0x100); // timeout +} + +static uint8_t +scsi_wait_cip(void) +{ + uint8_t auxst; + auxst = scsi_wait(WDC_AUXST_CIP, 0); + if (auxst == 0x100) + printf("WDC timeout CIP\n"); + return (auxst); +} + +/* + * get_wdc_reg_extended + * -------------------- + * Acquires the specified WDC33C93 register. + * Access to to internal registers is supported by way of + * the undocumented register get command. + */ +static uint8_t +get_wdc_reg_extended(uint reg) +{ + uint8_t reg3; + uint8_t reg4; + uint8_t value; + uint8_t scsi_stat; + + if (reg < 0x40) + return (get_wdc_reg(reg)); + + /* This might only work with the WD33C93B parts */ + INTERRUPTS_DISABLE(); + scsi_wait_cip(); + reg3 = get_wdc_reg(WDC_CDB1); + reg4 = get_wdc_reg(WDC_CDB2); + set_wdc_reg(WDC_CDB1, reg); + (void) get_wdc_reg(WDC_SCSI_STAT); + set_wdc_reg(WDC_CMD, WDC_CMD_GET_REGISTER); + scsi_wait_cip(); + scsi_stat = get_wdc_reg(WDC_SCSI_STAT); + + value = get_wdc_reg(WDC_CDB2); + set_wdc_reg(WDC_CDB1, reg3); + set_wdc_reg(WDC_CDB2, reg4); + INTERRUPTS_ENABLE(); + if (scsi_stat != (WDC_CMD_GET_REGISTER | 0x10)) + printf("[fail: %02x]", scsi_stat); + return (value); +} + +/* + * set_wdc_reg_extended + * -------------------- + * Writes an 8-bit WDC register value. + * Access to to internal registers is supported by way of + * the undocumented register set command. + */ static void -wdc_hard_reset(void) +set_wdc_reg_extended(uint reg, uint8_t value) { - uint8_t value = *ADDR8(SDMAC_CNTR); - *ADDR8(SDMAC_CNTR) = 0; // Disable interrupts - cia_spin(CIA_USEC(2)); - *ADDR8(SDMAC_CNTR) = SDMAC_CONTR_RESET; - cia_spin(CIA_USEC(2)); - *ADDR8(SDMAC_CNTR) = 0; - cia_spin(CIA_USEC(2)); - *ADDR8(SDMAC_CNTR) = value; + uint8_t reg3; + uint8_t reg4; + uint8_t scsi_stat; + + if (reg < 0x40) { + set_wdc_reg(reg, value); + return; + } + + /* This might only work with the WD33C93B parts */ + INTERRUPTS_DISABLE(); + scsi_wait_cip(); + reg3 = get_wdc_reg(WDC_CDB1); + reg4 = get_wdc_reg(WDC_CDB2); + set_wdc_reg(WDC_CDB1, reg); + set_wdc_reg(WDC_CDB2, value); + (void) get_wdc_reg(WDC_SCSI_STAT); + set_wdc_reg(WDC_CMD, WDC_CMD_SET_REGISTER); + scsi_wait_cip(); + scsi_stat = get_wdc_reg(WDC_SCSI_STAT); + + set_wdc_reg(WDC_CDB1, reg3); + set_wdc_reg(WDC_CDB2, reg4); + INTERRUPTS_ENABLE(); + if (scsi_stat != (WDC_CMD_SET_REGISTER | 0x10)) + printf("[fail: %02x]", scsi_stat); } + static void -wdc_soft_reset(void) +scsi_hard_reset(void) +{ + uint8_t value; + INTERRUPTS_DISABLE(); + value = *ADDR8(SDMAC_CONTR); + *ADDR8(SDMAC_CONTR) = 0; // Disable interrupts + cia_spin(CIA_USEC(10)); + *ADDR8(SDMAC_CONTR) = SDMAC_CONTR_RESET; + cia_spin(CIA_USEC(10)); + *ADDR8(SDMAC_CONTR) = 0; + cia_spin(CIA_USEC(10)); + *ADDR8(SDMAC_CONTR) = value; + INTERRUPTS_ENABLE(); +} + +static int +scsi_soft_reset(uint enhanced_features) { +#undef RESET_WDC_OWN +#ifdef RESET_WDC_OWN uint8_t wdc_own; +#endif uint8_t wdc_new_own; + uint auxst; + + INTERRUPTS_DISABLE(); wdc_new_own = 0x40 | // Input clock divisor 3 (FS0) for 14 MHz clock - 0x00 | // 0x04 to enable advanced features + 0x00 | // 0x08 to enable advanced features 0x07; // SCSI bus id + if (enhanced_features) + wdc_new_own |= 0x08; // EAF: Enable Advanced Features + if (enhanced_features > 1) + wdc_new_own |= 0x20; // RAF: Really Advanced Features - INTERRUPTS_DISABLE(); + /* Clear previous command status */ + (void) get_wdc_reg(WDC_AUXST); + (void) get_wdc_reg(WDC_SCSI_STAT); + +#ifdef RESET_WDC_OWN wdc_own = get_wdc_reg(WDC_OWN_ID); +#endif set_wdc_reg(WDC_OWN_ID, wdc_new_own); set_wdc_reg(WDC_CMD, WDC_CMD_RESET); - cia_spin(CIA_USEC(30)); + + /* Wait for Command-In-Progress to clear */ + auxst = scsi_wait(WDC_AUXST_CIP, 0); + + /* WD33C93A takes about 8ms to fully complete soft reset */ + if (auxst != 0x100) + auxst = scsi_wait(WDC_AUXST_INT, 1); + +#ifdef RESET_WDC_OWN set_wdc_reg(WDC_OWN_ID, wdc_own); +#endif + set_wdc_reg(WDC_SYNC_TX, 0); + + if (auxst == 0x100) { + if (irq_disabled) + Enable(); + printf("reset timeout\n"); + if (irq_disabled) + Disable(); + } + INTERRUPTS_ENABLE(); + if (auxst == 0x100) + return (1); + return (0); } __attribute__((noinline)) @@ -705,6 +1115,7 @@ get_sdmac_version(void) uint32_t rvalue; uint8_t istr = *ADDR8(SDMAC_ISTR); uint pass; + uint sdmac_version = 2; sdmac_fail_reason = ""; if ((istr & SDMAC_ISTR_FIFOE) && (istr & SDMAC_ISTR_FIFOF)) { @@ -737,14 +1148,13 @@ get_sdmac_version(void) *ADDR32(SDMAC_WTC) = ovalue; INTERRUPTS_ENABLE(); + if (flag_debug) + printf(">> SDMAC_WTC wvalue=%08x rvalue=%08x\n", wvalue, rvalue); + if (rvalue == wvalue) { /* At least some bits of this register are read-only in SDMAC */ if ((wvalue != 0x00000000) && (wvalue != 0xffffffff)) { sdmac_fail_reason = "read-only register bits not read-only"; - if (flag_debug) { - printf(">> SDMAC_WTC wvalue=%02x rvalue=%08x\n", - wvalue, rvalue); - } return (0); } } else if (((rvalue ^ wvalue) & 0x00ffffff) == 0) { @@ -752,22 +1162,16 @@ get_sdmac_version(void) } else if ((rvalue & BIT(2)) == 0) { /* SDMAC-04 possibly */ if (wvalue & BIT(2)) { - /* - * XXX: This should really run through all pattern - * values before determining that it's SDMAC-04. - */ - return (4); // SDMAC-04 BIT(2) is always 0 + /* SDMAC-04 bit 2 is always 0 */ + sdmac_version = 4; } } else { + /* SDMAC-04 bit 2 should never be 1 */ sdmac_fail_reason = "bit corruption in WTC register"; - if (flag_debug) { - printf(">> SDMAC_WTC wvalue=%02x rvalue=%08x\n", - wvalue, rvalue); - } return (0); } } - return (2); // SDMAC-02 WTC bits 0-23 are writable + return (sdmac_version); // SDMAC-02 WTC bits 0-23 are writable } static uint8_t @@ -910,15 +1314,44 @@ show_ramsey_config(void) #define LEVEL_WD33C93 1 #define LEVEL_WD33C93A 2 #define LEVEL_WD33C93B 3 -uint wd_level = LEVEL_WD33C93; -static const uint8_t valid_cmd_phases[] = { - 0x00, 0x10, 0x20, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x50, - 0x60, 0x61, -}; +static uint wd_level = LEVEL_WD33C93; + +static uint8_t wdc_regs_saved = 0; +static uint8_t wdc_regs_store[32]; +#define WDC_SAVE_MASK (WDC_OWN_ID | WDC_CONTROL | WDC_TPERIOD | \ + WDC_CDB1 | WDC_CDB2 | \ + WDC_CMDPHASE | WDC_SYNC_TX | \ + WDC_TCOUNT2 | WDC_TCOUNT1 | WDC_TCOUNT0) + +static void +scsi_save_regs(void) +{ + uint8_t pos; + for (pos = 0; pos < ARRAY_SIZE(wdc_regs_store); pos++) { + if (BIT(pos) & WDC_SAVE_MASK) + wdc_regs_store[pos] = get_wdc_reg(pos); + } + wdc_regs_saved++; +} + +static void +scsi_restore_regs(void) +{ + uint8_t pos; + if (wdc_regs_saved--) { + for (pos = 0; pos < ARRAY_SIZE(wdc_regs_store); pos++) { + if (BIT(pos) & WDC_SAVE_MASK) + set_wdc_reg(pos, wdc_regs_store[pos]); + } + } +} + +#define WD_DETECT_ERR_INVALID 0x0001 +#define WD_DETECT_ERR_AUXST 0x0002 +#define WD_DETECT_ERR_AUXST_WRITABLE 0x0004 +#define WD_DETECT_ERR_SCSI_STAT 0x0008 +#define WD_DETECT_ERR_CMDPHASE 0x0010 +#define WD_DETECT_ERR_RESET_STATUS 0x0020 static uint show_wdc_version(void) @@ -929,6 +1362,7 @@ show_wdc_version(void) uint8_t wvalue; uint pass; uint errs = 0; + uint wd_rev_value = 0; const char *wd_rev = ""; printf("SCSI Controller: "); @@ -936,174 +1370,348 @@ show_wdc_version(void) /* Verify that WD33C93 or WD33C93A can be detected */ rvalue = get_wdc_reg(WDC_INVALID_REG); if (rvalue != 0xff) - errs |= 1; + errs |= WD_DETECT_ERR_INVALID; rvalue = get_wdc_reg(WDC_AUXST); - if (rvalue & (BIT(2) | BIT(3))) - errs |= 2; - rvalue = get_wdc_reg(WDC_LUN); - if (rvalue & (BIT(3) | BIT(4) | BIT(5))) - errs |= 4; - rvalue = get_wdc_reg(WDC_CMDPHASE); - for (pass = 0; pass < ARRAY_SIZE(valid_cmd_phases); pass++) - if (rvalue == valid_cmd_phases[pass]) - break; - if (pass == ARRAY_SIZE(valid_cmd_phases)) - errs |= 8; + if (rvalue & (BIT(2) | BIT(3))) { + if (flag_debug) + printf("Got %02x for AUXST\n", rvalue); + errs |= WD_DETECT_ERR_AUXST; + } + + INTERRUPTS_DISABLE(); + rvalue = get_wdc_reg(WDC_AUXST); + ovalue = get_wdc_reg(WDC_CMDPHASE); + wvalue = 0xa5; + for (pass = 0; pass < 2; pass++) { + uint8_t rvalue2; + set_wdc_reg(WDC_CMDPHASE, wvalue); + rvalue2 = get_wdc_reg(WDC_AUXST); + cvalue = get_wdc_reg(WDC_CMDPHASE); + if (rvalue != rvalue2) + errs |= WD_DETECT_ERR_INVALID; + if (cvalue != wvalue) + errs |= WD_DETECT_ERR_CMDPHASE; + wvalue = 0x5a; + } + set_wdc_reg(WDC_CMDPHASE, ovalue); + INTERRUPTS_ENABLE(); + rvalue = get_wdc_reg(WDC_SCSI_STAT); switch (rvalue >> 4) { case 0: + if ((rvalue & 0xf) > 1) + errs |= WD_DETECT_ERR_SCSI_STAT; + break; case 1: + if ((rvalue & 0xf) == 7) // reserved + errs |= WD_DETECT_ERR_SCSI_STAT; + break; case 2: + if ((rvalue & 0xf) == 6) // reserved + errs |= WD_DETECT_ERR_SCSI_STAT; + break; case 4: + break; case 8: + if ((rvalue & 0xf) == 6) // reserved + errs |= WD_DETECT_ERR_SCSI_STAT; break; default: - errs |= 0x10; + /* Only one status bit may be set */ + errs |= WD_DETECT_ERR_SCSI_STAT; break; } + if ((errs & WD_DETECT_ERR_SCSI_STAT) && flag_debug) + printf("Got %02x for SCSI_STAT\n", rvalue); + + if (errs == 0) { + /* Attempt write of read-only AUXST */ + rvalue = get_wdc_reg(WDC_AUXST); + set_wdc_reg(WDC_AUXST, ~rvalue); + if (get_wdc_reg(WDC_AUXST) != rvalue) { + set_wdc_reg(WDC_AUXST, rvalue); + errs |= WD_DETECT_ERR_AUXST_WRITABLE; + } + } if (errs) { wd_level = LEVEL_UNKNOWN; goto fail; } - wd_level = LEVEL_WD33C93; INTERRUPTS_DISABLE(); - ovalue = get_wdc_reg(WDC_OWN_ID); - if (ovalue & BIT(4)) { // HHP (Halt on Host Parity error) - /* - * Official way to detect the WD33C93A vs the WD33C93: - * - * Enable: - * Bit 3 - EAF Advanced Features - * Bit 5 - EIH Enable Immediate Halt (WD33C93A) - * RAF Really Advanced Features (WD33C93B) - * and from there hit the controller with a reset. - * If the SCSI Status Register has a value of 0x01, then - * the WD33C93B is present. - */ + + /* + * Official way to detect the WD33C93A vs the WD33C93: + * + * Enable in WDC_OWN_ID: + * Bit 3 - EAF Advanced Features + * Bit 5 - EIH Enable Immediate Halt (WD33C93A) + * RAF Really Advanced Features (WD33C93B) + * and from there hit the controller with a reset. + * If the SCSI Status Register has a value of 0x01, then + * the WD33C93A or WD33C93B is present. + */ + scsi_save_regs(); + scsi_soft_reset(1); + rvalue = get_wdc_reg(WDC_SCSI_STAT); + if (rvalue == 0x00) { + /* Does not support advanced features */ + wd_level = LEVEL_WD33C93; + } else if (rvalue == 0x01) { + /* Supports advanced features: A or B part */ wd_level = LEVEL_WD33C93A; } else { - wvalue = ovalue | BIT(4); - set_wdc_reg(WDC_OWN_ID, wvalue); - rvalue = get_wdc_reg(WDC_OWN_ID); - if (rvalue & BIT(4)) { - wd_level = LEVEL_WD33C93A; // Could also be 'B' part - set_wdc_reg(WDC_OWN_ID, ovalue); - } + /* Bad part? */ + wd_level = LEVEL_UNKNOWN; + errs |= WD_DETECT_ERR_RESET_STATUS; + INTERRUPTS_ENABLE(); + goto fail; } - INTERRUPTS_ENABLE(); if (wd_level == LEVEL_WD33C93A) { /* + * Could be WD33C93A or WD33C93B or compatible chip at this point. + * * Try to detect WD33C93B by changing the QUETAG register. If the - * new value sticks, this part must be a WD33C93B. + * new value sticks, this part is a WD33C93B. */ - INTERRUPTS_DISABLE(); cvalue = get_wdc_reg(WDC_CONTROL); ovalue = get_wdc_reg(WDC_QUETAG); - for (pass = 0; pass < 4; pass++) { + for (pass = 4; pass > 0; pass--) { switch (pass) { - case 0: wvalue = 0x00; break; - case 1: wvalue = 0xff; break; + case 4: wvalue = 0x00; break; + case 3: wvalue = 0xff; break; case 2: wvalue = 0xa5; break; - case 3: wvalue = 0x5a; break; + case 1: wvalue = 0x5a; break; } set_wdc_reg(WDC_QUETAG, wvalue); - rvalue = get_wdc_reg(WDC_QUETAG); if (get_wdc_reg(WDC_CONTROL) != cvalue) { break; // Control register should remain the same } + rvalue = get_wdc_reg(WDC_QUETAG); if (rvalue != wvalue) { break; } } set_wdc_reg(WDC_QUETAG, ovalue); - INTERRUPTS_ENABLE(); - if (pass == 4) { + if (pass == 0) { + /* All QUETAG register tests passed */ wd_level = LEVEL_WD33C93B; - wd_rev = ""; } } - if (wd_level == LEVEL_WD33C93A) { + if ((wd_level == LEVEL_WD33C93A) || + (wd_level == LEVEL_WD33C93B)) { /* - * This table is mostly an assumption by me: - * Marking Revision - * WD33C93A 00-01 A Not released? - * WD33C93A 00-02 B Seen in A2091 - * WD33C93A 00-03 C Vesalia has stock, seen on ebay - * WD33C93A 00-04 D Common in A3000, with "PROTO" label - * WD33C93A 00-05 E Not released? - * WD33C93A 00-06 E Seen on ebay - * WD33C93A 00-07 F Not released? - * WD33C93A 00-08 F Final production, same as AM33C93A + * This table is mostly assumptions by me: + * Marking mcode HW Revision + * WD33C93A 00-01 A / Not released? / + * WD33C93A 00-02 B / Not released? / + * WD33C93A 00-03 06 C Seen on ebay and on A2091 + * WD33C93A 00-04 06 D Common in A3000 + * WD33C93A 00-05 07 E / Not released? / + * WD33C93A 00-06 08 E Seen on ebay + * WD33C93A 00-07 08 F / Not released? / + * WD33C93A 00-08 09 F Final production; same as AM33C93A? + * + * Actual samples from my part stock + * mcode datecode + * WD33C93 00-02 00 8849 115315200102 + * WD33C93A 00-03 00 8909 + * WD33C93A 00-04 00 9040 040315200102 9109 041816200102 + * WD33C93A 00-06 08 9018 058564200302 + * AM33C93A 08 9022 9009 1048EXA A 8950 1608EXA A + * WD33C93A 00-08 09 9209 F 25933A5-3503 9205 F 25890A2-3503 + * WD33C93B 00-02 0d 1025 E 2513427-3702 */ /* - * The below tests don't work because the older parts - * still allow all bits in the register to be set. + * The microcode version can be obtained by enabling the + * RAF bit in OWN_ID and then forcing a reset. The value + * is stored in the CDB1 register. */ - wd_rev = "or AM33C93A"; -#if 0 - /* Test for Revision E or Revision F */ - INTERRUPTS_DISABLE(); - ovalue = get_wdc_reg(WDC_DST_ID); - if (ovalue & BIT(5)) { // DF - Data Phase Direction Check Disable - wd_rev = "00-04 or higher or AM33C93A"; - } else { - wvalue = ovalue | BIT(5); - set_wdc_reg(WDC_OWN_ID, wvalue); - rvalue = get_wdc_reg(WDC_OWN_ID); - if (rvalue & BIT(5)) { - wd_rev = "00-04 or higher or AM33C93A"; - } else { - /* - * XXX: Verify that this works to detect 00-02 part - * - * A3000 doesn't boot with 00-02 part installed. - * It just hangs at first SCSI access. - * - * We should also (but don't) fall into this category for - * AM33C93A because the AMD datasheet says Own ID bit 5 is - * "0" not used. The datasheet is wrong. - */ - wd_rev = "00-02 C-D"; - } - set_wdc_reg(WDC_OWN_ID, ovalue); + scsi_soft_reset(2); + wd_rev_value = get_wdc_reg(WDC_CDB1); + } + scsi_soft_reset(0); + scsi_restore_regs(); + + if (wd_level == LEVEL_WD33C93A) { + switch (wd_rev_value) { + case 0x00: + wd_rev = " 00-04 or 00-03"; + break; + case 0x08: + wd_rev = " 00-06 or AM33C93A"; + break; + case 0x09: + wd_rev = " 00-08"; + break; } - INTERRUPTS_ENABLE(); -#endif } + if (wd_level == LEVEL_WD33C93B) { + wd_rev = ""; + } + INTERRUPTS_ENABLE(); fail: switch (wd_level) { case LEVEL_UNKNOWN: printf("Not detected:"); - if (errs & 1) + if (errs & WD_DETECT_ERR_INVALID) printf(" INVALID"); - if (errs & 2) + if (errs & WD_DETECT_ERR_AUXST) printf(" AUXST"); - if (errs & 4) - printf(" LUN"); - if (errs & 8) + if (errs & WD_DETECT_ERR_CMDPHASE) printf(" CMDPHASE"); - if (errs & 0x10) - printf(" STAT"); + if (errs & WD_DETECT_ERR_SCSI_STAT) + printf(" SCSI_STAT"); + if (errs & WD_DETECT_ERR_RESET_STATUS) + printf(" RESET_STATUS"); printf("\n"); break; case LEVEL_WD33C93: printf("WD33C93\n"); break; case LEVEL_WD33C93A: - printf("WD33C93A %s\n", wd_rev); + printf("WD33C93A%s microcode %02x\n", wd_rev, wd_rev_value); break; case LEVEL_WD33C93B: - printf("WD33C93B %s\n", wd_rev); + printf("WD33C93B microcode %02x\n", wd_rev_value); break; } return (errs); } +/* + * calc_wdc_clock + * -------------- + * This function forces a SCSI timeout. When measured, the SCSI input + * clock can be determined based on how long it takes for the SCSI timeout. + * It returns the measured clock speed in KHz. + */ +static uint +calc_wdc_clock(void) +{ + uint16_t ticks; + uint treg; +#undef DEBUG_CALC_WDC_LOCK +#ifdef DEBUG_CALC_WDC_LOCK + uint auxst; +#endif + uint sstat; + uint efreq; + uint count = 200000; + struct EClockVal now; + + INTERRUPTS_DISABLE(); + scsi_wait_cip(); + scsi_save_regs(); + scsi_soft_reset(0); // set the clock divisor to 3 + + sstat = get_wdc_reg(WDC_SCSI_STAT); // clear reset status + if (sstat == 0xff) + return (0); // timeout + + /* Perform a select to ID 7 LUN 7 (should cause timeout) */ + set_wdc_reg(WDC_DST_ID, 7); + set_wdc_reg(WDC_LUN, 7); + set_wdc_reg(WDC_SYNC_TX, 0); // async + set_wdc_reg(WDC_CONTROL, WDC_CONTROL_IDI | WDC_CONTROL_EDI); // No DMA + + /* + * Timeout Register = Tper * Ficlk / 80 + * Ficlk = input clock in MHz + * Tper = timeout period in milliseconds + * + * Reg = Tper * Ficlk / 80 + * Ficlk = Treg * 80 / Tper + * + * efreq = CIA ticks / second + * ticks = CIA ticks per timeout + * Tper = 1000 * ticks / efreq + * + * Ficlk = Treg * 80 / Tper + * Ficlk = Treg * 80 / 1000 / ticks * efreq + * Ficlk = efreq * Treg * 80 / 1000 / ticks + * FiclkKHz = efreq * Treg * 80 / ticks + * + * timeoutspersec = efreq / ticks + * SCSI clk = timeoutspersec * 80 * 100 + */ + treg = 10; // any higher and there is risk of CIA timer rollover + set_wdc_reg(WDC_TPERIOD, treg); + (void) cia_ticks(); + + /* Start select and wait for it to start */ + set_wdc_reg(WDC_CMD, WDC_CMD_SELECT_WITH_ATN); + (void) scsi_wait_cip(); + + /* Wait for select to timeout */ + ticks = cia_ticks(); + while ((*ADDR8(SDMAC_ISTR) & SDMAC_ISTR_INT_S) == 0) + if (--count == 0) + break; // timeout + + ticks -= cia_ticks(); // ticks count down, not up + + /* + * Hack: Compensate for microcode execution runtime + * There are minor variations in command processing time between + * WD33C93A 00-04, 00-06, and 00-08, but they are small enough + * to not affect the reported speed by even 1%. + */ + switch (wd_level) { + default: + case LEVEL_WD33C93: + ticks -= 292; // ~400 usec + break; + case LEVEL_WD33C93A: + ticks -= 380; // ~530 usec + break; + case LEVEL_WD33C93B: + ticks -= 260; // ~360 usec + break; + } + +#ifdef DEBUG_CALC_WDC_LOCK + auxst = scsi_wait(WDC_AUXST_LCI | WDC_AUXST_INT, 1); + sstat = get_wdc_reg(WDC_SCSI_STAT); +#endif + + scsi_soft_reset(0); // set the clock divisor to 3 + scsi_restore_regs(); + INTERRUPTS_ENABLE(); + + if (count == 0) + return (0); + + if (TimerBase == NULL) + TimerBase = (struct Device *) FindName(&SysBase->DeviceList, TIMERNAME); + efreq = ReadEClock(&now); + + /* + * efreq = CIA ticks / second + * ticks = CIA ticks per timeout + * Tper = 1000 * ticks / efreq + * Ficlk = Treg * 80 / Tper + * Ficlk = Treg * 80 / 1000 / ticks * efreq + * Ficlk = efreq * Treg * 80 / 1000 / ticks + * FiclkKHz = efreq * Treg * 80 / ticks + * + * timeoutspersec = efreq / ticks + * SCSI clk = timeoutspersec * 80 * 100 + */ +#ifdef DEBUG_CALC_WDC_LOCK + printf("auxst=%x sstat=%x\n", auxst, sstat); + printf("wdc clock ticks = %u\n", ticks); + printf("Eclk=%u Hz\n", efreq); + printf("timeouts/sec=%u\n", efreq / ticks); + printf("SCSI clk=%u\n", efreq * treg * 80 / ticks); +#endif + return (efreq * treg * 80 / ticks); +} + static uint show_wdc_config(void) { @@ -1117,6 +1725,7 @@ show_wdc_config(void) uint fsel_div = 3; // Assumption good for A3000 only (12-15 MHz) uint sync_tcycles; uint syncoff; + uint wdc_khz = calc_wdc_clock() + 50; if (0) inclk = inclk_pal; @@ -1147,6 +1756,9 @@ show_wdc_config(void) #endif printf("WDC Configuration: "); + if (wdc_khz >= 1000) + printf("%u.%u MHz, ", wdc_khz / 1000, (wdc_khz % 1000) / 100); + switch (control >> 5) { case 0: printf("Polled Mode"); break; case 1: printf("Burst Mode"); break; @@ -1168,16 +1780,21 @@ show_wdc_config(void) if (syncoff > 12) printf(" (BAD)"); - switch ((syncreg >> 4) & 0x7) { - default: - case 0: - case 1: sync_tcycles = 8; break; - case 2: sync_tcycles = 2; break; - case 3: sync_tcycles = 3; break; - case 4: sync_tcycles = 4; break; - case 5: sync_tcycles = 5; break; - case 6: sync_tcycles = 6; break; - case 7: sync_tcycles = 7; break; + if (wd_level == LEVEL_WD33C93) { + sync_tcycles = (syncreg >> 4) & 0x7; + } else { + /* WD33C93A and WD33C93B */ + switch ((syncreg >> 4) & 0x7) { + default: + case 0: + case 1: sync_tcycles = 8; break; + case 2: sync_tcycles = 2; break; + case 3: sync_tcycles = 3; break; + case 4: sync_tcycles = 4; break; + case 5: sync_tcycles = 5; break; + case 6: sync_tcycles = 6; break; + case 7: sync_tcycles = 7; break; + } } if ((wd_level == LEVEL_WD33C93B) && @@ -1195,7 +1812,7 @@ show_wdc_config(void) inclk, freq_mul, fsel_div, sync_tcycles); #endif } - printf("\n"); + printf("\n\n"); return (0); } @@ -1345,6 +1962,7 @@ test_wdc_access(void) /* WDC_LADDR0 should be fully writable */ INTERRUPTS_DISABLE(); + scsi_wait_cip(); covalue = get_wdc_reg(WDC_CONTROL); ovalue = get_wdc_reg(WDC_LADDR0); for (pos = 0; pos < ARRAY_SIZE(test_values); pos++) { @@ -1367,14 +1985,18 @@ test_wdc_access(void) printf("FAIL\n"); printf(" WDC_LADDR0 %02x != expected %02x\n", rvalue, wvalue); INTERRUPTS_DISABLE(); + scsi_wait_cip(); ovalue = get_wdc_reg(WDC_LADDR0); } + if (errs > 6) + break; } set_wdc_reg(WDC_LADDR0, ovalue); INTERRUPTS_ENABLE(); /* WDC_AUXST should be read-only */ INTERRUPTS_DISABLE(); + scsi_wait_cip(); covalue = get_wdc_reg(WDC_CONTROL); ovalue = get_wdc_reg(WDC_AUXST); for (pos = 0; pos < ARRAY_SIZE(test_values); pos++) { @@ -1388,6 +2010,7 @@ test_wdc_access(void) printf("FAIL\n"); printf(" WDC_CONTROL %02x != expected %02x\n", crvalue, covalue); INTERRUPTS_DISABLE(); + scsi_wait_cip(); } rvalue = get_wdc_reg(WDC_AUXST); if (rvalue != ovalue) { @@ -1398,14 +2021,18 @@ test_wdc_access(void) printf(" WDC_AUXST %02x != expected %02x when %02x written\n", rvalue, ovalue, wvalue); INTERRUPTS_DISABLE(); + scsi_wait_cip(); ovalue = get_wdc_reg(WDC_AUXST); } + if (errs > 6) + break; } set_wdc_reg(WDC_AUXST, ovalue); INTERRUPTS_ENABLE(); /* Undefined WDC register should be read-only and always 0xff */ INTERRUPTS_DISABLE(); + scsi_wait_cip(); covalue = get_wdc_reg(WDC_CONTROL); ovalue = get_wdc_reg(WDC_INVALID_REG); for (pos = 0; pos < ARRAY_SIZE(test_values); pos++) { @@ -1419,6 +2046,7 @@ test_wdc_access(void) printf("FAIL\n"); printf(" WDC_CONTROL %02x != expected %02x\n", crvalue, covalue); INTERRUPTS_DISABLE(); + scsi_wait_cip(); } rvalue = get_wdc_reg(WDC_INVALID_REG); if (rvalue != ovalue) { @@ -1429,8 +2057,11 @@ test_wdc_access(void) printf(" WDC Reg 0x1e %02x != expected %02x when %02x written\n", rvalue, ovalue, wvalue); INTERRUPTS_DISABLE(); + scsi_wait_cip(); ovalue = get_wdc_reg(WDC_INVALID_REG); } + if (errs > 6) + break; } set_wdc_reg(WDC_INVALID_REG, ovalue); INTERRUPTS_ENABLE(); @@ -1446,6 +2077,412 @@ test_wdc_access(void) return (errs); } +static void +scsi_set_transfer_len(uint len) +{ + set_wdc_reg24(WDC_TCOUNT2, len); +} + +static uint8_t +scsi_select(uint8_t target) +{ + uint8_t sstat = 0; + uint auxst; +// uint xfer_dir = WDC_DST_ID_DPD; // read from device + uint xfer_dir = 0; // write to device + + set_wdc_reg(WDC_DST_ID, (target & 0xf) | xfer_dir); + set_wdc_reg(WDC_SRC_ID, 0); + set_wdc_reg(WDC_LUN, target >> 8); + set_wdc_reg(WDC_SYNC_TX, 0); // async + + set_wdc_reg(WDC_TPERIOD, SBIC_TIMEOUT(250)); // ~250ms +// scsi_set_transfer_len(6); // WD will get count after select + scsi_set_transfer_len(0); // WD will get count after select + set_wdc_reg(WDC_CMD, WDC_CMD_SELECT_WITH_ATN); + + /* Wait for Command-In-Progress to clear */ + auxst = scsi_wait_cip(); + if (auxst == 0x100) { + printf("SS1 astat=%02x sstat=%02x", + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + return (0xff); + } + + /* Wait for Select to complete */ + auxst = scsi_wait(WDC_AUXST_LCI | WDC_AUXST_INT, 1); + sstat = get_wdc_reg(WDC_SCSI_STAT); + INTERRUPTS_ENABLE(); + if (auxst & 0x100) + printf(" timeout: no LCI or INT\n"); + if (auxst & WDC_AUXST_PE) + printf(" ParityError:%02x\n", sstat); + if (auxst & WDC_AUXST_CIP) + printf(" CIP:%02x\n", sstat); + if (auxst & WDC_AUXST_LCI) + printf(" LCI:%02x\n", sstat); + if (auxst & WDC_AUXST_BSY) + printf(" BSY:%02x\n", sstat); + INTERRUPTS_DISABLE(); + return (sstat); +} + +#if 0 +static void +scsi_disconnect(void) +{ + uint8_t auxst; + + scsi_wait_cip(); + set_wdc_reg(WDC_CMD, WDC_CMD_DISCONNECT); + scsi_wait_cip(); + + auxst = scsi_wait(WDC_AUXST_INT, 1); + if (auxst == 0x100) + printf("WDC timeout disconnect\n"); + printf(" disconnect sstat=%02x\n", get_wdc_reg(WDC_SCSI_STAT)); +} + +static void +scsi_disconnect_msg(void) +{ + set_wdc_reg(WDC_CMD, WDC_CMD_DISCONNECT_MSG); + cia_spin(10000); +} +#endif + +void +scsi_abort(void) +{ + uint auxst; + if (scsi_wait_cip() == 0x100) { + printf("SS1 astat=%02x sstat=%02x", + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + return; + } + set_wdc_reg(WDC_CMD, WDC_CMD_ABORT); + auxst = scsi_wait_cip(); + if (auxst != 0x100) + auxst = scsi_wait(WDC_AUXST_INT, 1); +} + +#undef DEBUG_PROBE_SCSI + +static void +scsi_set_cdb(void *ptr, uint len) +{ + uint pos; + uint8_t *ptr_b = (uint8_t *) ptr; + + if (len >= 12) { + printf("Invalid CDB len %u\n", len); + return; + } + set_wdc_reg(WDC_OWN_ID, len); + for (pos = 0; pos < len; pos++) + set_wdc_reg(WDC_CDB1 + pos, *(ptr_b++)); +} + +static void +scsi_transfer_start(uint phase) +{ + switch (phase) { + case WDC_PHASE_DATA_IN: + case WDC_PHASE_MESG_IN: +#ifdef DEBUG_PROBE_SCSI + printf(" phase in %x\n", phase); +#endif + set_wdc_reg(WDC_DST_ID, + get_wdc_reg(WDC_DST_ID) | WDC_DST_ID_DPD); + break; + case WDC_PHASE_DATA_OUT: + case WDC_PHASE_MESG_OUT: + case WDC_PHASE_CMD: +#ifdef DEBUG_PROBE_SCSI + printf(" phase out %x\n", phase); +#endif + set_wdc_reg(WDC_DST_ID, + get_wdc_reg(WDC_DST_ID) & ~WDC_DST_ID_DPD); + break; + default: + printf("Unknown phase %x\n", phase); + } +} + +static int +scsi_transfer_in(uint phase) +{ + uint timeout = 5000; + uint8_t auxst; + uint len = get_wdc_reg(WDC_OWN_ID); + uint count = 0; + + printf("trans_in\n"); + show_regs(0); + while (count < len) { + timeout = 50000; + auxst = get_wdc_reg(WDC_AUXST); + while ((auxst & WDC_AUXST_DBR) == 0) { + if ((auxst & WDC_AUXST_INT) || (timeout-- == 0)) { + printf(" WDC timeout in transfer_in, %u left: %02x\n", + len - count, auxst); + return (-1); + } + if (auxst & WDC_AUXST_LCI) { + uint8_t sstat = get_wdc_reg(WDC_SCSI_STAT); + printf("LCI sstat=%02x\n", sstat); + return (-1); + } + cia_spin(10); + auxst = get_wdc_reg(WDC_AUXST); + } + count++; + printf(" %02x", get_wdc_reg(WDC_DATA)); // Pull data + } + + printf(" [%02x %02x]\n", len, count); + return (len); +} + +static int +scsi_transfer_out(uint len, void *buf) +{ + uint8_t auxst; + uint8_t *bufptr = (uint8_t *) buf; + uint timeout; + + if (scsi_wait_cip() == 0x100) { + printf("STO1 astat=%02x sstat=%02x", + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + return (-1); + } + + /* + * The below read of AUXST and subsequent read of appears to be + * required. Otherwise, the WDC_CMD_TRANSFER_INFO will sometimes + * be ignored. + */ + auxst = get_wdc_reg(WDC_AUXST); + if (auxst & WDC_AUXST_INT) { + (void) get_wdc_reg(WDC_SCSI_STAT); +#ifdef DEBUG_PROBE_SCSI + printf("t_out auxst=%02x sstat=%02x\n", + auxst, get_wdc_reg(WDC_SCSI_STAT)); +#endif + } + + /* + * The processor either should initialize the Transfer + * Count Register prior to issuing WDC_CMD_TRANSFER_INFO or + * issue the command with the SBT bit in the Command Register + * set. SBT = Single-byte Transfer (one byte is transferred) + */ + set_wdc_reg(WDC_CONTROL, WDC_CONTROL_IDI | WDC_CONTROL_EDI); // polled xfer +// scsi_set_transfer_len(len); // WD will get count after select + set_wdc_reg(WDC_CMD, WDC_CMD_TRANSFER_INFO); + if (scsi_wait_cip() == 0x100) { + printf("STO2 astat=%02x sstat=%02x", + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + return (-1); + } + + while (len > 0) { + timeout = 5000; + auxst = get_wdc_reg(WDC_AUXST); + while ((auxst & WDC_AUXST_DBR) == 0) { + if ((auxst & WDC_AUXST_INT) || (timeout-- == 0)) { +#ifdef DEBUG_PROBE_SCSI + printf(" WDC timeout in transfer_out, %u left: %02x\n", + len, auxst); +#endif + if (auxst & WDC_AUXST_LCI) { + uint8_t sstat = get_wdc_reg(WDC_SCSI_STAT); + printf("LCI sstat=%02x\n", sstat); + return (-1); + } + return (len); + } + cia_spin(10); + auxst = get_wdc_reg(WDC_AUXST); + } + set_wdc_reg(WDC_DATA, *(bufptr++)); // Push data + len--; + } + return (len); +} + +static int +probe_scsi(void) +{ + int found = 0; + uint target; + uint8_t auxst; + uint8_t sstat; + uint8_t sstat2; + uint8_t sdmac_contr; + + INTERRUPTS_DISABLE(); + sdmac_contr = *ADDR8(SDMAC_CONTR); + *ADDR8(SDMAC_CONTR) = 0; // Disable interrupts + INTERRUPTS_ENABLE(); + + scsi_soft_reset(0); + (void) get_wdc_reg(WDC_SCSI_STAT); // clear reset status + + scsi_test_unit_ready_t tur; + memset(&tur, 0, sizeof (tur)); + tur.opcode = SCSI_TEST_UNIT_READY; + + INTERRUPTS_DISABLE(); + for (target = 0; target < 7; target++) { + uint8_t phase; + uint cmdlen; + uint present = 0; + + if (scsi_wait_cip() == 0x100) { + printf("CIP%d astat=%02x sstat=%02x", 0, + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + found = -1; + break; + } + sstat = get_wdc_reg(WDC_SCSI_STAT); + if (sstat & 0xe0) { + printf("odd sstat %02x\n", sstat); + found = -1; + break; + } + /* Disable WDC DMA mode */ + set_wdc_reg(WDC_CONTROL, WDC_CONTROL_IDI | WDC_CONTROL_EDI); + + cmdlen = sizeof (tur); + scsi_set_cdb(&tur, cmdlen); + scsi_set_transfer_len(0); + sstat = scsi_select(target); + sstat2 = get_wdc_reg(WDC_SCSI_STAT); + + set_wdc_reg(WDC_SRC_ID, 0); // Disable reselection + + if (sstat == WDC_SSTAT_SEL_COMPLETE) { + uint timeout = 10; + while (sstat2 == sstat) { + cia_spin(10); + sstat2 = get_wdc_reg(WDC_SCSI_STAT); + if (--timeout == 0) + break; + } + if (timeout == 0) { + printf("timeout: sstat=%02x sstat2=%02x\n", sstat, sstat2); + found = -1; + break; + } + phase = sstat2 & 0x07; // phase bits of status + present = 1; + found++; +#ifdef DEBUG_PROBE_SCSI + printf(" sstat2=%02x astat=%02x\n", + sstat2, get_wdc_reg(WDC_AUXST)); +#endif + if (scsi_wait_cip() == 0x100) { + printf("CIP%d astat=%02x sstat=%02x", 1, + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + found = -1; + break; + } + + /* Set up transfer */ + set_wdc_reg(WDC_DST_ID, target & 0x7); + scsi_transfer_start(phase); + + /* Delay and then check for interrupt */ + if (scsi_wait_cip() == 0x100) { + printf("CIP%d astat=%02x sstat=%02x", 2, + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + found = -1; + break; + } + (void) get_wdc_reg(WDC_AUXST); + (void) get_wdc_reg(WDC_AUXST); + (void) get_wdc_reg(WDC_AUXST); + auxst = get_wdc_reg(WDC_AUXST); + if (auxst & WDC_AUXST_INT) { + /* Clear pending interrupt */ + (void) get_wdc_reg(WDC_SCSI_STAT); + } + if (auxst & WDC_AUXST_LCI) { + printf("LCI during SCSI transfter start: %02x\n", + get_wdc_reg(WDC_SCSI_STAT)); + found = -1; + break; + } + if ((phase == WDC_PHASE_DATA_IN) || (phase == WDC_PHASE_MESG_IN)) { + if (scsi_transfer_in(phase) <= 0) { + printf("phase=%02x sstat=%02x sstat2=%02x\n", + phase, sstat, sstat2); + found = -1; + break; + } + } else if (scsi_transfer_out(cmdlen, &tur) <= 0) { + found = -1; + break; + } + set_wdc_reg(WDC_CONTROL, WDC_CONTROL_IDI | WDC_CONTROL_EDI); + scsi_abort(); + } else if (sstat == WDC_SSTAT_SEL_TIMEOUT) { + present = 0; + } else { + printf(" Unexpected SCSI STAT sstat=%02x %02x\n", sstat, sstat2); + found = -1; + break; + } + + if (scsi_wait_cip() == 0x100) { + printf("CIP%d astat=%02x sstat=%02x", 3, + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + found = -1; + break; + } + scsi_soft_reset(0); + if (scsi_wait_cip() == 0x100) { + printf("CIP%d astat=%02x sstat=%02x", 4, + get_wdc_reg(WDC_AUXST), get_wdc_reg(WDC_SCSI_STAT)); + found = -1; + break; + } + (void) get_wdc_reg(WDC_SCSI_STAT); // clear reset status + + *ADDR8(SDMAC_CLR_INT) = 0; // Clear pending interrupts + + if (is_user_abort()) { + printf("^C Abort\n"); + found = -1; + break; + } + INTERRUPTS_ENABLE(); +#ifdef DEBUG_PROBE_SCSI + printf("%u %s sstat=%02x phase=%x\n", + target, present ? "present" : "no response", sstat, phase); +#else + printf(" %u %s\n", target, present ? "present" : "no response"); +#endif + INTERRUPTS_DISABLE(); + } +#ifdef DEBUG_PROBE_SCSI + printf("auxst=%02x\n", get_wdc_reg(WDC_AUXST)); + printf("sstat=%02x\n", get_wdc_reg(WDC_SCSI_STAT)); + printf("istr=%02x\n", *ADDR8(SDMAC_ISTR)); +#endif + *ADDR8(SDMAC_CLR_INT) = 0; // Clear interrupt + *ADDR8(SDMAC_CONTR) = sdmac_contr; // Restore interrupts + INTERRUPTS_ENABLE(); + if (found == 0) { + printf("No device found\n"); + return (1); + } + if (found == -1) + return (1); + return (0); +} + int main(int argc, char **argv) { @@ -1453,7 +2490,12 @@ main(int argc, char **argv) int raw_sdmac_regs = 0; int all_regs = 0; int loop_until_failure = 0; + int readwrite_wdc_reg = 0; + int probe_scsi_bus = 0; + int flag_show = 0; + int flag_force_test = 0; int arg; + uint pass = 0; for (arg = 1; arg < argc; arg++) { char *ptr = argv[arg]; @@ -1466,9 +2508,46 @@ main(int argc, char **argv) case 'L': loop_until_failure++; break; - case 'r': - all_regs++; + case 'p': + probe_scsi_bus++; + break; + case 'r': { + int pos = 0; + uint addr; + uint val; + char *arg1 = argv[arg + 1]; + char *arg2 = argv[arg + 2]; + if ((argc <= arg + 1) || (*arg1 == '-')) { + /* Display all registers */ + all_regs++; + break; + } + if ((sscanf(arg1, "%x%n", &addr, &pos) != 1) || + (arg1[pos] != '\0') || (addr > 0xff)) { + printf("Invalid address %s for -%s\n", arg1, ptr); + exit(1); + } + if ((argc <= arg + 2) || (*arg2 == '-')) { + /* read */ + val = get_wdc_reg_extended(addr); + arg++; + readwrite_wdc_reg++; + show_wdc_reg(addr, val); + break; + } + if ((sscanf(arg2, "%x%n", &val, &pos) != 1) || + (arg2[pos] != '\0') || (val > 0xff)) { + printf("Invalid data %s for -%s\n", arg2, ptr); + exit(1); + } + + /* write */ + set_wdc_reg_extended(addr, val); + arg += 2; + readwrite_wdc_reg++; + show_wdc_reg(addr, val); break; + } case 'R': do_wdc_reset++; break; @@ -1490,52 +2569,79 @@ main(int argc, char **argv) printf("%s\nOptions:\n" " -d Debug output\n" " -L Loop tests until failure\n" + " -p probe SCSI bus (not well-tested)\n" " -R reset WD SCSI Controller\n" - " -r Display registers\n" + " -r [ []] Display/change WDC registers\n" " -s Display raw SDMAC registers\n" " -t Force tests to run\n" " -v Display program version\n", version + 7); exit(1); } } - BERR_DSACK_SAVE(); - if (do_wdc_reset) { - if (do_wdc_reset > 1) { - wdc_hard_reset(); - } else { - wdc_soft_reset(); - } - printf("WDC %s reset complete\n", (do_wdc_reset > 1) ? "hard" : "soft"); + if (all_regs) + goto finish; // Do not probe or perform tests + if (readwrite_wdc_reg) goto finish; + + if ((probe_scsi_bus == 0) && + (do_wdc_reset == 0) && + (raw_sdmac_regs == 0) && + (flag_force_test == 0)) { + flag_force_test++; + flag_show++; } - if (show_ramsey_version() || - show_ramsey_config() || - show_dmac_version() || - show_wdc_version() || - show_wdc_config()) { + + if (flag_show && + (show_ramsey_version() || + show_ramsey_config() || + show_dmac_version() || + show_wdc_version() || + show_wdc_config())) { if (flag_force_test == 0) goto finish; } - printf("\n"); do { - if (test_ramsey_access() + - test_sdmac_access() + - test_wdc_access() > 0) { + pass++; + if (flag_force_test && + (test_ramsey_access() + + test_sdmac_access() + + test_wdc_access() > 0)) { + break; + } + if (probe_scsi_bus && + probe_scsi()) { + break; + } + if (do_wdc_reset) { + const char *mode; + if (do_wdc_reset > 3) { + scsi_hard_reset(); + mode = "hard"; + } else { + scsi_soft_reset(do_wdc_reset); + mode = "soft"; + } + printf("WDC %s reset complete\n", mode); + } + if (is_user_abort()) { + printf("^C Abort\n"); break; } } while (loop_until_failure); finish: if (all_regs) { - show_regs(); + show_regs(all_regs > 1); } if (raw_sdmac_regs) { get_raw_regs(); dump_raw_sdmac_regs(); } BERR_DSACK_RESTORE(); + if (loop_until_failure) + printf("%s at pass %u\n", is_user_abort() ? "Stopped" : "Failed", pass); exit(0); }