| 1 | // |
| 2 | // Memory Track cartridge emulation |
| 3 | // |
| 4 | // by James Hammons |
| 5 | // (C) 2016 Underground Software |
| 6 | // |
| 7 | // The Memory Track is just a large(-ish) EEPROM, holding 128K. We emulate the |
| 8 | // Atmel part, since it seems to be easier to deal with than the AMD part. The |
| 9 | // way it works is the 68K checks in its R/W functions to see if the MT is |
| 10 | // inserted, and, if so, call the R/W functions here. It also checks to see if |
| 11 | // the ROM width was changed to 32-bit; if not, then it reads the normal ROM in |
| 12 | // the ROM space like usual. |
| 13 | // |
| 14 | // The Atmel part reads/writes a single byte into a long space. So we have to |
| 15 | // adjust for that when reading from/writing to the NVRAM. |
| 16 | // |
| 17 | // JLH = James Hammons <jlhamm@acm.org> |
| 18 | // JPM = Jean-Paul Mari <djipi.mari@gmail.com> |
| 19 | // |
| 20 | // Who When What |
| 21 | // --- ---------- ----------------------------------------------------------- |
| 22 | // JLH 06/12/2016 Created this file ;-) |
| 23 | // JPM 06/06/2016 Visual Studio support |
| 24 | // |
| 25 | |
| 26 | #include "memtrack.h" |
| 27 | |
| 28 | #include <stdlib.h> |
| 29 | #include <string.h> |
| 30 | #include "log.h" // JPM: changed <log.h> to "log.h" |
| 31 | #include "settings.h" // JPM: changed <settings.h> to "settings.h" |
| 32 | |
| 33 | |
| 34 | #define MEMTRACK_FILENAME "memtrack.eeprom" |
| 35 | //#define DEBUG_MEMTRACK |
| 36 | |
| 37 | enum { MT_NONE, MT_PROD_ID, MT_RESET, MT_WRITE_ENABLE }; |
| 38 | enum { MT_IDLE, MT_PHASE1, MT_PHASE2 }; |
| 39 | |
| 40 | uint8_t mtMem[0x20000]; |
| 41 | uint8_t mtCommand = MT_NONE; |
| 42 | uint8_t mtState = MT_IDLE; |
| 43 | bool haveMT = false; |
| 44 | char mtFilename[MAX_PATH]; |
| 45 | |
| 46 | // Private function prototypes |
| 47 | void MTWriteFile(void); |
| 48 | void MTStateMachine(uint8_t reg, uint16_t data); |
| 49 | |
| 50 | |
| 51 | void MTInit(void) |
| 52 | { |
| 53 | sprintf(mtFilename, "%s%s", vjs.EEPROMPath, MEMTRACK_FILENAME); |
| 54 | FILE * fp = fopen(mtFilename, "rb"); |
| 55 | |
| 56 | if (fp) |
| 57 | { |
| 58 | size_t ignored = fread(mtMem, 1, 0x20000, fp); |
| 59 | fclose(fp); |
| 60 | WriteLog("MT: Loaded NVRAM from %s\n", mtFilename); |
| 61 | haveMT = true; |
| 62 | } |
| 63 | else |
| 64 | WriteLog("MT: Could not open file \"%s\"!\n", mtFilename); |
| 65 | } |
| 66 | |
| 67 | |
| 68 | void MTReset(void) |
| 69 | { |
| 70 | if (!haveMT) |
| 71 | memset(mtMem, 0xFF, 0x20000); |
| 72 | } |
| 73 | |
| 74 | |
| 75 | void MTDone(void) |
| 76 | { |
| 77 | MTWriteFile(); |
| 78 | WriteLog("MT: Done.\n"); |
| 79 | } |
| 80 | |
| 81 | |
| 82 | void MTWriteFile(void) |
| 83 | { |
| 84 | if (!haveMT) |
| 85 | return; |
| 86 | |
| 87 | FILE * fp = fopen(mtFilename, "wb"); |
| 88 | |
| 89 | if (fp) |
| 90 | { |
| 91 | fwrite(mtMem, 1, 0x20000, fp); |
| 92 | fclose(fp); |
| 93 | } |
| 94 | else |
| 95 | WriteLog("MT: Could not create file \"%s\"!", mtFilename); |
| 96 | } |
| 97 | |
| 98 | |
| 99 | // |
| 100 | // This is crappy, there doesn't seem to be a word interface to the NVRAM. But |
| 101 | // we'll keep this as a placeholder for now. |
| 102 | // |
| 103 | uint16_t MTReadWord(uint32_t addr) |
| 104 | { |
| 105 | uint32_t value = MTReadLong(addr); |
| 106 | |
| 107 | if ((addr & 0x03) == 0) |
| 108 | value >>= 16; |
| 109 | else if ((addr & 0x03) == 2) |
| 110 | value &= 0xFFFF; |
| 111 | |
| 112 | #ifdef DEBUG_MEMTRACK |
| 113 | WriteLog("MT: Reading word @ $%06X: $%04X\n", addr, value); |
| 114 | #endif |
| 115 | |
| 116 | return (uint16_t)value; |
| 117 | } |
| 118 | |
| 119 | |
| 120 | uint32_t MTReadLong(uint32_t addr) |
| 121 | { |
| 122 | uint32_t value = 0; |
| 123 | |
| 124 | if (mtCommand == MT_PROD_ID) |
| 125 | { |
| 126 | if (addr == 0x800000) |
| 127 | value = 0x1F; |
| 128 | else if (addr == 0x800004) |
| 129 | value = 0xD5; |
| 130 | } |
| 131 | else |
| 132 | { |
| 133 | value = (uint32_t)mtMem[(addr & 0x7FFFC) >> 2]; |
| 134 | } |
| 135 | |
| 136 | // We do this because we're not sure how the real thing behaves; but it |
| 137 | // seems reasonable on its face to do it this way. :-P So we turn off write |
| 138 | // mode when reading the NVRAM. |
| 139 | if (mtCommand == MT_WRITE_ENABLE) |
| 140 | mtCommand = MT_NONE; |
| 141 | |
| 142 | #ifdef DEBUG_MEMTRACK |
| 143 | WriteLog("MT: Reading long @ $%06X: $%08X\n", addr, value << 16); |
| 144 | #endif |
| 145 | return value << 16; |
| 146 | } |
| 147 | |
| 148 | |
| 149 | void MTWriteWord(uint32_t addr, uint16_t data) |
| 150 | { |
| 151 | // We don't care about writes to long offsets + 2 |
| 152 | if ((addr & 0x3) == 2) |
| 153 | return; |
| 154 | |
| 155 | #ifdef DEBUG_MEMTRACK |
| 156 | WriteLog("MT: Writing word @ $%06X: $%04X (Writing is %sabled)\n", addr, data, (mtCommand == MT_WRITE_ENABLE ? "en" : "dis")); |
| 157 | #endif |
| 158 | |
| 159 | // Write to the NVRAM if it's enabled... |
| 160 | if (mtCommand == MT_WRITE_ENABLE) |
| 161 | { |
| 162 | mtMem[(addr & 0x7FFFC) >> 2] = (uint8_t)(data & 0xFF); |
| 163 | return; |
| 164 | } |
| 165 | |
| 166 | switch (addr) |
| 167 | { |
| 168 | case (0x800000 + (4 * 0x5555)): // $815554 |
| 169 | MTStateMachine(0, data); |
| 170 | break; |
| 171 | case (0x800000 + (4 * 0x2AAA)): // $80AAA8 |
| 172 | MTStateMachine(1, data); |
| 173 | break; |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | |
| 178 | void MTWriteLong(uint32_t addr, uint32_t data) |
| 179 | { |
| 180 | // Strip off lower 3 bits of the passed in address |
| 181 | addr &= 0xFFFFFC; |
| 182 | |
| 183 | MTWriteWord(addr + 0, data & 0xFFFF); |
| 184 | MTWriteWord(addr + 2, data >> 16); |
| 185 | } |
| 186 | |
| 187 | |
| 188 | void MTStateMachine(uint8_t reg, uint16_t data) |
| 189 | { |
| 190 | #ifdef DEBUG_MEMTRACK |
| 191 | WriteLog("MTStateMachine: reg = %u, data = $%02X, current state = %u\n", reg, data, mtState); |
| 192 | #endif |
| 193 | switch (mtState) |
| 194 | { |
| 195 | case MT_IDLE: |
| 196 | if ((reg == 0) && (data == 0xAA)) |
| 197 | mtState = MT_PHASE1; |
| 198 | |
| 199 | break; |
| 200 | case MT_PHASE1: |
| 201 | if ((reg == 1) && (data == 0x55)) |
| 202 | mtState = MT_PHASE2; |
| 203 | else |
| 204 | mtState = MT_IDLE; |
| 205 | |
| 206 | break; |
| 207 | case MT_PHASE2: |
| 208 | if (reg == 0) |
| 209 | { |
| 210 | if (data == 0x90) // Product ID |
| 211 | mtCommand = MT_PROD_ID; |
| 212 | else if (data == 0xF0) // Reset |
| 213 | mtCommand = MT_NONE; |
| 214 | else if (data == 0xA0) // Write enagle |
| 215 | mtCommand = MT_WRITE_ENABLE; |
| 216 | else |
| 217 | mtCommand = MT_NONE; |
| 218 | } |
| 219 | |
| 220 | mtState = MT_IDLE; |
| 221 | break; |
| 222 | } |
| 223 | #ifdef DEBUG_MEMTRACK |
| 224 | WriteLog(" state = %u, cmd = %u\n", mtState, mtCommand); |
| 225 | #endif |
| 226 | } |
| 227 | |