3DS reverse engineering

Discussion of development of software for any "obsolete" computer or video game system.
nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Wed Oct 02, 2019 10:41 pm

Cool, that's interesting... hmmmm... but it doesn't really match-up with the few details that were thought to be known about the new3ds hardware...

On 3dbrew, the NFC is called "Device 15", https://www.3dbrew.org/wiki/I2C_Registers#Device_15 going by that, I2C command/responses should consist of a 4 byte header, followed by 0-255 byte data.
After power-up, I have noticed that the NFC chip sends six bytes on I2C bus: 10 60 00 02 00 01, then followed by endless FF-bytes. That's apparently some power-up/status message, with 4 byte header and 2 byte data.

In the pdf I couldn't spot anything mentioning those 4 byte headers. At best it seems to describe roughly similar 3 byte headers.
Looking at some source code, https://android.googlesource.com/kernel ... 079x-i2c.c it seems that there can be both 3 byte "HCI headers" and 4 byte "NCI headers" (whatever that is good for).

And, I guess there must be at least two protocols envolved, one for the I2C bus commands, and one for the actual wireless transmissons. So trying to use the specs for the wrong protocol might screw up everything. The pdf does mention I2C bus though, so it should theoretically cover the 4 byte headers that are used on new3ds I2C bus.

And, the chip manufacturer might have added yet another custom protocol (or at least few custom commands), eg. for the firmware update/patch feature.

profi200
Posts: 22
Joined: Fri May 10, 2019 4:48 am

Re: 3DS reverse engineering

Post by profi200 » Sun Nov 24, 2019 2:03 pm

Thought this may be useful. Recently i was digging in AgbBg and figured out how to turn on the speaker DAC. This is basically 1:1 what it does. The CODEC chip seems to be completely different to the TSC2117 because literally no reg or page/bank matches.

https://gist.github.com/profi200/492664 ... adbffb4aaf

References:
https://github.com/derrekr/fastboot3DS/ ... re/codec.c
https://github.com/derrekr/fastboot3DS/ ... are/gpio.c
https://www.3dbrew.org/wiki/Hardware_calibration#CDC (probably got reverse engineered from https://www.3dbrew.org/wiki/Factory_Setup#CTRAging ).

nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Wed Nov 27, 2019 6:52 am

