all repos — mgba @ e7d6cba3616f00d6b633eecdfad5b89b5a15846c

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