all repos — mgba @ bdf8f73ba57d1d4738023bde436c38e9948f37ed

mGBA Game Boy Advance Emulator

src/ds/io.c (view raw)

  1/* Copyright (c) 2013-2016 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include <mgba/internal/ds/io.h>
  7
  8#include <mgba/core/interface.h>
  9#include <mgba/internal/ds/ds.h>
 10#include <mgba/internal/ds/ipc.h>
 11#include <mgba/internal/ds/slot1.h>
 12#include <mgba/internal/ds/spi.h>
 13
 14mLOG_DEFINE_CATEGORY(DS_IO, "DS I/O");
 15
 16static void _DSHaltCNT(struct DSCommon* dscore, uint8_t value) {
 17	switch (value >> 6) {
 18	case 0:
 19	default:
 20		break;
 21	case 1:
 22		mLOG(DS_IO, STUB, "Enter GBA mode not supported");
 23		break;
 24	case 2:
 25		ARMHalt(dscore->cpu);
 26		break;
 27	case 3:
 28		mLOG(DS_IO, STUB, "Enter sleep mode not supported");
 29		break;
 30	}
 31}
 32
 33static uint16_t _scheduleDiv(struct DS* ds, uint16_t control) {
 34	mTimingDeschedule(&ds->ds9.timing, &ds->divEvent);
 35	mTimingSchedule(&ds->ds9.timing, &ds->divEvent, (control & 3) ? 36 : 68);
 36	return control | 0x8000;
 37}
 38
 39static uint16_t _scheduleSqrt(struct DS* ds, uint16_t control) {
 40	mTimingDeschedule(&ds->ds9.timing, &ds->sqrtEvent);
 41	mTimingSchedule(&ds->ds9.timing, &ds->sqrtEvent, 26);
 42	return control | 0x8000;
 43}
 44
 45static uint32_t DSIOWrite(struct DSCommon* dscore, uint32_t address, uint16_t value) {
 46	switch (address) {
 47	// Video
 48	case DS_REG_DISPSTAT:
 49		DSVideoWriteDISPSTAT(dscore, value);
 50		break;
 51
 52	// DMA Fill
 53	case DS_REG_DMA0FILL_LO:
 54	case DS_REG_DMA0FILL_HI:
 55	case DS_REG_DMA1FILL_LO:
 56	case DS_REG_DMA1FILL_HI:
 57	case DS_REG_DMA2FILL_LO:
 58	case DS_REG_DMA2FILL_HI:
 59	case DS_REG_DMA3FILL_LO:
 60	case DS_REG_DMA3FILL_HI:
 61		break;
 62
 63	// Timers
 64	case DS_REG_TM0CNT_LO:
 65		GBATimerWriteTMCNT_LO(&dscore->timers[0], value);
 66		return 0x20000;
 67	case DS_REG_TM1CNT_LO:
 68		GBATimerWriteTMCNT_LO(&dscore->timers[1], value);
 69		return 0x20000;
 70	case DS_REG_TM2CNT_LO:
 71		GBATimerWriteTMCNT_LO(&dscore->timers[2], value);
 72		return 0x20000;
 73	case DS_REG_TM3CNT_LO:
 74		GBATimerWriteTMCNT_LO(&dscore->timers[3], value);
 75		return 0x20000;
 76
 77	case DS_REG_TM0CNT_HI:
 78		value &= 0x00C7;
 79		DSTimerWriteTMCNT_HI(&dscore->timers[0], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], value);
 80		break;
 81	case DS_REG_TM1CNT_HI:
 82		value &= 0x00C7;
 83		DSTimerWriteTMCNT_HI(&dscore->timers[1], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], value);
 84		break;
 85	case DS_REG_TM2CNT_HI:
 86		value &= 0x00C7;
 87		DSTimerWriteTMCNT_HI(&dscore->timers[2], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], value);
 88		break;
 89	case DS_REG_TM3CNT_HI:
 90		value &= 0x00C7;
 91		DSTimerWriteTMCNT_HI(&dscore->timers[3], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], value);
 92		break;
 93
 94	// IPC
 95	case DS_REG_IPCSYNC:
 96		value &= 0x6F00;
 97		value |= dscore->memory.io[address >> 1] & 0x000F;
 98		DSIPCWriteSYNC(dscore->ipc->cpu, dscore->ipc->memory.io, value);
 99		break;
100	case DS_REG_IPCFIFOCNT:
101		value = DSIPCWriteFIFOCNT(dscore, value);
102		break;
103
104	// Cart bus
105	case DS_REG_AUXSPICNT:
106		if (dscore->memory.slot1Access) {
107			value = DSSlot1Configure(dscore->p, value);
108			dscore->ipc->memory.io[address >> 1] = value;
109		} else {
110			mLOG(DS_IO, GAME_ERROR, "Invalid cart access");
111			return 0;
112		}
113		break;
114	case DS_REG_ROMCNT_HI:
115		if (dscore->memory.slot1Access) {
116			DSSlot1ROMCNT cnt = value << 16;
117			cnt |= dscore->memory.io[(address - 2) >> 1];
118			cnt = DSSlot1Control(dscore->p, cnt);
119			value = cnt >> 16;
120			dscore->ipc->memory.io[address >> 1] = value;
121		} else {
122			mLOG(DS_IO, GAME_ERROR, "Invalid cart access");
123			return 0;
124		}
125		break;
126	case DS_REG_ROMCNT_LO:
127	case DS_REG_ROMCMD_0:
128	case DS_REG_ROMCMD_2:
129	case DS_REG_ROMCMD_4:
130	case DS_REG_ROMCMD_6:
131		if (dscore->memory.slot1Access) {
132			dscore->ipc->memory.io[address >> 1] = value;
133		} else {
134			mLOG(DS_IO, GAME_ERROR, "Invalid cart access");
135			return 0;
136		}
137		break;
138
139	// Interrupts
140	case DS_REG_IME:
141		DSWriteIME(dscore->cpu, dscore->memory.io, value);
142		break;
143	case 0x20A:
144		value = 0;
145		// Some bad interrupt libraries will write to this
146		break;
147	case DS_REG_IF_LO:
148	case DS_REG_IF_HI:
149		value = dscore->memory.io[address >> 1] & ~value;
150		break;
151	default:
152		return 0;
153	}
154	return value | 0x10000;
155}
156
157uint32_t DSIOWrite32(struct DSCommon* dscore, uint32_t address, uint32_t value) {
158	switch (address) {
159	case DS_REG_DMA0SAD_LO:
160		value = DSDMAWriteSAD(dscore, 0, value);
161		break;
162	case DS_REG_DMA1SAD_LO:
163		value = DSDMAWriteSAD(dscore, 1, value);
164		break;
165	case DS_REG_DMA2SAD_LO:
166		value = DSDMAWriteSAD(dscore, 2, value);
167		break;
168	case DS_REG_DMA3SAD_LO:
169		value = DSDMAWriteSAD(dscore, 3, value);
170		break;
171
172	case DS_REG_DMA0DAD_LO:
173		value = DSDMAWriteDAD(dscore, 0, value);
174		break;
175	case DS_REG_DMA1DAD_LO:
176		value = DSDMAWriteDAD(dscore, 1, value);
177		break;
178	case DS_REG_DMA2DAD_LO:
179		value = DSDMAWriteDAD(dscore, 2, value);
180		break;
181	case DS_REG_DMA3DAD_LO:
182		value = DSDMAWriteDAD(dscore, 3, value);
183		break;
184
185	case DS_REG_IPCFIFOSEND_LO:
186		DSIPCWriteFIFO(dscore, value);
187		break;
188	case DS_REG_IE_LO:
189		DSWriteIE(dscore->cpu, dscore->memory.io, value);
190		break;
191	}
192
193	return value;
194}
195
196static uint16_t DSIOReadExKeyInput(struct DS* ds) {
197	uint16_t input = 0;
198	if (ds->keyCallback) {
199		input = ds->keyCallback->readKeys(ds->keyCallback);
200	} else if (ds->keySource) {
201		input = *ds->keySource;
202	}
203	input = ~(input >> 10) & 0x3;
204	input |= 0x3C;
205	return input;
206}
207
208static uint16_t DSIOReadKeyInput(struct DS* ds) {
209	uint16_t input = 0;
210	if (ds->keyCallback) {
211		input = ds->keyCallback->readKeys(ds->keyCallback);
212	} else if (ds->keySource) {
213		input = *ds->keySource;
214	}
215	// TODO: Put back
216	/*if (!dscore->p->allowOpposingDirections) {
217		unsigned rl = input & 0x030;
218		unsigned ud = input & 0x0C0;
219		input &= 0x30F;
220		if (rl != 0x030) {
221			input |= rl;
222		}
223		if (ud != 0x0C0) {
224			input |= ud;
225		}
226	}*/
227	return ~input & 0x3FF;
228}
229
230static void DSIOUpdateTimer(struct DSCommon* dscore, uint32_t address) {
231	switch (address) {
232	case DS_REG_TM0CNT_LO:
233		GBATimerUpdateRegisterInternal(&dscore->timers[0], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
234		break;
235	case DS_REG_TM1CNT_LO:
236		GBATimerUpdateRegisterInternal(&dscore->timers[1], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
237		break;
238	case DS_REG_TM2CNT_LO:
239		GBATimerUpdateRegisterInternal(&dscore->timers[2], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
240		break;
241	case DS_REG_TM3CNT_LO:
242		GBATimerUpdateRegisterInternal(&dscore->timers[3], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
243		break;
244	}
245}
246
247void DS7IOInit(struct DS* ds) {
248	memset(ds->memory.io7, 0, sizeof(ds->memory.io7));
249	ds->memory.io7[DS_REG_IPCFIFOCNT >> 1] = 0x0101;
250	ds->memory.io7[DS_REG_POSTFLG >> 1] = 0x0001;
251}
252
253void DS7IOWrite(struct DS* ds, uint32_t address, uint16_t value) {
254	switch (address) {
255	case DS7_REG_SPICNT:
256		value &= 0xCF83;
257		value = DSSPIWriteControl(ds, value);
258		break;
259	case DS7_REG_SPIDATA:
260		DSSPIWrite(ds, value);
261		break;
262	default:
263		{
264			uint32_t v2 = DSIOWrite(&ds->ds7, address, value);
265			if (v2 & 0x10000) {
266				value = v2;
267				break;
268			} else if (v2 & 0x20000) {
269				return;
270			}
271		}
272		mLOG(DS_IO, STUB, "Stub DS7 I/O register write: %06X:%04X", address, value);
273		if (address >= DS7_REG_MAX) {
274			mLOG(DS_IO, GAME_ERROR, "Write to unused DS7 I/O register: %06X:%04X", address, value);
275			return;
276		}
277		break;
278	}
279	ds->memory.io7[address >> 1] = value;
280}
281
282void DS7IOWrite8(struct DS* ds, uint32_t address, uint8_t value) {
283	if (address == DS7_REG_HALTCNT) {
284		_DSHaltCNT(&ds->ds7, value);
285		return;
286	}
287	if (address < DS7_REG_MAX) {
288		uint16_t value16 = value << (8 * (address & 1));
289		value16 |= (ds->ds7.memory.io[(address & 0xFFF) >> 1]) & ~(0xFF << (8 * (address & 1)));
290		DS7IOWrite(ds, address & 0xFFFFFFFE, value16);
291	} else {
292		mLOG(DS, STUB, "Writing to unknown DS7 register: %08X:%02X", address, value);
293	}
294}
295
296void DS7IOWrite32(struct DS* ds, uint32_t address, uint32_t value) {
297	switch (address) {
298	case DS_REG_DMA0SAD_LO:
299	case DS_REG_DMA1SAD_LO:
300	case DS_REG_DMA2SAD_LO:
301	case DS_REG_DMA3SAD_LO:
302	case DS_REG_DMA0DAD_LO:
303	case DS_REG_DMA1DAD_LO:
304	case DS_REG_DMA2DAD_LO:
305	case DS_REG_DMA3DAD_LO:
306	case DS_REG_IPCFIFOSEND_LO:
307	case DS_REG_IE_LO:
308		value = DSIOWrite32(&ds->ds7, address, value);
309		break;
310
311	case DS_REG_DMA0CNT_LO:
312		DS7DMAWriteCNT(&ds->ds7, 0, value);
313		break;
314	case DS_REG_DMA1CNT_LO:
315		DS7DMAWriteCNT(&ds->ds7, 1, value);
316		break;
317	case DS_REG_DMA2CNT_LO:
318		DS7DMAWriteCNT(&ds->ds7, 2, value);
319		break;
320	case DS_REG_DMA3CNT_LO:
321		DS7DMAWriteCNT(&ds->ds7, 3, value);
322		break;
323	default:
324		DS7IOWrite(ds, address, value & 0xFFFF);
325		DS7IOWrite(ds, address | 2, value >> 16);
326		return;
327	}
328	ds->ds7.memory.io[address >> 1] = value;
329	ds->ds7.memory.io[(address >> 1) + 1] = value >> 16;
330}
331
332uint16_t DS7IORead(struct DS* ds, uint32_t address) {
333	switch (address) {
334	case DS_REG_TM0CNT_LO:
335	case DS_REG_TM1CNT_LO:
336	case DS_REG_TM2CNT_LO:
337	case DS_REG_TM3CNT_LO:
338		DSIOUpdateTimer(&ds->ds7, address);
339		break;
340	case DS_REG_KEYINPUT:
341		return DSIOReadKeyInput(ds);
342	case DS7_REG_EXTKEYIN:
343		return DSIOReadExKeyInput(ds);
344	case DS_REG_VCOUNT:
345	case DS_REG_DMA0FILL_LO:
346	case DS_REG_DMA0FILL_HI:
347	case DS_REG_DMA1FILL_LO:
348	case DS_REG_DMA1FILL_HI:
349	case DS_REG_DMA2FILL_LO:
350	case DS_REG_DMA2FILL_HI:
351	case DS_REG_DMA3FILL_LO:
352	case DS_REG_DMA3FILL_HI:
353	case DS_REG_TM0CNT_HI:
354	case DS_REG_TM1CNT_HI:
355	case DS_REG_TM2CNT_HI:
356	case DS_REG_TM3CNT_HI:
357	case DS7_REG_SPICNT:
358	case DS7_REG_SPIDATA:
359	case DS_REG_IPCSYNC:
360	case DS_REG_IPCFIFOCNT:
361	case DS_REG_ROMCNT_LO:
362	case DS_REG_ROMCNT_HI:
363	case DS_REG_IME:
364	case 0x20A:
365	case DS_REG_IE_LO:
366	case DS_REG_IE_HI:
367	case DS_REG_IF_LO:
368	case DS_REG_IF_HI:
369	case DS_REG_POSTFLG:
370		// Handled transparently by the registers
371		break;
372	default:
373		mLOG(DS_IO, STUB, "Stub DS7 I/O register read: %06X", address);
374	}
375	if (address < DS7_REG_MAX) {
376		return ds->memory.io7[address >> 1];
377	}
378	return 0;
379}
380
381uint32_t DS7IORead32(struct DS* ds, uint32_t address) {
382	switch (address) {
383	case DS_REG_IPCFIFORECV_LO:
384		return DSIPCReadFIFO(&ds->ds7);
385	case DS_REG_ROMDATA_0:
386		if (ds->ds7.memory.slot1Access) {
387			return DSSlot1Read(ds);
388		} else {
389			mLOG(DS_IO, GAME_ERROR, "Invalid cart access");
390			return 0;
391		}
392	default:
393		return DS7IORead(ds, address & 0x00FFFFFC) | (DS7IORead(ds, (address & 0x00FFFFFC) | 2) << 16);
394	}
395}
396
397void DS9IOInit(struct DS* ds) {
398	memset(ds->memory.io9, 0, sizeof(ds->memory.io9));
399	ds->memory.io9[DS_REG_IPCFIFOCNT >> 1] = 0x0101;
400	ds->memory.io9[DS_REG_POSTFLG >> 1] = 0x0001;
401	DS9IOWrite(ds, DS9_REG_VRAMCNT_G, 0x0300);
402}
403
404void DS9IOWrite(struct DS* ds, uint32_t address, uint16_t value) {
405	if (address <= DS9_REG_A_BLDY && (address > DS_REG_VCOUNT || address == DS9_REG_A_DISPCNT_LO || address == DS9_REG_A_DISPCNT_HI)) {
406		value = ds->video.renderer->writeVideoRegister(ds->video.renderer, address, value);
407	} else if (address >= DS9_REG_B_DISPCNT_LO && address <= DS9_REG_B_BLDY) {
408		value = ds->video.renderer->writeVideoRegister(ds->video.renderer, address, value);
409	} else {
410		switch (address) {
411		// VRAM control
412		case DS9_REG_VRAMCNT_A:
413		case DS9_REG_VRAMCNT_C:
414		case DS9_REG_VRAMCNT_E:
415			value &= 0x9F9F;
416			DSVideoConfigureVRAM(ds, address - DS9_REG_VRAMCNT_A, value & 0xFF);
417			DSVideoConfigureVRAM(ds, address - DS9_REG_VRAMCNT_A + 1, value >> 8);
418			break;
419		case DS9_REG_VRAMCNT_G:
420			value &= 0x9F03;
421			DSVideoConfigureVRAM(ds, 6, value & 0xFF);
422			DSConfigureWRAM(&ds->memory, value >> 8);
423			break;
424		case DS9_REG_VRAMCNT_H:
425			value &= 0x9F9F;
426			DSVideoConfigureVRAM(ds, 7, value & 0xFF);
427			DSVideoConfigureVRAM(ds, 8, value >> 8);
428			break;
429
430		case DS9_REG_EXMEMCNT:
431			value &= 0xE8FF;
432			DSConfigureExternalMemory(ds, value);
433			break;
434
435		// Math
436		case DS9_REG_DIVCNT:
437			value = _scheduleDiv(ds, value);
438			break;
439		case DS9_REG_DIV_NUMER_0:
440		case DS9_REG_DIV_NUMER_1:
441		case DS9_REG_DIV_NUMER_2:
442		case DS9_REG_DIV_NUMER_3:
443		case DS9_REG_DIV_DENOM_0:
444		case DS9_REG_DIV_DENOM_1:
445		case DS9_REG_DIV_DENOM_2:
446		case DS9_REG_DIV_DENOM_3:
447			ds->memory.io9[DS9_REG_DIVCNT >> 1] = _scheduleDiv(ds, ds->memory.io9[DS9_REG_DIVCNT >> 1]);
448			break;
449		case DS9_REG_SQRTCNT:
450			value = _scheduleSqrt(ds, value);
451			break;
452		case DS9_REG_SQRT_PARAM_0:
453		case DS9_REG_SQRT_PARAM_1:
454		case DS9_REG_SQRT_PARAM_2:
455		case DS9_REG_SQRT_PARAM_3:
456			ds->memory.io9[DS9_REG_SQRTCNT >> 1] = _scheduleSqrt(ds, ds->memory.io9[DS9_REG_SQRTCNT >> 1]);
457			break;
458
459		// High Video
460		case DS9_REG_POWCNT1:
461			value = ds->video.renderer->writeVideoRegister(ds->video.renderer, address, value);
462			break;
463
464		default:
465			{
466				uint32_t v2 = DSIOWrite(&ds->ds9, address, value);
467				if (v2 & 0x10000) {
468					value = v2;
469					break;
470				} else if (v2 & 0x20000) {
471					return;
472				}
473			}
474			mLOG(DS_IO, STUB, "Stub DS9 I/O register write: %06X:%04X", address, value);
475			if (address >= DS7_REG_MAX) {
476				mLOG(DS_IO, GAME_ERROR, "Write to unused DS9 I/O register: %06X:%04X", address, value);
477				return;
478			}
479			break;
480		}
481	}
482	ds->memory.io9[address >> 1] = value;
483}
484
485void DS9IOWrite8(struct DS* ds, uint32_t address, uint8_t value) {
486	if (address < DS9_REG_MAX) {
487		uint16_t value16 = value << (8 * (address & 1));
488		value16 |= (ds->memory.io9[(address & 0x1FFF) >> 1]) & ~(0xFF << (8 * (address & 1)));
489		DS9IOWrite(ds, address & 0xFFFFFFFE, value16);
490	} else {
491		mLOG(DS, STUB, "Writing to unknown DS9 register: %08X:%02X", address, value);
492	}
493}
494
495void DS9IOWrite32(struct DS* ds, uint32_t address, uint32_t value) {
496	switch (address) {
497	case DS_REG_DMA0SAD_LO:
498	case DS_REG_DMA1SAD_LO:
499	case DS_REG_DMA2SAD_LO:
500	case DS_REG_DMA3SAD_LO:
501	case DS_REG_DMA0DAD_LO:
502	case DS_REG_DMA1DAD_LO:
503	case DS_REG_DMA2DAD_LO:
504	case DS_REG_DMA3DAD_LO:
505	case DS_REG_IPCFIFOSEND_LO:
506	case DS_REG_IE_LO:
507		value = DSIOWrite32(&ds->ds9, address, value);
508		break;
509
510	case DS_REG_DMA0CNT_LO:
511		DS9DMAWriteCNT(&ds->ds9, 0, value);
512		break;
513	case DS_REG_DMA1CNT_LO:
514		DS9DMAWriteCNT(&ds->ds9, 1, value);
515		break;
516	case DS_REG_DMA2CNT_LO:
517		DS9DMAWriteCNT(&ds->ds9, 2, value);
518		break;
519	case DS_REG_DMA3CNT_LO:
520		DS9DMAWriteCNT(&ds->ds9, 3, value);
521		break;
522
523	default:
524		DS9IOWrite(ds, address, value & 0xFFFF);
525		DS9IOWrite(ds, address | 2, value >> 16);
526		return;
527	}
528	ds->ds9.memory.io[address >> 1] = value;
529	ds->ds9.memory.io[(address >> 1) + 1] = value >> 16;
530}
531
532uint16_t DS9IORead(struct DS* ds, uint32_t address) {
533	switch (address) {
534	case DS_REG_TM0CNT_LO:
535	case DS_REG_TM1CNT_LO:
536	case DS_REG_TM2CNT_LO:
537	case DS_REG_TM3CNT_LO:
538		DSIOUpdateTimer(&ds->ds9, address);
539		break;
540	case DS_REG_KEYINPUT:
541		return DSIOReadKeyInput(ds);
542	case DS_REG_VCOUNT:
543	case DS_REG_DMA0FILL_LO:
544	case DS_REG_DMA0FILL_HI:
545	case DS_REG_DMA1FILL_LO:
546	case DS_REG_DMA1FILL_HI:
547	case DS_REG_DMA2FILL_LO:
548	case DS_REG_DMA2FILL_HI:
549	case DS_REG_DMA3FILL_LO:
550	case DS_REG_DMA3FILL_HI:
551	case DS_REG_TM0CNT_HI:
552	case DS_REG_TM1CNT_HI:
553	case DS_REG_TM2CNT_HI:
554	case DS_REG_TM3CNT_HI:
555	case DS_REG_IPCSYNC:
556	case DS_REG_IPCFIFOCNT:
557	case DS_REG_ROMCNT_LO:
558	case DS_REG_ROMCNT_HI:
559	case DS_REG_IME:
560	case 0x20A:
561	case DS_REG_IE_LO:
562	case DS_REG_IE_HI:
563	case DS_REG_IF_LO:
564	case DS_REG_IF_HI:
565	case DS9_REG_DIVCNT:
566	case DS9_REG_DIV_NUMER_0:
567	case DS9_REG_DIV_NUMER_1:
568	case DS9_REG_DIV_NUMER_2:
569	case DS9_REG_DIV_NUMER_3:
570	case DS9_REG_DIV_DENOM_0:
571	case DS9_REG_DIV_DENOM_1:
572	case DS9_REG_DIV_DENOM_2:
573	case DS9_REG_DIV_DENOM_3:
574	case DS9_REG_DIV_RESULT_0:
575	case DS9_REG_DIV_RESULT_1:
576	case DS9_REG_DIV_RESULT_2:
577	case DS9_REG_DIV_RESULT_3:
578	case DS9_REG_DIVREM_RESULT_0:
579	case DS9_REG_DIVREM_RESULT_1:
580	case DS9_REG_DIVREM_RESULT_2:
581	case DS9_REG_DIVREM_RESULT_3:
582	case DS9_REG_SQRTCNT:
583	case DS9_REG_SQRT_PARAM_0:
584	case DS9_REG_SQRT_PARAM_1:
585	case DS9_REG_SQRT_PARAM_2:
586	case DS9_REG_SQRT_PARAM_3:
587	case DS9_REG_SQRT_RESULT_LO:
588	case DS9_REG_SQRT_RESULT_HI:
589	case DS_REG_POSTFLG:
590		// Handled transparently by the registers
591		break;
592	default:
593		mLOG(DS_IO, STUB, "Stub DS9 I/O register read: %06X", address);
594	}
595	if (address < DS9_REG_MAX) {
596		return ds->ds9.memory.io[address >> 1];
597	}
598	return 0;
599}
600
601uint32_t DS9IORead32(struct DS* ds, uint32_t address) {
602	switch (address) {
603	case DS_REG_IPCFIFORECV_LO:
604		return DSIPCReadFIFO(&ds->ds9);
605	case DS_REG_ROMDATA_0:
606		if (ds->ds9.memory.slot1Access) {
607			return DSSlot1Read(ds);
608		} else {
609			mLOG(DS_IO, GAME_ERROR, "Invalid cart access");
610			return 0;
611		}
612	default:
613		return DS9IORead(ds, address & 0x00FFFFFC) | (DS9IORead(ds, (address & 0x00FFFFFC) | 2) << 16);
614	}
615}