Released no$gba v3.00 http://problemkaputt.de/gba.htm
The main news is that the 3ds hardware specs are now fairly complete (or I thought so, before profi200 found those new register banks in the TSC chip). There are still hundreds of unknown bits, but at least they are known to exist, and I hope that there won't be too many thousands of yet unknown registers popping up in future.
The emulator/debugger can load and disassemble 3ds firm files and 3ds mcu firmware images (but it doesn't actually emulate any 3ds hardware).

Code: Select all

27 Nov 2019 - version 3.00
- dsi/teak/help: mmio info from wwylele's .md files and lauterbach .per files
- dsi/teak/help: tested/added/clarified more/undocumented teak mmio details
- 3ds/teak/help: fixed errors in CFG11_SHAREDWRAM_32K_CODE/DATA descriptions
- 3ds/help: rev-engineered CSND sound/capture specs (mostly same as NDS sound)
- 3ds/help: rev-engineered most NDMA startup modes and CDMA peripheral IDs
- 3ds/help: basic notes on New3DS NFC hardware (Near-field communication)
- 3ds/help: basic specs for New3DS QTM io expander (whatever that is used for)
- 3ds/help: more or less working specs for New3DS C-stick and ZL/ZR buttons
- 3ds/help: full specs for accelerometer, and for both gyroscope chip versions
- 3ds/help: full specs for irda chip (yet no info on irda-software protocol)
- 3ds/help: full specs for corelink dma registers (still need opcodes though)
- 3ds/help: scanned SPI bus and I2C bus (with some new device id findings each)
- 3ds/help: added many i2c irq-sources (routed through gpio registers)
- 3ds/help: added comprehensive list of unknown lcd-i2c registers
- 3ds/help: removed lots of dirt from the official arm11 mpcore interrupt specs
- 3ds/help: rev-engineered event/fault irq numbers for XDMA, OldCDMA, NewCDMA
- 3ds/help: tested I2C+ARM camera access (and identified left and right cameras)
- 3ds/help: major rewrite of mcu chapter (focusing on actual info without blurb)
- tsc/help: added TSC flowcharts for touchscr,microphone,nds-mode and basic init
- mic/help: rev-engineered microphone, moved mic from unknown to sndex chapter
- 3ds/help: rev-engineered I2C clock config and manual/fifo SPI clock rates
- dsi/help: added 8mhz spi bus clock (not 3ds specific) (enable via scfg_ext7)
- 3ds/help: added complete New3DS XL Component List (and semi-complete Old3DS)
- 3ds/help: added basic MMU virtual memory table specs (in arm cp15 chapter)
- 3ds/gpu/help: completely rewritten Top/Bottom Screen/Framebuffer Setup chapter
- 3ds/gpu/help: added notes on unknown read/write-able bits in PICA registers
- 3ds/gpu/help: added list of unknown/unused/undocumented PICA registers
- 3ds/gpu/help: added specs for finalize/interrupt registers PICA(0000h..0035h)
- setup/controls: allows to use DEL/BS keys (toggles between none and that key)
- 3ds/cdma/xdma/help: added summary of all Corelink DMA registers and opcodes
- help/emu/disass: supports invalid arm/libgcc BX PC opcode (thanx scott norton)
- 3ds/help: better gpio specs, and various details here and there
- 3ds/help: added stubs with R/W masks for most unknown arm9/arm11 io ports
- 3ds/debug: assembler/disassembler supports all new ARMv6/ARMv6K opcodes
- 3ds/debug: start_direct can now load FIRM files to memory (for disass)
- 3ds/memory: started allocating some 3ds-specific memory (AXI, WRAM, etc)
- 3ds/arm11/help: arm/thumb opcode encoding specs for new ARMv6/ARMv6K opcodes
- 3ds/bptwl/help: added notes on (limited) bptwl i2c register emulation
- 3ds/mcu/help: RL78 opcodes, registers, flags, memory map, SFR I/O map
- 3ds/cpu/help: added notes on branch prediction affecting waitbyloop timings
- 3ds/config11/help: rev-engineered details for new3ds clk/mode change register
- wifiboot: uploader uses non-blocking tcp socket (for abort by keystroke)
- 3ds/disass: added RL78 disassembler (for 3DS.mcu or New3DS.mcu firmware image)
- debug/help: included no$gba debug help in gbatek (moved to bottom of text)
Some of the things I have worked on last two months:

After reverse-engineering the peripherals, I have had a closer look at the patchwork needed to access them. The I2C and SPI bus clock rates are now more or less documented (for better specs it might help to wire an oscilloscope to the mainboard - are there any known test points for the I2C/SPI clock signals, preferable on the bottom side of the New3DS mainboard?).

Some unexpected findings were 8MHz SPI clock in 3DS manual-SPI mode (which turned out to be dating back to DSi). And SPI bus scanning revealed that the TSC chip is wired to two SPI buses (maybe one intended to be used by ARM11 while running in NDS/DSi mode). And some I2C bus devices respond to some special/broadcast addresses (most of them mirroring to the BPTWL registers, which is probably a bug, and there is at least one more chip responding, which might actually support broadcast commands).

For IRQ and DMA, I have tracked down most of the I2C interrupt signals (which are routed through GPIO registers), ands IRQs for the DMA event/fault signals, and also most NDMA startup modes and CDMA/XDMA peripheral IDs. Missing are Microphone DMA (no surprise because the TSC stuff is always good for hiding nasty secrets), and external ROM cartridge DMA (but ROM carts are pretty much in the dark anyways, the running joke is that we know that something called CARD0 and CARD1 does exist, but I don't think that anybody has ever figured out or documented what CARD0 or CARD1 means).

The C-stick reading is now known, too. The problem with the FFh-response bytes was merely caused by missing I2C bus timing init. After fixing that, reading the C-stick and ZL/ZR button data worked without problems. One can also write some command/mode to the chip, but I don't what that does (anyways, reading works fine without needing to write anything).

Then I've tested camera access, which worked more or less without problems, ie. same as on DSi (with some moved control bits in the ARM11 registers). The silly trophy is that I am probably the first person on earth who has found out (and documented) which of the two cameras is for left-eye and which for right-eye : )

The gbatek specs are now also including some flowcharts for initializing the TSC registers, activating touchscreen and microphone inputs, and switching the TSC chip to NDS mode. Most of that should probably work on 3DS, too (at least the touchscreen reading is tested and working).

One big new thing are specs for the XpertTeak MMIO registers, based on wwylele's .md files (and some bits from the lauterbach .per files), with that info, I have done some more tests on dsi/3ds hardware and discovered a couple of additional registers and bits. And, I think, I have even solved the "What is Teak processor clock" question (that is... pretty unexpected... 107MHz, aka 134MHz/1.25).

And finally, I have also looked at the 3ds CSND registers, which turned out to be more or less same as NDS sound/capture registers (with increased length values, and simplified volume control, and a few other changes).

nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Wed Nov 27, 2019 7:46 am

profi200 wrote:
Sun Nov 24, 2019 2:03 pm
Thought this may be useful. Recently i was digging in AgbBg and figured out how to turn on the speaker DAC. This is basically 1:1 what it does. The CODEC chip seems to be completely different to the TSC2117 because literally no reg or page/bank matches.
Cool, the additional registers in bank 64h/65h are pretty unexpected.
AgbBg is for Gameboy Advance sound? Or does the code also work for NDS/DSi/3DS sound?
The DSi-style TSC registers exist on 3DS, too (and at least dsi-style touchscreen reading is working just fine).
I have tried reading bank 64h/65h yesterday, but they seem to be just zerofilled. Is that the normal reset state, or is there something needed to enable that banks?
The 3DS has the TSC chip wired to SPI bus 0 (device 2) and SPI bus 1 (device 0). Which bus did you use there? TSC bank 00h-03h seem to be same on each bus. For bank 64h/65h, I have only tried reading bus 0 yet, maybe bank 64h/65h appear on bus 1 only?

Register 10145000h is somewhat equivalent to DSi SNDEXCNT register. If lower 6bit are volume (?) then they might contain a DSi-style volume ratio for CSND and DSP. Or better, maybe there are separate volume settings for CSND and DSP each (so that one isn't restricted to half volume when using both sound sources).

What does this line do: "*((vu32*)0x10147020) &= ~1u; // GPIO bitmask 0x40"?
Asking because I thought that bit0-15 are read-only, with bit0 being the input from C-stick IRQ signal.
Of course, C-stick exists on New3DS only... I suspect that the GPIO register/bits might be fairly different on Old3DS.

With my broken top-screen backlight connector, I also don't have any working speakers and volume slider. But headphones might be working (if the disconnected slider defaults to non-zero volume, or if there's a way to override the slider via MCU or TSC registers). I didn't get the headphones working so far, I'll need to have a closer look at your code, maybe you have some extra init-steps in there that I didn't had tried yet.

profi200
Posts: 22
Joined: Fri May 10, 2019 4:48 am

Re: 3DS reverse engineering

Post by profi200 » Wed Nov 27, 2019 2:59 pm

nocash wrote:
Wed Nov 27, 2019 6:52 am
After reverse-engineering the peripherals, I have had a closer look at the patchwork needed to access them. The I2C and SPI bus clock rates are now more or less documented (for better specs it might help to wire an oscilloscope to the mainboard - are there any known test points for the I2C/SPI clock signals, preferable on the bottom side of the New3DS mainboard?).
You should find I2C lines right next to the MCU and SPI next to the CODEC chip.
nocash wrote:
Wed Nov 27, 2019 6:52 am
Some unexpected findings were 8MHz SPI clock in 3DS manual-SPI mode (which turned out to be dating back to DSi). And SPI bus scanning revealed that the TSC chip is wired to two SPI buses (maybe one intended to be used by ARM11 while running in NDS/DSi mode). And some I2C bus devices respond to some special/broadcast addresses (most of them mirroring to the BPTWL registers, which is probably a bug, and there is at least one more chip responding, which might actually support broadcast commands).
Regarding SPI clock: I have not confirmed the possible clocks for the new NSPI interface but a guess was 4 MHz. May be actually 8 on the highest setting.

The device table in the .c file may answer a few of your questions.
https://github.com/derrekr/fastboot3DS/ ... ware/spi.h
https://github.com/derrekr/fastboot3DS/ ... ware/spi.c
nocash wrote:
Wed Nov 27, 2019 6:52 am
For IRQ and DMA, I have tracked down most of the I2C interrupt signals (which are routed through GPIO registers), ands IRQs for the DMA event/fault signals, and also most NDMA startup modes and CDMA/XDMA peripheral IDs. Missing are Microphone DMA (no surprise because the TSC stuff is always good for hiding nasty secrets), and external ROM cartridge DMA (but ROM carts are pretty much in the dark anyways, the running joke is that we know that something called CARD0 and CARD1 does exist, but I don't think that anybody has ever figured out or documented what CARD0 or CARD1 means).
Regarding GPIO IRQs here is another goodie i got out of AgbBg a while ago:

Basically the entire GPIO to ARM11 GIC IRQ mapping.
https://gist.github.com/profi200/a0a0ad ... 090b8dbeea
nocash wrote:
Wed Nov 27, 2019 6:52 am
The C-stick reading is now known, too. The problem with the FFh-response bytes was merely caused by missing I2C bus timing init. After fixing that, reading the C-stick and ZL/ZR button data worked without problems. One can also write some command/mode to the chip, but I don't what that does (anyways, reading works fine without needing to write anything).
I2C clock and unknown settings:

These are hardcoded in everything interfacing with I2C devices.
https://github.com/derrekr/fastboot3DS/ ... #L125-L126
nocash wrote:
Wed Nov 27, 2019 6:52 am
The gbatek specs are now also including some flowcharts for initializing the TSC registers, activating touchscreen and microphone inputs, and switching the TSC chip to NDS mode. Most of that should probably work on 3DS, too (at least the touchscreen reading is tested and working).
3DS mode touchscreen and Circle-Pad reading has been implemented. It's basically all the CODEC_init() function does in the fastboot3DS codec.c i linked earlier. The function below CODEC_init() is for reading Circle-Pad and touchscreen ADC values (see hid.c in the same dir as codec.c).
nocash wrote:
Wed Nov 27, 2019 6:52 am
And finally, I have also looked at the 3ds CSND registers, which turned out to be more or less same as NDS sound/capture registers (with increased length values, and simplified volume control, and a few other changes).
As for CSND: I have been trying to play a simple 50% square wave to confirm i'm getting sound output but without luck so far. I'm probably missing volume control on the CODEC side or something. Not sure if needed but i added extra code to get the DSP out of reset in case that is needed for CSND to work but it's probably not needed.

Code: Select all

	*((vu8*)0x10141230) = 1;
	*((vu16*)0x10203008) = 1;
	TIMER_sleepMs(1);
	*((vu16*)0x10203008) = 0;
	//*((vu16*)0x10145000) = (*((vu16*)0x10145000) & ~0x3Fu) | 0x20;
	*((vu16*)0x10103000) = 0x8000;
	*((vu16*)0x10103002) = 1u<<15 | 1u<<14;

	*((vu16*)0x10103502) = 0; //0x3FEC3FC / 16360u;
	*((vu16*)0x10103504) = 0x4000;
	*((vu16*)0x10103506) = 0x4000;
	*((vu16*)0x10103508) = 0x4000;
	*((vu16*)0x1010350A) = 0x4000;
	*((vu32*)0x1010350C) = 0;
	*((vu32*)0x10103510) = 0;
	*((vu32*)0x10103514) = 0;
	*((vu32*)0x10103518) = 0;
	*((vu32*)0x1010351C) = 0;
	*((vu16*)0x10103500) = 1u<<15 | 1u<<14 | 3u<<12 | 1u<<10;
I basically dumped these reg values straight from Horizon OS userland with a homebrew running using CSND.

nocash wrote:
Wed Nov 27, 2019 7:46 am
profi200 wrote:
Sun Nov 24, 2019 2:03 pm
Thought this may be useful. Recently i was digging in AgbBg and figured out how to turn on the speaker DAC. This is basically 1:1 what it does. The CODEC chip seems to be completely different to the TSC2117 because literally no reg or page/bank matches.
AgbBg is for Gameboy Advance sound? Or does the code also work for NDS/DSi/3DS sound?
AgbBg is the AGB_FIRM ARM11 background process that handles a few bits of "hardware emulation".
nocash wrote:
Wed Nov 27, 2019 7:46 am
I have tried reading bank 64h/65h yesterday, but they seem to be just zerofilled. Is that the normal reset state, or is there something needed to enable that banks?
The 3DS has the TSC chip wired to SPI bus 0 (device 2) and SPI bus 1 (device 0). Which bus did you use there? TSC bank 00h-03h seem to be same on each bus. For bank 64h/65h, I have only tried reading bus 0 yet, maybe bank 64h/65h appear on bus 1 only?
3DS mode is exclusively using the bus 0 interface as you can see in the device table in spi.c.
nocash wrote:
Wed Nov 27, 2019 7:46 am
Register 10145000h is somewhat equivalent to DSi SNDEXCNT register. If lower 6bit are volume (?) then they might contain a DSi-style volume ratio for CSND and DSP. Or better, maybe there are separate volume settings for CSND and DSP each (so that one isn't restricted to half volume when using both sound sources).
Similar but a bit different. I have added my findings about these regs to 3dbrew.

https://www.3dbrew.org/wiki/CODEC_Registers
nocash wrote:
Wed Nov 27, 2019 7:46 am
What does this line do: "*((vu32*)0x10147020) &= ~1u; // GPIO bitmask 0x40"?
Asking because I thought that bit0-15 are read-only, with bit0 being the input from C-stick IRQ signal.
Of course, C-stick exists on New3DS only... I suspect that the GPIO register/bits might be fairly different on Old3DS.
They are not read only. These GPIOs work in both directions and this bit is set every time there are big changes to CODEC registers like init or entering/waking from sleep.
I updated the CODEC/sound init gist with more details i left out previously (some more values used from the calibration struct mainly).

https://gist.github.com/profi200/492664 ... adbffb4aaf
nocash wrote:
Wed Nov 27, 2019 7:46 am
With my broken top-screen backlight connector, I also don't have any working speakers and volume slider. But headphones might be working (if the disconnected slider defaults to non-zero volume, or if there's a way to override the slider via MCU or TSC registers). I didn't get the headphones working so far, I'll need to have a closer look at your code, maybe you have some extra init-steps in there that I didn't had tried yet.
The entire init is a huge clusterfuck of unknown regs. Speaker/headphone switching is also broken probably because this is handled in software on the ARM11. Not sure. Volume is probaly also controlled in software because the volume slider is already connected to the MCU.

profi200
Posts: 22
Joined: Fri May 10, 2019 4:48 am

Re: 3DS reverse engineering

Post by profi200 » Thu Nov 28, 2019 7:47 am

2 new findings. After you updated the docs on the CSND regs i improved my CSND code. Samplerate calculation is wrong but good enough to get something out of the speakers.
If i let this code run and continue booting HOS i get the expected result because csnd module is lazy as f... and doesn't reset the regs correctly resulting in the channel still playing in background. But on bare metal still nothing. The I2C reg write happens in codec module immediately after it is done with all the regular and sound init but it doesn't have any effect either. And the 2 CODEC reg writes i maybe thought could be for sound volume but they do nothing.

Code: Select all

	//I2C_writeReg(I2C_DEV_MCU, 0x26, I2C_readReg(I2C_DEV_MCU, 0x26) | 0x10);
	//codecSwitchBankWriteReg(0x64, 0x7A, 128);
	//codecWriteReg(0x78, 128);
	*((vu8*)0x10141230) = 1;
	*((vu16*)0x10203008) = 1;
	TIMER_sleepMs(1);
	*((vu16*)0x10203008) = 0;
	//*((vu16*)0x10145000) = (*((vu16*)0x10145000) & ~0x3Fu) | 0x20;
	*((vu16*)0x10103002) = 1u<<15;
	*((vu16*)0x10103000) = 0x8000;
	*((vu16*)0x10103002) = 1u<<15 | 1u<<14;
	//*((vu32*)0x10103010) = ?;
	//*((vu8*)0x10103014) = ?;

	*((vu16*)0x10103502) = (0x3FEC3FCu / 16360) & 0xFFFFu; // This calculation seems to be wrong.
	*((vu16*)0x10103504) = 0x8000;
	*((vu16*)0x10103506) = 0x8000;
	*((vu16*)0x10103508) = 0x8000;
	*((vu16*)0x1010350A) = 0x8000;
	*((vu32*)0x1010350C) = 0;
	*((vu32*)0x10103510) = 0; // Size
	*((vu32*)0x10103514) = 0;
	*((vu32*)0x10103518) = 0;
	*((vu32*)0x1010351C) = 0;
	*((vu16*)0x10103500) = 1u<<15 | 1u<<14 | 3u<<12 | 1u<<10;
Note: Many of these regs are actually accessed as u16 by the csnd module unlike what you documented.


You maybe remember this piece of code:

Code: Select all

	/*if(!rodata_126A48)*/ codecMaskReg(0x45, 0x30, (*((vu8*)0x10147010) & 1u)<<4 | 0x20u); // GPIO bitmask 8.
Writing 1 to bit 4 turns off the speakers and probably turns on the headphone jack (not confirmed yet).

nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Thu Nov 28, 2019 11:09 am

In this line: "*((vu8*)0x10141230) = 1;"
Better write 3 instead of 1. I think otherwise writes to the DSP registers at 10203000h will ignored. DSP-init probably isn't needed for CSND though.

On the other hand, many registers may become read-only once they are *enabled*. If you are running out of ideas, try readback the written values to see if the write had worked (of course won't work for write-only CSND registers) (though one can use CSND capture to see if sound is playing).

The SNDEXCNT 10145000h register does probably include a mute bit... my guess would be in bit14 or bit30. Getting that wrong might explain missing sound.

3DS Volume seems to be routed from MCU to TSC via the MCU's own I2C bus (the bus that does also connect to the accelerometer). So volume should work automatically without ARM11 code.

The headphone switch (the switch in the headphone socket): The DSi has that signal wired to the TSC chip, and also to ARM7 GPIO. I guess the TSC can switch to headphones automatically, and the ARM7 GPIO bit is only intended as a status flag. A rather unintended side-effect is that setting the GPIO to output direction could make the TSC think that a headphone is connected. On New3DS the headphone signal seems to be in GPIO 10147010h.bit0 (but maybe Old3DS had that in bit4).

One register that is still missing is SOUNDBIAS, if that's uninitialized/zero then one would probably hear only positive samples (but that should be good enough to hear something).

If DSP and CSND have separate volumes then I would try to set both bit0-5 and bit6-11 to nonzero values, like 20h.
Or if they have a volume "ratio" as on DSi then maybe better set both to 10h (assuming that full range is 0..20h). In that case bit0-5 and bit6-11 might be ratios for left & right side.

Oops, yes, GPIO 10147020h is write-able (I forgot that the Input & Output registers are both mapped to the same address).

Accessing registers as 8bit/16bit/32bit doesn't really matter in most cases (exceptions would be FIFOs, and a few things like 8bit writes to old NDS-wifi registers). So it's mostly a cosmetic decision whether to describe them as 8bit or 16bit or 32bit registers. The CSND regs are tested & working with 32bit access. Of course, CNT, Rate, Volume registers may be easier to use with 16bit access.

For the SPI and I2C bus numbering...
I have numbered them bus 0,1,2 (with bus 0 being the one for DSi-devices) (and using 0 and 1 as in bootrom I2C functions).
Whilst 3dbrew prefers numbering like 1,2,3, or 3,1,2.
Whatever is best, it's highly confusing; the only workaround is probably to specifiy the ARM11 port addresses instead bus numbers.

Anyways, if I get your code/table right, you are using port 10142000h for 3DS-TSC access (and 10160000h would be the bus that is used in NDS/DSi-TSC-mode).

Circle Pad reading is nice! As far as I understand it does simply require reading a few more bytes after reading the touchscreen screen coordinates (and using the bank 64h/65h/etc. init code that you had posted earlier above).

profi200
Posts: 22
Joined: Fri May 10, 2019 4:48 am

Re: 3DS reverse engineering

Post by profi200 » Fri Nov 29, 2019 1:44 pm

nocash wrote:
Thu Nov 28, 2019 11:09 am
In this line: "*((vu8*)0x10141230) = 1;"
Better write 3 instead of 1. I think otherwise writes to the DSP registers at 10203000h will ignored. DSP-init probably isn't needed for CSND though.
Ah, good catch. But it makes no difference writing 3 instead unfortunately.
nocash wrote:
Thu Nov 28, 2019 11:09 am
On the other hand, many registers may become read-only once they are *enabled*. If you are running out of ideas, try readback the written values to see if the write had worked (of course won't work for write-only CSND registers) (though one can use CSND capture to see if sound is playing).
The problem is not CSND not working (it works and i can hear the channel playing when i boot Horizon OS as noted above).
nocash wrote:
Thu Nov 28, 2019 11:09 am
The SNDEXCNT 10145000h register does probably include a mute bit... my guess would be in bit14 or bit30. Getting that wrong might explain missing sound.
There is no mute bit. See the notes below the detailed register documentation. I'm writing exactly what the codec module writes to these regs and i confirmed these are accurate by both checking the Corgi3DS log output and printing these regs in userland (homebrew) while a CSND channel is playing.
nocash wrote:
Thu Nov 28, 2019 11:09 am
3DS Volume seems to be routed from MCU to TSC via the MCU's own I2C bus (the bus that does also connect to the accelerometer). So volume should work automatically without ARM11 code.
What makes you think this is the case?
nocash wrote:
Thu Nov 28, 2019 11:09 am
The headphone switch (the switch in the headphone socket): The DSi has that signal wired to the TSC chip, and also to ARM7 GPIO. I guess the TSC can switch to headphones automatically, and the ARM7 GPIO bit is only intended as a status flag. A rather unintended side-effect is that setting the GPIO to output direction could make the TSC think that a headphone is connected. On New3DS the headphone signal seems to be in GPIO 10147010h.bit0 (but maybe Old3DS had that in bit4).
I meant bit 4 of the CODEC reg. Note the & 1 with the GPIO reg and then shifting it up to bit 4.
nocash wrote:
Thu Nov 28, 2019 11:09 am
One register that is still missing is SOUNDBIAS, if that's uninitialized/zero then one would probably hear only positive samples (but that should be good enough to hear something).
This doesn't seem to exist in 3DS mode but there are CODEC regs for bias.
nocash wrote:
Thu Nov 28, 2019 11:09 am
If DSP and CSND have separate volumes then I would try to set both bit0-5 and bit6-11 to nonzero values, like 20h.
Or if they have a volume "ratio" as on DSi then maybe better set both to 10h (assuming that full range is 0..20h). In that case bit0-5 and bit6-11 might be ratios for left & right side.
See above. 6-11 is never touched but 0-5 is set to 0x20 every time the DSP is active. Nothing else is changed at runtime.
nocash wrote:
Thu Nov 28, 2019 11:09 am
Oops, yes, GPIO 10147020h is write-able (I forgot that the Input & Output registers are both mapped to the same address).
Actually after you mentioned this i changed it to a u16 write.
nocash wrote:
Thu Nov 28, 2019 11:09 am
Accessing registers as 8bit/16bit/32bit doesn't really matter in most cases (exceptions would be FIFOs, and a few things like 8bit writes to old NDS-wifi registers). So it's mostly a cosmetic decision whether to describe them as 8bit or 16bit or 32bit registers. The CSND regs are tested & working with 32bit access. Of course, CNT, Rate, Volume registers may be easier to use with 16bit access.
I would rather go with the official way of accessing these regs just to be on the safe side.
nocash wrote:
Thu Nov 28, 2019 11:09 am
Anyways, if I get your code/table right, you are using port 10142000h for 3DS-TSC access (and 10160000h would be the bus that is used in NDS/DSi-TSC-mode).
Correct. 3DS mode code never uses the DSi interface as far as i can tell.
nocash wrote:
Thu Nov 28, 2019 11:09 am
Circle Pad reading is nice! As far as I understand it does simply require reading a few more bytes after reading the touchscreen screen coordinates (and using the bank 64h/65h/etc. init code that you had posted earlier above).
All ADC and touchscreen readings seem to be in bank/page 0xFB reg 1.

nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Sat Nov 30, 2019 9:07 am

I have implemented most of your code for initializing TSC registers in bank 00h,01h,64h,65h. And now I am actually hearing CSND output with square wave and noise on the headphones! Many thanks for that code!
I have also initialized various other control registers like CFG11 and SNDEXCNT and SPI powerman. I'll need to remove that stuff step-by-step to figure out which parts are really needed for sound.
The bank 64h/65h registers seem to be accessible only on the "new" SPI bus (10142000h), trying to read them on the "old" SPI bus (10160000h) does return only zeroes in that banks (apart from the bank number being visible in the register at index 0).

For the Volume routing:
The DSi mainboard has I2C volume output wired from the MCU (BPTWL chip) to an I2C potentionmeter (ISL95810 chip with device ID 50h) which does then forward an analog signal to the TSC chip's volume wiper input.
The 3DS MCU firmware disasembly does output volume to some I2C device (with device ID A4h), which is probably being the TSC chip (unless the 3DS does also have a separate I2C potentionmeter somewhere on the mainboard).
That "direct" routing from MCU to TSC is probably also the reason why the volume slider calibration is handled inside of the MCU (with the 0% and 100% settings in MCU[58h,59h]).
The 3dbrew webpage does also mention some corner cases, like setting both MCU[58h,59h] to 00h causing "always 100% volume" regardless of the slider (I have used to bypass my broken top-screen slider-connector; dunno if that was really needed).

Ah, okay, now I got it, forwarding the GPIO headphone bit to one of the TSC register bits. Hmmm, I haven't specifically done that yet (and still have headphone sound). And I would have thought that the TSC chip can switch between headphone/speakers automatically. Or maybe it's optional whether or not to disable speakers when connecting headphones.

Btw. are you sure that it's legit to change GPIO data via RMW? The Input value isn't always same as Output value. At least so for input-direction (but, yes, that should be don't care when writing the Output value). However, I don't know what appears as readback value for data in output-direction (it might be the raw output data selection, or the output signal mangled with external signals/impedances).

I haven't tried Circle Pad reading yet. But good that you mentioned that the data is read from bank FBh instead FCh! I would have probably missed that detail.

profi200
Posts: 22
Joined: Fri May 10, 2019 4:48 am

Re: 3DS reverse engineering

Post by profi200 » Sat Nov 30, 2019 10:33 am

Can you give more details? What are doing different from this code? And in which order are you doing what?

Note that now all functions take the bank as parameter because relying on previous bank switches can be error prone and is annoying.
https://gist.github.com/profi200/492664 ... adbffb4aaf

nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Sat Nov 30, 2019 11:04 am

I'll do the step-by-step removal of the init functions later today, and let you know which ARM registers are needed.

Looking at your new code...

Code: Select all

static void codecSwitchBank(u8 bank)
{
	static u8 curBank = 0x63;
	if(bank != curBank)
	{
		alignas(4) u8 inBuf[4];

		inBuf[0] = 0; // Write
		inBuf[1] = bank;
		NSPI_writeRead(NSPI_DEV_CODEC, (u32*)inBuf, NULL, 2, 0, true);

		curBank = bank;
	}
}

static void codecReadRegBuf(u8 reg, u32 *buf, u32 size)
{
	alignas(4) u8 inBuf[4];

	inBuf[0] = reg<<1 | 1u;
	NSPI_writeRead(NSPI_DEV_CODEC, (u32*)inBuf, buf, 1, size, true);
}

static u8 codecReadReg(u8 bank, u8 reg)
{
	alignas(4) u8 outBuf[4];

	codecSwitchBank(bank);
	codecReadRegBuf(reg, (u32*)outBuf, 1);

	return outBuf[0];
}

static void codecWriteRegBuf(u8 bank, u8 reg, u32 *buf, u32 size)
{
	alignas(4) u8 inBuf[4];

	codecSwitchBank(bank);
	inBuf[0] = reg<<1; // Write

	NSPI_writeRead(NSPI_DEV_CODEC, (u32*)inBuf, NULL, 1, 0, false);
	NSPI_writeRead(NSPI_DEV_CODEC, buf, NULL, size, 0, true);
}

static void codecWriteReg(u8 bank, u8 reg, u8 val)
{
	alignas(4) u8 inBuf[4];

	codecSwitchBank(bank);
	inBuf[0] = reg<<1; // Write
	inBuf[1] = val;

	NSPI_writeRead(NSPI_DEV_CODEC, (u32*)inBuf, NULL, 2, 0, true);
}

static void codecMaskReg(u8 bank, u8 reg, u8 mask, u8 val)
{
	u8 data = codecReadReg(bank, reg);
	data = (data & ~mask) | (val & mask);
	codecWriteReg(bank, reg, data);
}

...
	// General codec reset + init?
	*((vu8*)0x10141220) |= 2;
	codecWriteReg(0x64, 1, 1);
	TIMER_sleepMs(40);
	codecSwitchBank(0); // What? Dummy switch after reset?
	codecWriteReg(0x64, 0x43, 0x11);
	codecMaskReg(0x65, 0x77, 1, 1);
	codecMaskReg(0, 0x39, 0x66, 0x66);
	codecWriteReg(0x65, 0x7A, 1);
	codecMaskReg(0x64, 0x22, 0x18, 0x18);
	GPIO_config(GPIO_2_0, GPIO_IRQ_ENABLE | GPIO_EDGE_RISING | GPIO_INPUT); // TODO: Handle codec events.
	codecMaskReg(0x64, 0x45, 0x30, (*((vu8*)0x10147010) & 1u)<<4 | 0x20u); // GPIO bitmask 8.
	codecMaskReg(0x64, 0x43, 0x80, 0);
	codecMaskReg(0x64, 0x43, 0x80, 0x80);
	codecWriteReg(0, 0xB, 0x87);
	codecMaskReg(0x64, 0x7C, 1, 0);

	// sub_3257FC()
	codecMaskReg(0x64, 0x22, 4, 0);
	// In AgbBg this is swapped at runtime.
	alignas(4) static const u16 unkData1[3] = {0xE17F, 0x1F80, 0xC17F};
	codecWriteRegBuf(4, 8, (u32*)unkData1, 6);
	alignas(4) static const u16 filterMic32[28] = {0xFF7F, 0x0000, 0x0000, 0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000};
	// filterMic47 is identical in the default, hardcoded calibration.
	codecWriteRegBuf(5, 8, (u32*)filterMic32, 56);
	codecWriteRegBuf(5, 0x48, (u32*)filterMic32, 56);
	codecMaskReg(1, 0x30, 0xC0, 0x40);
	codecMaskReg(1, 0x31, 0xC0, 0x40);
	codecWriteReg(0x65, 0x33, 3); // val = microphoneBias
	for(u32 i = 0; i < 64; i++)
	{
		codecMaskReg(0x65, 0x41, 0x3F, 0); // val = PGA_GAIN
		if((codecReadReg(0x65, 0x41) & 0x3Fu) == 0) break;
	}
	for(u32 i = 0; i < 64; i++)
	{
		codecMaskReg(0x65, 0x42, 3, 2); // val = quickCharge
		if((codecReadReg(0x65, 0x42) & 3u) == 2) break;
	}
	codecWriteReg(1, 0x2F, 0x2Bu & 0x7Fu);
	codecMaskReg(0x64, 0x31, 0x44, 0x44); // AgbBg uses val = 0 here
	codecWriteReg(0, 0x41, 0xFD);    // val = shutterVolume[0]
	codecWriteReg(0, 0x42, 0xFD);    // val = shutterVolume[0]
	codecWriteReg(0x64, 0x7B, 0xEC); // val = shutterVolume[1]

	// Sound stuff starts here
	GPIO_config(GPIO_3_0, GPIO_OUTPUT);
	*((vu16*)0x10147020) |= 1u; // GPIO bitmask 0x40
	TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO.
	*((vu16*)0x10145000) &= ~0xE000u;
	*((vu16*)0x10145000) |= 0xC800u;
	*((vu16*)0x10145002) &= ~0xE000u;
	*((vu16*)0x10145000) |= 0xE000u;
	codecMaskReg(0x65, 0x11, 0x1C, 0x10);
	codecWriteReg(0x64, 0x7A, 0);
	codecWriteReg(0x64, 0x78, 0);

	alignas(4) static const u16 filterFree[28] = {0xFF7F, 0, 0, 0x61CD, 0xAFDD, 0xFF7F, 0x5122, 0x9F32, 0x61CD, 0xAFDD, 0xFF7F, 0x5122, 0x9F32, 0x61CD, 0xAFDD, 0xFF7F, 0x5122, 0x9F32, 0xFF7F, 0, 0, 0, 0, 0xFF7F, 0, 0, 0, 0};
	{ // Missing in AgbBg
		const bool flag = (~codecReadReg(0, 0x40) & 0xCu) == 0;
		codecMaskReg(0, 0x3F, 0xC0, 0);
		codecWriteReg(0, 0x40, 0xC);
		for(u32 i = 0; i < 100; i++)
		{
			if(!(~codecReadReg(0x64, 0x26) & 0x44u)) break;
			TIMER_sleepMs(1);
		}
		codecWriteRegBuf(9, 2, (u32*)filterFree, 6);
		codecWriteRegBuf(8, 0xC, (u32*)&filterFree[3], 50);
		codecWriteRegBuf(9, 8, (u32*)filterFree, 6);
		codecWriteRegBuf(8, 0x4C, (u32*)&filterFree[3], 50);
		if(!flag)
		{
			codecMaskReg(0, 0x3F, 0xC0, 0xC0);
			codecWriteReg(0, 0x40, 0);
		}
	}
	{
		const bool flag = (~codecReadReg(0x64, 0x77) & 0xCu) == 0;
		codecMaskReg(0x64, 0x77, 0xC, 0xC);
		for(u32 i = 0; i < 100; i++)
		{
			if(!(~codecReadReg(0x64, 0x26) & 0x88u)) break;
			TIMER_sleepMs(1);
		}
		codecWriteRegBuf(0xA, 2, (u32*)filterFree, 6);
		codecWriteRegBuf(0xA, 0xC, (u32*)&filterFree[3], 50);
		if(!flag) codecMaskReg(0x64, 0x77, 0xC, 0);
	}

	alignas(4) static const u16 filterSP32[15] = {0xFF7F, 0x7194, 0x8D57, 0x9678, 0x588E, 0x50C9, 0x3075, 0x50C9, 0x0000, 0x0000, 0xE07F, 0x10C0, 0x0000, 0xE03F, 0x0000};
	codecWriteRegBuf(0xC, 2, (u32*)filterSP32, 30);
	codecWriteRegBuf(0xC, 0x42, (u32*)filterSP32, 30);
	alignas(4) static const u16 filterSP47[15] = {0xFF7F, 0xBD8E, 0xBD62, 0xE07A, 0x088A, 0xBEC7, 0x3075, 0xBEC7, 0x0000, 0x0000, 0xE97F, 0x0CC0, 0x0000, 0xE93F, 0x0000};
	codecWriteRegBuf(0xC, 0x20, (u32*)filterSP47, 30);
	codecWriteRegBuf(0xC, 0x60, (u32*)filterSP47, 30);
	alignas(4) static const u16 filterHP32[15] = {0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xE07F, 0x10C0, 0x0000, 0xE03F, 0x0000};
	codecWriteRegBuf(0xB, 2, (u32*)filterHP32, 30);
	codecWriteRegBuf(0xB, 0x42, (u32*)filterHP32, 30);
	alignas(4) static const u16 filterHP47[15] = {0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xFF7F, 0x0000, 0x0000, 0x0000, 0x0000, 0xE97F, 0x0CC0, 0x0000, 0xE93F, 0x0000};
	codecWriteRegBuf(0xB, 0x20, (u32*)filterHP47, 30);
	codecWriteRegBuf(0xB, 0x60, (u32*)filterHP47, 30);
	codecMaskReg(0x64, 0x76, 0xC0, 0xC0);
	TIMER_sleepMs(10);
	for(u32 i = 0; i < 100; i++)
	{
		if(!(~codecReadReg(0x64, 0x25) & 0x88u)) break;
		TIMER_sleepMs(1);
	}
	codecWriteReg(0x65, 0xA, 0xA);

	codecMaskReg(0, 0x3F, 0xC0, 0xC0);
	codecWriteReg(0, 0x40, 0);
	codecMaskReg(0x64, 0x77, 0xC, 0);

	u8 val;
	if((codecReadReg(0, 2) & 0xFu) <= 1u && ((codecReadReg(0, 3) & 0x70u)>>4 <= 2u))
	{
		val = 0x3C;
	}
	else val = 0x1C;
	codecWriteReg(0x65, 0xB, val);

	codecWriteReg(0x65, 0xC, (0u<<3) | 4); // val = (driverGainHP<<3) | 4
	codecWriteReg(0x65, 0x16, 0); // val = analogVolumeHP
	codecWriteReg(0x65, 0x17, 0); // val = analogVolumeHP
	codecMaskReg(0x65, 0x11, 0xC0, 0xC0);
	codecWriteReg(0x65, 0x12, (1u<<2) | 2); // val = (driverGainSP<<2) | 2
	codecWriteReg(0x65, 0x13, (1u<<2) | 2); // val = (driverGainSP<<2) | 2
	codecWriteReg(0x65, 0x1B, 7); // val = analogVolumeSP
	codecWriteReg(0x65, 0x1C, 7); // val = analogVolumeSP
	TIMER_sleepMs(38);
	*((vu16*)0x10147020) &= ~1u; // GPIO bitmask 0x40
	TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO.


	// Circle pad
	codecWriteReg(0x67, 0x24, 0x98);
	codecWriteReg(0x67, 0x26, 0x00);
	codecWriteReg(0x67, 0x25, 0x43);
	codecWriteReg(0x67, 0x24, 0x18);
	codecWriteReg(0x67, 0x17, 0x43);
	codecWriteReg(0x67, 0x19, 0x69);
	codecWriteReg(0x67, 0x1B, 0x80);
	codecWriteReg(0x67, 0x27, 0x11);
	codecWriteReg(0x67, 0x26, 0xEC);
	codecWriteReg(0x67, 0x24, 0x18);
	codecWriteReg(0x67, 0x25, 0x53);

	// Touchscreen
	codecMaskReg(0x67, 0x26, 0x80, 0x80);
	codecMaskReg(0x67, 0x24, 0x80, 0x00);
	codecMaskReg(0x67, 0x25, 0x3C, 0x10);
	// Touchscreen deinit
	//codecMaskReg(0x67, 0x26, 0x80, 0x00);
	//codecMaskReg(0x67, 0x24, 0x80, 0x80);


	//codecMaskReg(0x64, 0x45, 0x30, 1u<<4 | 0x20u); // Switches to headphone jack?
	//codecWriteReg(0x64, 0x7A, 1);
	//codecWriteReg(0x78, 1); // This reg is never written to at least according to Corgi3DS log.
	I2C_writeReg(I2C_DEV_MCU, 0x58, 0x0E); // Volume slider 0% offset
	I2C_writeReg(I2C_DEV_MCU, 0x59, 0xF6); // Volume slider 100% offset
	I2C_writeReg(I2C_DEV_MCU, 0x26, I2C_readReg(I2C_DEV_MCU, 0x26) | 0x10);
	*((vu8*)0x10141230) = 3;
	*((vu16*)0x10203008) = 1;
	TIMER_sleepMs(1);
	*((vu16*)0x10203008) = 0;
	//*((vu16*)0x10145000) = (*((vu16*)0x10145000) & ~0x3Fu) | 0x20;
	*((vu16*)0x10103002) = 1u<<15;
	*((vu16*)0x10103000) = 0x8000;
	*((vu16*)0x10103002) = 1u<<15 | 1u<<14;
	//*((vu32*)0x10103010) = ?;
	//*((vu8*)0x10103014) = ?;

	*((vu16*)0x10103502) = (0x3FEC3FCu / 16360) & 0xFFFFu; // This calculation seems to be wrong.
	*((vu16*)0x10103504) = 0x8000;
	*((vu16*)0x10103506) = 0x8000;
	*((vu16*)0x10103508) = 0x8000;
	*((vu16*)0x1010350A) = 0x8000;
	*((vu32*)0x1010350C) = 0;
	*((vu32*)0x10103510) = 0; // Size
	*((vu32*)0x10103514) = 0;
	*((vu32*)0x10103518) = 0;
	*((vu32*)0x1010351C) = 0;
	*((vu16*)0x10103500) = 1u<<15 | 1u<<14 | 3u<<12 | 1u<<10;
...
Yes, including bank numbers in all function calls is making it easier to read. Oh, and there's now also more init stuff for touchscreen/circle pad in bank 67h.

profi200
Posts: 22
Joined: Fri May 10, 2019 4:48 am

Re: 3DS reverse engineering

Post by profi200 » Sat Nov 30, 2019 1:44 pm

nocash wrote:
Sat Nov 30, 2019 9:07 am
Btw. are you sure that it's legit to change GPIO data via RMW? The Input value isn't always same as Output value. At least so for input-direction (but, yes, that should be don't care when writing the Output value). However, I don't know what appears as readback value for data in output-direction (it might be the raw output data selection, or the output signal mangled with external signals/impedances).
Yes. There is a function in AgbBg which just read-modify-writes the whole bank of GPIOs. GPIOs set to output always seem to read as 0.

I tried now to init the power management control reg without success but the audio amp is already on anyway because i can hear noise from the speakers. This uses the very first entry from the device table in my code just for the record:

Code: Select all

	alignas(4) u8 buf[4];
	buf[0] = 1u<<7 | 0u; // Read power management reg 0
	NSPI_writeRead(NSPI_DEV_POWERMAN, (u32*)buf, (u32*)buf, 1, 1, true);
	buf[1] = (buf[0] & ~2u) | 1u;
	buf[0] = 0u; // Write power management reg 0
	NSPI_writeRead(NSPI_DEV_POWERMAN, (u32*)buf, NULL, 2, 0, true);

nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Sat Nov 30, 2019 4:41 pm

GPIO returns data=0 for bits with direction=output??? Then it would be unwise to use RMW on GPIO data (it would clear all other data bits).

I have tried to remove all init code, and now I have only two required register writes remaining (plus the SPI-TSC writes and CSND channel writes).
CFG11 and GPIO etc. seem to be all okay with their initial power-on values. MCU writes or SPI-Powerman writes apparently aren't needed either.
The two required writes are:
(before below writes, I am writing the SPI-TSC registers)
[10145000h]=080000000h ;CODEC SNDEXCNT
[10103000h]=0c0008000h ;CSND Control
(after above writes, I am writing the CSND channel registers)
If that values aren't working for you, try using headphones (which is what I have tested).

I have also tried some other settings for those registers:

Code: Select all

 ldr  r1,=REGBASE_CODEC                                 ;\
 ldr  r0,=0e0009000h+10h*41h  ;works                    ;
 ldr  r0,=0e0008000h+10h*41h  ;works                    ;
;ldr  r0,=0e000c000h+10h*41h  ;works                    ;
;ldr  r0,=0c0008000h+10h*41h  ;works, but very dissonant;
 ldr  r0,=0a0008000h+10h*41h  ;works                    ;
 ldr  r0,=080008000h+10h*41h  ;works                    ;
 ldr  r0,=020008000h+10h*41h  ;works, but silent        ;
;ldr  r0,=080008000h+10h*40h  ;works                    ;
;ldr  r0,=080008000h+10h*01h  ;works                    ;
;ldr  r0,=080008000h+20h*01h  ;works                    ;
;ldr  r0,=080008000h+02h*01h  ;works                    ;
;ldr  r0,=080008000h+20h*40h  ;works                    ;
;ldr  r0,=080008000h+02h*40h  ;works                    ;
;ldr  r0,=080008000h+3fh*41h  ;works                    ;
;ldr  r0,=080000000h          ;works                    ;
;ldr  r0,=0ffffffffh          ;works                    ;
 str  r0,[r1,REG_CODEC_SNDEXCNT]   ;aka [10145000h]     ;/

 ldr  r4,=REGBASE_CSND
 ldr  r0,=0ffffffffh          ;works, but silent        ;\
;ldr  r0,=0c0000000h          ;works, but silent        ;
 ldr  r0,=0c0008000h          ;works, good              ;
;ldr  r0,=0c0018000h          ;works, but silent        ;
;ldr  r0,=0c0010000h          ;works, but silent        ;
;ldr  r0,=080000000h          ;works, but silent        ;
;ldr  r0,=080008000h          ;works, but dissonant     ;
 str  r0,[r4,REG_CSND_CONTROL]     ;aka [10103000h]     ;/
The values marked "works, but silent" don't cause audible sound (but the waveforms are generated and can be displayed via capture unit).
The two values marked "works, but (very) dissonant" sound quite weird, not sure what happens there, maybe something gets screwed up, or maybe the output is mixed with echos from capture unit.

I didn't notice any volume changes when setting/clearing/changing the lower 6 bits or next higher 6 bits in SNDEXCNT register.
Maybe that bits affect only DSP volume, or one needs to enable/disable those bits before changing them?

For the channel output volume at 10103404h+(N*20h), bit0-15 is right channel, and bit16-31 is left channel. 8000h is max volume. And 0000h is silent.

And, the SPI-TSC init code, that's based on the code that you had posted some days ago, but I've been lazy and omitted several things...
Removed fixed-time delays (which, I wanted to remove them as far as possible anyways).
Removed wait-until-bit-gets-set/cleared stuff (btw. is that "masked-write-then-read" intended?) ("masked-read" would look more plausible).
Removed writes to filter/coefficients in bank 05h,0Ah,0Bh,0Ch.
Removed any non-SPI writes like GPIO or DSP etc.
Replaced read-modify-writes by absolute writes (based on the initial reset values, merged with the bits that you were changing).
With that changes, everything is still working for me. Or if your original code doesn't work... maybe I have even accidently improved something ; )

Anyways, the remaining SPI init looks as so:

Code: Select all

special_3ds_tsc_init_list:
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 01h*2+0, 001h          ;TSC[64h:01h]=01h     ;3DS Software Reset (?)
 db 1+1, 00h*2+0, 064h          ;--bank 64h (rewrite, in case above did reset it to bank zero)
 db 1+1, 43h*2+0, 011h          ;TSC[64h:43h]=11h     ;3DS
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 77h*2+0, 094h+1        ;TSC[65h:77h].bit0=1  ;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 39h*2+0, 000h+66h      ;TSC[00h:39h].bit's=1 ;DSi ADC DC Measurement 1
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 7Ah*2+0, 001h          ;TSC[65h:7Ah]=01h     ;3DS
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 22h*2+0, 000h+18h      ;TSC[64h:22h].bit's=1 ;3DS
 db 1+1, 43h*2+0, 011h+0        ;TSC[64h:43h].bit7=0  ;3DS
 db 1+1, 43h*2+0, 011h+80h      ;TSC[64h:43h].bit7=1  ;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 0Bh*2+0, 087h          ;TSC[00h:0Bh]=87h     ;DSi DAC NDAC Value
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 7Ch*2+0, 00Ah ;-1      ;TSC[64h:7Ch].bit0=0  ;3DS
 db 1+1, 22h*2+0, 018h ;-4      ;TSC[64h:22h].bit2=0  ;3DS
 db 1+1, 00h*2+0, 004h          ;--bank 04h
 db 1+6, 08h*2+0, 07Fh,0E1h,080h,01Fh,07Fh,0C1h  ;TSC[4:08h]=...  ;DSi some coeff's
 db 1+1, 00h*2+0, 005h          ;--bank 05h
;db 1+56 08h*2+0, .....
;db 1+56 48h*2+0, .....
 db 1+1, 00h*2+0, 001h          ;--bank 01h
 db 1+1, 30h*2+0, 040h ;+40h    ;TSC[01h:30h].bit6-7  ;DSi P-Terminal Delta-Sigma Mono ADC Channel Fine-Gain Input
 db 1+1, 31h*2+0, 040h ;+40h    ;TSC[01h:31h].bit6-7  ;DSi M-Terminal ADC Input Selection
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 33h*2+0, 003h          ;TSC[65h:33h]=03h     ;3DS
;XXX repeatedly clear TSC[65h:41h].bit0-5 and wait for TSC[65h:41h]=00h ?
;XXX repeatedly change TSC[65h:42h].bit0-1 and wait for TSC[65h:42h]=02h ?
 db 1+1, 00h*2+0, 001h          ;--bank 01h
 db 1+1, 2Fh*2+0, 02Bh          ;TSC[01h:2Fh]=2Bh     ;DSi MIC PGA
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 31h*2+0, 000h ;-44h    ;TSC[64h:31h].bit2,6=0;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 41h*2+0, 0FDh          ;TSC[00h:41h]=FDh     ;DSi DAC Left Volume Control
 db 1+1, 42h*2+0, 0FDh          ;TSC[00h:42h]=FDh     ;DSi DAC Right Volume Control
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 7Bh*2+0, 0ECh          ;TSC[64h:7Bh]=ECh     ;3DS
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 11h*2+0, 000h+10h      ;TSC[65h:11h].bit2-4  ;3DS
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 7Ah*2+0, 000h          ;TSC[64h:7Ah]=00h     ;3DS
 db 1+1, 78h*2+0, 000h          ;TSC[64h:78h]=00h     ;3DS
;XXX read TSC[64h:77h].bit2-3      ;3DS
 db 1+1, 77h*2+0, 00Ch ;+0Ch    ;TSC[64h:77h].bit2-3=1;3DS
;XXX wait for TSC[64h:26h].bit3,7   ?
;XXXX filters in banks 0Ah,0Ch,0Bh
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 76h*2+0, 014h+0C0h     ;TSC[64h:76h].bit6-7  ;3DS
;XXX wait for TSC[64h:25h].bit3,7   ?
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 0Ah*2+0, 00Ah          ;TSC[65h:0Ah]=0Ah     ;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 3Fh*2+0, 0D4h ;+0C0h   ;TSC[00h:3Fh].bit6,7  ;DSi DAC Data-Path Setup
 db 1+1, 40h*2+0, 000h          ;TSC[00h:40h]=00h     ;DSi DAC Volume Control
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 77h*2+0, 00Ch-0Ch      ;TSC[64h:77h].bit2-3=0;3DS
;XXX read A=TSC[00h:02h].bit0-3 ;Reserved (xxh) 
;XXX read B=TSC[00h:03h].bit4-6 ;Overtemperature OT Flag (reserved bits)
;XXX if A<=1 AND B<=2 then set below to 3Ch else to 1Ch
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 0Bh*2+0, 01Ch ;1Ch/3Ch ;TSC[65h:0Bh]=1Ch/3Ch ;3DS
 db 1+1, 0Ch*2+0, 004h          ;TSC[65h:0Ch]=04h     ;3DS
 db 1+1, 16h*2+0, 000h          ;TSC[65h:16h]=00h     ;3DS   ;\
 db 1+1, 17h*2+0, 000h          ;TSC[65h:17h]=00h     ;3DS   ;/
 db 1+1, 11h*2+0, 010h+0C0h     ;TSC[65h:11h].bit6,7  ;3DS
 db 1+1, 12h*2+0, 006h          ;TSC[65h:12h]=06h     ;3DS   ;\
 db 1+1, 13h*2+0, 006h          ;TSC[65h:13h]=06h     ;3DS   ;/
 db 1+1, 1Bh*2+0, 007h          ;TSC[65h:1Bh]=07h     ;3DS   ;\
 db 1+1, 1Ch*2+0, 007h          ;TSC[65h:1Ch]=07h     ;3DS   ;/
;- - - -
 db 0
.align 4

profi200
Posts: 22
Joined: Fri May 10, 2019 4:48 am

Re: 3DS reverse engineering

Post by profi200 » Sat Nov 30, 2019 6:16 pm

nocash wrote:
Sat Nov 30, 2019 4:41 pm
GPIO returns data=0 for bits with direction=output??? Then it would be unwise to use RMW on GPIO data (it would clear all other data bits).
Oops, brainfart. I meant writes are ignored when the GPIO is set to input.
nocash wrote:
Sat Nov 30, 2019 4:41 pm
I have tried to remove all init code, and now I have only two required register writes remaining (plus the SPI-TSC writes and CSND channel writes).
CFG11 and GPIO etc. seem to be all okay with their initial power-on values. MCU writes or SPI-Powerman writes apparently aren't needed either.
The two required writes are:
(before below writes, I am writing the SPI-TSC registers)
[10145000h]=080000000h ;CODEC SNDEXCNT
[10103000h]=0c0008000h ;CSND Control
(after above writes, I am writing the CSND channel registers)
If that values aren't working for you, try using headphones (which is what I have tested).

I have also tried some other settings for those registers:

Code: Select all

 ldr  r1,=REGBASE_CODEC                                 ;\
 ldr  r0,=0e0009000h+10h*41h  ;works                    ;
 ldr  r0,=0e0008000h+10h*41h  ;works                    ;
;ldr  r0,=0e000c000h+10h*41h  ;works                    ;
;ldr  r0,=0c0008000h+10h*41h  ;works, but very dissonant;
 ldr  r0,=0a0008000h+10h*41h  ;works                    ;
 ldr  r0,=080008000h+10h*41h  ;works                    ;
 ldr  r0,=020008000h+10h*41h  ;works, but silent        ;
;ldr  r0,=080008000h+10h*40h  ;works                    ;
;ldr  r0,=080008000h+10h*01h  ;works                    ;
;ldr  r0,=080008000h+20h*01h  ;works                    ;
;ldr  r0,=080008000h+02h*01h  ;works                    ;
;ldr  r0,=080008000h+20h*40h  ;works                    ;
;ldr  r0,=080008000h+02h*40h  ;works                    ;
;ldr  r0,=080008000h+3fh*41h  ;works                    ;
;ldr  r0,=080000000h          ;works                    ;
;ldr  r0,=0ffffffffh          ;works                    ;
 str  r0,[r1,REG_CODEC_SNDEXCNT]   ;aka [10145000h]     ;/

 ldr  r4,=REGBASE_CSND
 ldr  r0,=0ffffffffh          ;works, but silent        ;\
;ldr  r0,=0c0000000h          ;works, but silent        ;
 ldr  r0,=0c0008000h          ;works, good              ;
;ldr  r0,=0c0018000h          ;works, but silent        ;
;ldr  r0,=0c0010000h          ;works, but silent        ;
;ldr  r0,=080000000h          ;works, but silent        ;
;ldr  r0,=080008000h          ;works, but dissonant     ;
 str  r0,[r4,REG_CSND_CONTROL]     ;aka [10103000h]     ;/
The values marked "works, but silent" don't cause audible sound (but the waveforms are generated and can be displayed via capture unit).
The two values marked "works, but (very) dissonant" sound quite weird, not sure what happens there, maybe something gets screwed up, or maybe the output is mixed with echos from capture unit.

I didn't notice any volume changes when setting/clearing/changing the lower 6 bits or next higher 6 bits in SNDEXCNT register.
Maybe that bits affect only DSP volume, or one needs to enable/disable those bits before changing them?

For the channel output volume at 10103404h+(N*20h), bit0-15 is right channel, and bit16-31 is left channel. 8000h is max volume. And 0000h is silent.

And, the SPI-TSC init code, that's based on the code that you had posted some days ago, but I've been lazy and omitted several things...
Removed fixed-time delays (which, I wanted to remove them as far as possible anyways).
Removed wait-until-bit-gets-set/cleared stuff (btw. is that "masked-write-then-read" intended?) ("masked-read" would look more plausible).
Removed writes to filter/coefficients in bank 05h,0Ah,0Bh,0Ch.
Removed any non-SPI writes like GPIO or DSP etc.
Replaced read-modify-writes by absolute writes (based on the initial reset values, merged with the bits that you were changing).
With that changes, everything is still working for me. Or if your original code doesn't work... maybe I have even accidently improved something ; )

Anyways, the remaining SPI init looks as so:

Code: Select all

special_3ds_tsc_init_list:
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 01h*2+0, 001h          ;TSC[64h:01h]=01h     ;3DS Software Reset (?)
 db 1+1, 00h*2+0, 064h          ;--bank 64h (rewrite, in case above did reset it to bank zero)
 db 1+1, 43h*2+0, 011h          ;TSC[64h:43h]=11h     ;3DS
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 77h*2+0, 094h+1        ;TSC[65h:77h].bit0=1  ;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 39h*2+0, 000h+66h      ;TSC[00h:39h].bit's=1 ;DSi ADC DC Measurement 1
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 7Ah*2+0, 001h          ;TSC[65h:7Ah]=01h     ;3DS
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 22h*2+0, 000h+18h      ;TSC[64h:22h].bit's=1 ;3DS
 db 1+1, 43h*2+0, 011h+0        ;TSC[64h:43h].bit7=0  ;3DS
 db 1+1, 43h*2+0, 011h+80h      ;TSC[64h:43h].bit7=1  ;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 0Bh*2+0, 087h          ;TSC[00h:0Bh]=87h     ;DSi DAC NDAC Value
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 7Ch*2+0, 00Ah ;-1      ;TSC[64h:7Ch].bit0=0  ;3DS
 db 1+1, 22h*2+0, 018h ;-4      ;TSC[64h:22h].bit2=0  ;3DS
 db 1+1, 00h*2+0, 004h          ;--bank 04h
 db 1+6, 08h*2+0, 07Fh,0E1h,080h,01Fh,07Fh,0C1h  ;TSC[4:08h]=...  ;DSi some coeff's
 db 1+1, 00h*2+0, 005h          ;--bank 05h
;db 1+56 08h*2+0, .....
;db 1+56 48h*2+0, .....
 db 1+1, 00h*2+0, 001h          ;--bank 01h
 db 1+1, 30h*2+0, 040h ;+40h    ;TSC[01h:30h].bit6-7  ;DSi P-Terminal Delta-Sigma Mono ADC Channel Fine-Gain Input
 db 1+1, 31h*2+0, 040h ;+40h    ;TSC[01h:31h].bit6-7  ;DSi M-Terminal ADC Input Selection
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 33h*2+0, 003h          ;TSC[65h:33h]=03h     ;3DS
;XXX repeatedly clear TSC[65h:41h].bit0-5 and wait for TSC[65h:41h]=00h ?
;XXX repeatedly change TSC[65h:42h].bit0-1 and wait for TSC[65h:42h]=02h ?
 db 1+1, 00h*2+0, 001h          ;--bank 01h
 db 1+1, 2Fh*2+0, 02Bh          ;TSC[01h:2Fh]=2Bh     ;DSi MIC PGA
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 31h*2+0, 000h ;-44h    ;TSC[64h:31h].bit2,6=0;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 41h*2+0, 0FDh          ;TSC[00h:41h]=FDh     ;DSi DAC Left Volume Control
 db 1+1, 42h*2+0, 0FDh          ;TSC[00h:42h]=FDh     ;DSi DAC Right Volume Control
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 7Bh*2+0, 0ECh          ;TSC[64h:7Bh]=ECh     ;3DS
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 11h*2+0, 000h+10h      ;TSC[65h:11h].bit2-4  ;3DS
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 7Ah*2+0, 000h          ;TSC[64h:7Ah]=00h     ;3DS
 db 1+1, 78h*2+0, 000h          ;TSC[64h:78h]=00h     ;3DS
;XXX read TSC[64h:77h].bit2-3      ;3DS
 db 1+1, 77h*2+0, 00Ch ;+0Ch    ;TSC[64h:77h].bit2-3=1;3DS
;XXX wait for TSC[64h:26h].bit3,7   ?
;XXXX filters in banks 0Ah,0Ch,0Bh
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 76h*2+0, 014h+0C0h     ;TSC[64h:76h].bit6-7  ;3DS
;XXX wait for TSC[64h:25h].bit3,7   ?
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 0Ah*2+0, 00Ah          ;TSC[65h:0Ah]=0Ah     ;3DS
 db 1+1, 00h*2+0, 000h          ;--bank 00h
 db 1+1, 3Fh*2+0, 0D4h ;+0C0h   ;TSC[00h:3Fh].bit6,7  ;DSi DAC Data-Path Setup
 db 1+1, 40h*2+0, 000h          ;TSC[00h:40h]=00h     ;DSi DAC Volume Control
 db 1+1, 00h*2+0, 064h          ;--bank 64h
 db 1+1, 77h*2+0, 00Ch-0Ch      ;TSC[64h:77h].bit2-3=0;3DS
;XXX read A=TSC[00h:02h].bit0-3 ;Reserved (xxh) 
;XXX read B=TSC[00h:03h].bit4-6 ;Overtemperature OT Flag (reserved bits)
;XXX if A<=1 AND B<=2 then set below to 3Ch else to 1Ch
 db 1+1, 00h*2+0, 065h          ;--bank 65h
 db 1+1, 0Bh*2+0, 01Ch ;1Ch/3Ch ;TSC[65h:0Bh]=1Ch/3Ch ;3DS
 db 1+1, 0Ch*2+0, 004h          ;TSC[65h:0Ch]=04h     ;3DS
 db 1+1, 16h*2+0, 000h          ;TSC[65h:16h]=00h     ;3DS   ;\
 db 1+1, 17h*2+0, 000h          ;TSC[65h:17h]=00h     ;3DS   ;/
 db 1+1, 11h*2+0, 010h+0C0h     ;TSC[65h:11h].bit6,7  ;3DS
 db 1+1, 12h*2+0, 006h          ;TSC[65h:12h]=06h     ;3DS   ;\
 db 1+1, 13h*2+0, 006h          ;TSC[65h:13h]=06h     ;3DS   ;/
 db 1+1, 1Bh*2+0, 007h          ;TSC[65h:1Bh]=07h     ;3DS   ;\
 db 1+1, 1Ch*2+0, 007h          ;TSC[65h:1Ch]=07h     ;3DS   ;/
;- - - -
 db 0
.align 4
The write to 0x10145000 did it for me and CSND is playing now through the speakers :) Investigating a bit more it seems to be bit 13-14 of the first reg that screwed up sound output and it works by setting them to 0. Maybe the init is configuring the wrong frequency on the codec side? I need to find out which reg controls this. This is definitely not what Horizon OS is doing because as said it never touches these parts of the reg after init. Only the DSP volume is changed to either 0x20 or 0.

edit:
What the f...? It's not these bits. It's simply enough to rewrite these 2 regs (maybe I2S somehow hung and resets because of this??).

edit2:
Oh damn. Do you see the mistake? :) https://gist.github.com/profi200/492664 ... dec-c-L116

Took a quick look into the CSND module and here is how to calculate the correct frequency:

Code: Select all

	*((vs16*)0x10103502) = -(s16)(67027964u / 16360);
In this case the frequency is 16360 Hz. This sounds now exactly like an userland homebrew firing up a square wave channel with the same parameters.

edit3:
Figured out automatic output switching. Bank 0x64 reg 0x45 bit 4 controls the current output and bit 5 selects between manual mode (1) or automatic (0). There is a catch though. Bit 4 must match the current GPIO state or it will be stuck despite enabling auto mode.

nocash
Posts: 1086
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash » Mon Dec 02, 2019 5:40 pm

profi200 wrote:
Sat Nov 30, 2019 6:16 pm
Figured out automatic output switching. Bank 0x64 reg 0x45 bit 4 controls the current output and bit 5 selects between manual mode (1) or automatic (0). There is a catch though. Bit 4 must match the current GPIO state or it will be stuck despite enabling auto mode.
Ah, okay, but once when auto-mode is initialized with correct values... it does then start to work automatically, without further manual updating?
After you had posted that, I have checked if any registers change on headphone insert/eject... TSC[64h:2Ch] bit0 seems to be 1=connected, and bit1 sometimes gets set too, but not always.
I guess I should also do some headphone tests on DSi to see if it does switch speakers/headphones automatically, I haven't tested much of the DSi sound hardware yet; because I always had the volume set to zero for avoiding the System Menu music.

Looking at the code comments that you had added recently, I figured out that you had derived most of the TSC initialization constants from the HWCAL0/1.dat file's entries at file offset 2E0h and up:

Code: Select all

Codec (CDC) (aka TSC)
  2E0h 1    u8 DriverGainHP       ;TSC[65h:0Ch].bit3-7
  2E1h 1    u8 DriverGainSP       ;TSC[65h:12h].bit2-7 and TSC[65h:13h].bit2-7
  2E2h 1    u8 AnalogVolumeHP     ;TSC[65h:16h] and TSC[65h:17h]
  2E3h 1    u8 AnalogVolumeSP     ;TSC[65h:1Bh] and TSC[65h:1Ch]
  2E4h 1    s8 ShutterVolume0     ;TSC[00h:41h] and TSC[00h:42h] ;\maybe camera
  2E5h 1    s8 ShutterVolume1     ;TSC[64h:7Bh]                  ;/sound fx?
  2E6h 1    u8 MicrophoneBias     ;TSC[65h:33h]
  2E7h 1    u8 QuickCharge (???)  ;TSC[65h:42h].bit0-1
  2E8h 1    u8 PGA_GAIN (mic)     ;TSC[65h:41h].bit0-5
  2E9h 3    u8 reserved[3]
  2ECh 1Eh  s16 FilterHP32[3*5]   ;TSC[0Bh:02h..1Fh] and TSC[0Bh:42h..5Fh]
  30Ah 1Eh  s16 FilterHP47[3*5]   ;TSC[0Bh:20h..3Dh] and TSC[0Bh:60h..7Dh]
  328h 1Eh  s16 FilterSP32[3*5]   ;TSC[0Ch:02h..1Fh] and TSC[0Ch:42h..5Fh]
  346h 1Eh  s16 FilterSP47[3*5]   ;TSC[0Ch:20h..3Dh] and TSC[0Ch:60h..7Dh]
  364h 38h  s16 FilterMic32[1+2+ (1+4)*5] ;TSC[05h:08h..3Fh], TSC[05h:48h..7Fh]
  39Ch 38h  s16 FilterMic47[1+2+ (1+4)*5] ;(...same as above...?)
  3D4h 38h  s16 FilterFree [1+2+ (1+4)*5] ;TSC[08h:xxh, 09h;xxh, 0Ah:xxh]
  40Ch 1    u8 AnalogInterval          ;\
  40Dh 1    u8 AnalogStabilize         ;
  40Eh 1    u8 AnalogPrecharge         ;
  40Fh 1    u8 AnalogSense             ; maybe TSC[67h:xxh] ?
  410h 1    u8 AnalogDebounce          ;
  411h 1    u8 Analog_XP_Pullup        ;
  412h 1    u8 YM_Driver               ;/
  413h 1    u8 reserved
  414h 2    Checksum?
  416h 2    Zero
Good to know by itself, and also good because the names for the .dat file entries do imply the TSC register purposes.
I am not sure what ShutterVolume means; my only idea would be that it might override slider-volume when playing camera shutter sounds and other alerts(?)
And also unsure what the AnalogXxx values mean, AnalogDebounce sounds touchscreen related, or maybe microphone related.
What does FilterMic47 do? You seem to leave that entry unused, apart from the comment on saying that it is same as FilterMic32... does that mean one of the filters initialized with FilterMic32 is actually FilterMic47?
Oh, and one general thing: The DSi doesn't seem to use any miniDSP instructions anywhere (I don't even know if the TSC chip does actually support them). Would be good to know if the 3ds is using miniDSP code somewhere.

Here is a summary of the used TSC registers, based on your code and on the HWCAL entry names, and with some notes on some further "unused" registers with nonzero values.

Code: Select all

3DS TSC, Register Summary
-------------------------

Page Selection
  TSC[xxh:00h]=page    ;Page (each TSC SPI bus probably has own page+index?)

Page 00h-01h (DSi Registers)
  TSC[00h:02h]=read    ;DSi Undocumented status (reserved bits)
  TSC[00h:03h]=read    ;DSi Overtemperature OT Flag (reserved bits)
  TSC[00h:0Bh]=87h     ;DSi DAC NDAC Value
  TSC[00h:39h]=66h     ;DSi ADC DC Measurement 1 (reset=00h, ORed with 66h)
  TSC[00h:3Fh]=D4h     ;DSi DAC Data-Path Setup  (reset=D4h, ORed with C0h)
  TSC[00h:40h]=00h     ;DSi DAC Volume Control
  TSC[00h:41h]=FDh     ;DSi DAC Left Volume Control  ;\aka 3DS     ;HWCAL[2E4h]
  TSC[00h:42h]=FDh     ;DSi DAC Right Volume Control ;/ShutterVol0 ;HWCAL[2E4h]
  TSC[01h:2Fh]=2Bh     ;DSi MIC PGA
  TSC[01h:30h]=40h     ;DSi P-Terminal ADC Channel Fine-Gain Input (reset=40h)
  TSC[01h:31h]=40h     ;DSi M-Terminal ADC Input Selection         (reset=40h)
The 3DS does usually access only the registers mentioned above (but there are
many more DSi-style registers in page 00h,01h,03h; see DSi chapter for
details).

Page 04h-0Ch (DSi Coefficient RAM)
  TSC[04h:08h-0Dh]=... ;DSi Some coeff's (7Fh,E1h,80h,1Fh,7Fh,C1h)
  TSC[05h:08h-3Fh]=... ;3DS FilterMic32                      ;HWCAL[364h-39Bh]
  TSC[05h:48h-7Fh]=... ;3DS FilterMic32, too? Or Mic47?      ;HWCAL[xxxh..]
  TSC[08h:0Ch-3Dh]=... ;3DS FilterFreeB   ;\                 ;HWCAL[3DAh-40Bh]
  TSC[08h:4Ch-7Dh]=... ;3DS FilterFreeB'  ; initialized for  ;HWCAL[3DAh-40Bh]
  TSC[09h:02h-07h]=... ;3DS FilterFreeA   ; non-GBA only     ;HWCAL[3D4h-3D9h]
  TSC[09h:08h-0Dh]=... ;3DS FilterFreeA'  ;/                 ;HWCAL[3D4h-3D9h]
  TSC[0Ah:02h-07h]=... ;3DS FilterFreeA''                    ;HWCAL[3D4h-3D9h]
  TSC[0Ah:0Ch-3Dh]=... ;3DS FilterFreeB''                    ;HWCAL[3DAh-40Bh]
  TSC[0Bh:02h-1Fh]=... ;3DS FilterHP32                       ;HWCAL[2ECh-309h]
  TSC[0Bh:20h-3Dh]=... ;3DS FilterHP47                       ;HWCAL[30Ah-327h]
  TSC[0Bh:42h-5Fh]=... ;3DS FilterHP32'                      ;HWCAL[2ECh-309h]
  TSC[0Bh:60h-7Dh]=... ;3DS FilterHP47'                      ;HWCAL[30Ah-327h]
  TSC[0Ch:02h-1Fh]=... ;3DS FilterSP32                       ;HWCAL[328h-345h]
  TSC[0Ch:20h-3Dh]=... ;3DS FilterSP47                       ;HWCAL[346h-363h]
  TSC[0Ch:42h-5Fh]=... ;3DS FilterSP32'                      ;HWCAL[328h-345h]
  TSC[0Ch:60h-7Dh]=... ;3DS FilterSP47'                      ;HWCAL[346h-363h]
The above coefficient RAM pages exists on DSi, too. However, the DSi is usually
initializing only those in page 04h.
Unknown how the 3DS is using the extra coefficients... does it use miniDSP
instructions for that?

Page 64h (3DS Sound/Microphone Config)
  TSC[64h:01h]=01h     ;3DS Software Reset (?)
  TSC[64h:22h]=18h     ;3DS ? (reset=00h, ORed with 18h, later bit2=cleared)
  TSC[64h:25h]=read    ;3DS status, wait for bit3,7
  TSC[64h:26h]=read    ;3DS status, wait for bit3,7
  TSC[64h:2Ch]         ;unused, but nonzero  ;bit0,1=headphone connect status
  TSC[64h:30h]         ;unused, but nonzero
  TSC[64h:31h]=00h/44h ;3DS ? (reset=00h) (GBA:00h, Other:44h)
  TSC[64h:43h]=11h/91h ;3DS set to 11h, later toggles bit=0 then bit7=1
  TSC[64h:44h]         ;unused, but nonzero
  TSC[64h:45h]=20h/30h ;3DS Speaker off (reset=00h, later=20h, 30h=speakerOff)
  TSC[64h:75h]         ;unused, but nonzero
  TSC[64h:76h]=14h/D4h ;3DS ? (reset=14h, ORed with C0h)
  TSC[64h:77h]=0Ch/00h ;3DS ? (reset=0Ch, later clear bit2,3 after coeff init)
  TSC[64h:78h]=00h     ;3DS ?
  TSC[64h:7Ah]=00h     ;3DS ?
  TSC[64h:7Bh]=ECh     ;3DS ShutterVolume1                   ;HWCAL[2E5h]
  TSC[64h:7Ch]=0Ah     ;3DS ? (reset=0Ah, later clears bit0)

Page 65h (3DS Sound/Microphone Gains)
  TSC[65h:0Ah]=0Ah     ;3DS ?
  TSC[65h:0Bh]=1Ch/3Ch ;3DS ?  ... depends on TSC[00h:02h..03h]
  TSC[65h:0Ch]=04h     ;3DS DriverGainHP                     ;HWCAL[2E0h]*8+4
  TSC[65h:11h]=10h/D0h ;3DS ? (reset=00h, ORed with 10h, later ORed with C0h)
  TSC[65h:12h]=06h     ;3DS DriverGainSP    ;\maybe left?    ;HWCAL[2E1h]*4+2
  TSC[65h:13h]=06h     ;3DS DriverGainSP'   ;/      right?   ;HWCAL[2E1h]*4+2
  TSC[65h:16h]=00h     ;3DS AnalogVolumeHP  ;\maybe left?    ;HWCAL[2E2h]
  TSC[65h:17h]=00h     ;3DS AnalogVolumeHP' ;/      right?   ;HWCAL[2E2h]
  TSC[65h:1Bh]=07h     ;3DS AnalogVolumeSP  ;\maybe left?    ;HWCAL[2E3h]
  TSC[65h:1Ch]=07h     ;3DS AnalogVolumeSP' ;/      right?   ;HWCAL[2E3h]
  TSC[65h:33h]=03h     ;3DS MicrophoneBias                   ;HWCAL[2E6h]
  TSC[65h:41h]=00h+wait;3DS PGA_GAIN (mic)      (bit0-5)     ;HWCAL[2E8h]
  TSC[65h:42h]=02h+wait;3DS QuickCharge (what?) (bit0-1)     ;HWCAL[2E7h]
  TSC[65h:47h,4Bh,4Ch,4Dh,4Eh,52h,53h]  ;unused, but nonzero
  TSC[65h:77h]=94h/95h ;3DS ? (reset=94h, ORed with 01h)
  TSC[65h:78h]         ;unused, but nonzero
  TSC[65h:7Ah]=01h     ;3DS ?

Page 67h,FBh (3DS Touchscreen/Circle Pad)
  TSC[67h:17h]=43h     ;3DS ?
  TSC[67h:19h]=69h     ;3DS ?
  TSC[67h:1Bh]=80h     ;3DS ?
  TSC[67h:24h]=98h/18h ;3DS bit7=0=touchscreen.on  ;bit2=1=has new touchdata?
  TSC[67h:25h]=43h/53h ;3DS bit5-2=0100b=touchscreen.on
  TSC[67h:26h]=00h/ECh ;3DS bit7=1=touchscreen.on  ;bit1=1=had old touchdata?
  TSC[67h:27h]=11h     ;3DS ?
  TSC[FBh:01h]=read    ;3DS fifo 26x16bit; 5xTSC.x, 5xTSC.y, 8xCPAD.y, 8xCPAD.x
There are many more unused/zero registers, I haven't tried yet if any of them are R/W, or if there are other ways to get nonzero values into them.

Edit: Are that "TIMER_sleepMs(nn)" based on timings from nintendo? I guess some of the delays might be there to avoid minor speaker noise during init, and some delays might be actually required for working initialization.
The old TSC datasheet mentions a 100us delay between SoftReset and Coefficient RAM initialization, something like that might be needed here, too. I have some doubts about needing a 40ms delay after SoftReset though.

Post Reply