all repos — mgba @ a7f15672861a46767a37cd0fec1398c05a2790fc

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