all repos — mgba @ d10ed92c562aa0c724ade1cfffeb1c49c235b86c

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