diff --git a/include/arm11/hardware/codec.h b/include/arm11/hardware/codec.h index 08d6639..59a4aa5 100644 --- a/include/arm11/hardware/codec.h +++ b/include/arm11/hardware/codec.h @@ -21,6 +21,7 @@ #include "types.h" + /** * @brief Initialize CODEC for Circle-Pad/Touchscreen/Sound. */ diff --git a/include/arm11/hardware/csnd.h b/include/arm11/hardware/csnd.h new file mode 100644 index 0000000..e3786b5 --- /dev/null +++ b/include/arm11/hardware/csnd.h @@ -0,0 +1,186 @@ +#pragma once + +/* + * This file is part of fastboot 3DS + * Copyright (C) 2019 derrek, profi200 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types.h" +#include "mem_map.h" + + + +#define CSND_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x3000) +#define REG_CSND_MASTER_VOL *((vu16*)(CSND_REGS_BASE + 0x000)) // CSND master volume 0-0x8000 +#define REG_CSND_UNK_CNT *((vu16*)(CSND_REGS_BASE + 0x002)) + +// 32 sound channels. PSG on channel 8-13 and noise 14-15. +#define REG_CSND_CH_CNT(n) *((vu16*)(CSND_REGS_BASE + 0x400 + ((n) * 32))) +#define REG_CSND_CH_SR(n) *((vs16*)(CSND_REGS_BASE + 0x402 + ((n) * 32))) // Samplerate +#define REG_CSND_CH_VOL_R(n) *((vu16*)(CSND_REGS_BASE + 0x404 + ((n) * 32))) // 0-0x8000 +#define REG_CSND_CH_VOL_L(n) *((vu16*)(CSND_REGS_BASE + 0x406 + ((n) * 32))) // 0-0x8000 +#define REG_CSND_CH_VOL(n) *((vu32*)(CSND_REGS_BASE + 0x404 + ((n) * 32))) // R and L combined +#define REG_CSND_CH_CAPVOL_R(n) *((vu16*)(CSND_REGS_BASE + 0x408 + ((n) * 32))) // Unconfirmed. 0-0x8000 +#define REG_CSND_CH_CAPVOL_L(n) *((vu16*)(CSND_REGS_BASE + 0x40A + ((n) * 32))) // Unconfirmed. 0-0x8000 +#define REG_CSND_CH_CAPVOL(n) *((vu32*)(CSND_REGS_BASE + 0x408 + ((n) * 32))) // R and L combined +#define REG_CSND_CH_ST_ADDR(n) *((vu32*)(CSND_REGS_BASE + 0x40C + ((n) * 32))) // Start address and playback position +#define REG_CSND_CH_SIZE(n) *((vu32*)(CSND_REGS_BASE + 0x410 + ((n) * 32))) // Size in bytes +#define REG_CSND_CH_LP_ADDR(n) *((vu32*)(CSND_REGS_BASE + 0x414 + ((n) * 32))) // Loop restart address +#define REG_CSND_CH_ST_ADPCM(n) *((vu32*)(CSND_REGS_BASE + 0x418 + ((n) * 32))) // Start IMA-ADPCM state +#define REG_CSND_CH_LP_ADPCM(n) *((vu32*)(CSND_REGS_BASE + 0x41C + ((n) * 32))) // Loop Restart IMA-ADPCM state + +// 2 capture units for right and left side. +#define REG_CSND_CAP_CNT(n) *((vu16*)(CSND_REGS_BASE + 0x800 + ((n) * 16))) +#define REG_CSND_CAP_SR(n) *((vs16*)(CSND_REGS_BASE + 0x804 + ((n) * 16))) // Samplerate +#define REG_CSND_CAP_SIZE(n) *((vu32*)(CSND_REGS_BASE + 0x808 + ((n) * 16))) // Capture length in bytes +#define REG_CSND_CAP_ADDR(n) *((vu32*)(CSND_REGS_BASE + 0x80C + ((n) * 16))) // Address + + +// REG_CSND_CH_CNT +#define CSND_CH_DUTY(d) (d) // For PSG (channel 8-13) only. In 12.5% units. 0 = high/12.5%. +#define CSND_CH_LINEAR_INTERP (1u<<6) // Linear interpolation +#define CSND_CH_HOLD (1u<<7) // Hold last sample after one shot. +#define CSND_CH_PLAYING (1u<<14) +#define CSND_CH_START (1u<<15) + +enum +{ + CSND_CH_RPT_MANUAL = 0u<<10, + CSND_CH_RPT_LOOP = 1u<<10, + CSND_CH_RPT_ONE_SHOT = 2u<<10 +}; + +enum +{ + CSND_CH_FMT_PCM8 = 0u<<12, // Signed PCM8 + CSND_CH_FMT_PCM16 = 1u<<12, // Signed PCM16 little endian + CSND_CH_FMT_IMA_ADPCM = 2u<<12, + CSND_CH_FMT_PSG_NOISE = 3u<<12 +}; + + +// REG_CSND_CAP_CNT +#define CSND_CAP_RPT_LOOP (0u) +#define CSND_CAP_RPT_ONE_SHOT (1u) +#define CSND_CAP_FMT_PCM16 (0u) // Signed PCM16 little endian +#define CSND_CAP_FMT_PCM8 (1u<<1) // Signed PCM8 +#define CSND_CAP_UNK2 (1u<<2) +#define CSND_CAP_START (1u<<15) + + +// Samplerate helpers +#define CSND_SAMPLERATE(s) (-(s16)(67027964u / (s))) +#define CSND_PSG_FREQ(f) (CSND_SAMPLERATE(32u * (f))) + + + +/** + * @brief Initializes the CSND hardware. + */ +void CSND_init(void); + +/** + * @brief Calculates the left and right volumes. + * + * @param[in] lvol The left volume. + * @param[in] rvol The right volume. + * + * @return The volume pair needed for CSND_setupCh(). + */ +static inline u32 CSND_calcVol(float lvol, float rvol) +{ + return (u32)(lvol * 32768.0f)<<16 | (u16)(rvol * 32768.0f); +} + +/** + * @brief Sets up a channel for sound playback (in paused state). + * + * @param[in] ch The sound channel. 0-31. + * @param[in] sampleRate The sample rate. + * @param[in] vol The L/R volume pair. + * @param[in] data The start address. + * @param[in] data2 The loop restart address. + * @param[in] size The size. + * @param[in] flags The flags. + */ +void CSND_setupCh(u8 ch, s16 sampleRate, u32 vol, const u32 *const data, const u32 *const data2, u32 size, u16 flags); + +/** + * @brief Pauses or unpauses a sound channel. + * + * @param[in] ch The sound channel. 0-31. + * @param[in] playing The play state. + */ +static inline void CSND_setChState(u8 ch, bool playing) +{ + REG_CSND_CH_CNT(ch) = (REG_CSND_CH_CNT(ch) & ~CSND_CH_PLAYING) | ((u16)playing<<14); +} + +/** + * @brief Returns the current audio buffer position (address). + * + * @param[in] ch The sound channel. 0-31. + * + * @return The playback position (address). + */ +static inline u32 CSND_getChPos(u8 ch) +{ + return REG_CSND_CH_ST_ADDR(ch); +} + +/** + * @brief Stops a sound channel. + * + * @param[in] ch The sound channel. 0-31. + */ +static inline void CSND_stopCh(u8 ch) +{ + REG_CSND_CH_CNT(ch) = 0; // Stop +} + + +/** + * @brief Captures the output of all left/right sound channels combined. + * + * @param[in] ch The capture side. 0 = right, 1 = left. + * @param[in] sampleRate The sample rate. + * @param data The output address. + * @param[in] size The size. + * @param[in] flags The flags. + */ +void CSND_startCap(u8 ch, s16 sampleRate, u32 *const data, u32 size, u16 flags); + +/** + * @brief Returns the current capture buffer position (address). + * + * @param[in] ch The capture side. 0 = right, 1 = left. + * + * @return The capture position (address). + */ +static inline u32 CSND_getCapPos(u8 ch) +{ + return REG_CSND_CAP_ADDR(ch); +} + +/** + * @brief Stops a capture channel. + * + * @param[in] ch The capture side. 0 = right, 1 = left. + */ +static inline void CSND_stopCap(u8 ch) +{ + REG_CSND_CAP_CNT(ch) = 0; +} diff --git a/source/arm11/hardware/codec.c b/source/arm11/hardware/codec.c index 46c39ae..a068443 100644 --- a/source/arm11/hardware/codec.c +++ b/source/arm11/hardware/codec.c @@ -86,14 +86,14 @@ static void codecSwitchBank(u8 bank) { static u8 curBank = 0x63; - if(bank != curBank) + if(curBank != bank) { + curBank = bank; + alignas(4) u8 inBuf[4]; inBuf[0] = 0; // Write inBuf[1] = bank; NSPI_writeRead(NSPI_DEV_CTR_CODEC, (u32*)inBuf, NULL, 2, 0, true); - - curBank = bank; } } @@ -209,6 +209,21 @@ codecMaskReg(0x67, 0x24, 0x80, 0x80); } +static void codecLegacyStuff(bool enabled) +{ + if(enabled) + { + *((vu16*)0x10141114) |= 2u; + *((vu16*)0x10141116) |= 2u; + codecMaskReg(0x67, 0x25, 0x40, 0x40); + } + else + { + codecMaskReg(0x67, 0x25, 0, 0x40); + *((vu16*)0x10141114) &= ~2u; + } +} + void CODEC_init(void) { @@ -223,6 +238,125 @@ CodecCal *const cal = &fallbackCal; codecSwapCalibrationData(cal); // Come the fuck on. Why is this not stored in the correct endianness? + // 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); // Headphone jack IRQ + //codecMaskReg(0x64, 0x45, (*((vu8*)0x10147010) & 1u)<<4 | 1u<<5, 0x30); // GPIO bitmask 8. + codecMaskReg(0x64, 0x45, 0, 0x30); // With automatic output switching + codecMaskReg(0x64, 0x43, 0, 0x80); + codecMaskReg(0x64, 0x43, 0x80, 0x80); + codecWriteReg(0, 0xB, 0x87); + codecMaskReg(0x64, 0x7C, 0, 1); + + // sub_3257FC() + codecMaskReg(0x64, 0x22, 0, 4); + // In AgbBg this is swapped at runtime. + alignas(4) static const u16 unkData1[3] = {0xE17F, 0x1F80, 0xC17F}; + codecWriteRegBuf(4, 8, (u32*)unkData1, 6); + codecWriteRegBuf(5, 8, (u32*)cal->filterMic32, 56); + codecWriteRegBuf(5, 0x48, (u32*)cal->filterMic47, 56); + codecMaskReg(1, 0x30, 0x40, 0xC0); + codecMaskReg(1, 0x31, 0x40, 0xC0); + codecWriteReg(0x65, 0x33, cal->microphoneBias); + codecMaskWaitReg(0x65, 0x41, cal->PGA_GAIN, 0x3F); + codecMaskWaitReg(0x65, 0x42, cal->quickCharge, 3); + codecWriteReg(1, 0x2F, 0x2Bu & 0x7Fu); + codecMaskReg(0x64, 0x31, 0x44, 0x44); // AgbBg uses val = 0 here + codecWriteReg(0, 0x41, cal->shutterVolume[0]); + codecWriteReg(0, 0x42, cal->shutterVolume[0]); + codecWriteReg(0x64, 0x7B, cal->shutterVolume[1]); + + // Sound stuff starts here + GPIO_config(GPIO_4_0, GPIO_OUTPUT); + GPIO_write(GPIO_4_0, 1); // GPIO bitmask 0x40 + TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO. + *((vu16*)0x10145000) = 0xE800u; // 47.61 kHz. codec module writes 0xC800 instead. + *((vu16*)0x10145002) = 0xE000u; + codecMaskReg(0x65, 0x11, 0x10, 0x1C); + codecWriteReg(0x64, 0x7A, 0); + codecWriteReg(0x64, 0x78, 0); + { // This code block is missing in AgbBg but present in codec module. + const bool flag = (~codecReadReg(0, 0x40) & 0xCu) == 0; + codecMaskReg(0, 0x3F, 0, 0xC0); + codecWriteReg(0, 0x40, 0xC); + for(u32 i = 0; i < 100; i++) // Some kind of timeout? No error checking. + { + if(!(~codecReadReg(0x64, 0x26) & 0x44u)) break; + TIMER_sleepMs(1); + } + codecWriteRegBuf(9, 2, (u32*)cal->filterFree, 6); + codecWriteRegBuf(8, 0xC, (u32*)&cal->filterFree[3], 50); + codecWriteRegBuf(9, 8, (u32*)cal->filterFree, 6); + codecWriteRegBuf(8, 0x4C, (u32*)&cal->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++) // Some kind of timeout? No error checking. + { + if(!(~codecReadReg(0x64, 0x26) & 0x88u)) break; + TIMER_sleepMs(1); + } + codecWriteRegBuf(0xA, 2, (u32*)cal->filterFree, 6); + codecWriteRegBuf(0xA, 0xC, (u32*)&cal->filterFree[3], 50); + if(!flag) codecMaskReg(0x64, 0x77, 0, 0xC); + } + + codecWriteRegBuf(0xC, 2, (u32*)cal->filterSP32, 30); + codecWriteRegBuf(0xC, 0x42, (u32*)cal->filterSP32, 30); + codecWriteRegBuf(0xC, 0x20, (u32*)cal->filterSP47, 30); + codecWriteRegBuf(0xC, 0x60, (u32*)cal->filterSP47, 30); + codecWriteRegBuf(0xB, 2, (u32*)cal->filterHP32, 30); + codecWriteRegBuf(0xB, 0x42, (u32*)cal->filterHP32, 30); + codecWriteRegBuf(0xB, 0x20, (u32*)cal->filterHP47, 30); + codecWriteRegBuf(0xB, 0x60, (u32*)cal->filterHP47, 30); + codecMaskReg(0x64, 0x76, 0xC0, 0xC0); + TIMER_sleepMs(10); + for(u32 i = 0; i < 100; i++) // Some kind of timeout? No error checking. + { + 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, 0, 0xC); + + 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, (cal->driverGainHP<<3) | 4); + codecWriteReg(0x65, 0x16, cal->analogVolumeHP); + codecWriteReg(0x65, 0x17, cal->analogVolumeHP); + codecMaskReg(0x65, 0x11, 0xC0, 0xC0); + codecWriteReg(0x65, 0x12, (cal->driverGainSP<<2) | 2); + codecWriteReg(0x65, 0x13, (cal->driverGainSP<<2) | 2); + codecWriteReg(0x65, 0x1B, cal->analogVolumeSP); + codecWriteReg(0x65, 0x1C, cal->analogVolumeSP); + TIMER_sleepMs(38); + GPIO_write(GPIO_4_0, 0); // GPIO bitmask 0x40 + TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO. + + // Circle pad codecWriteReg(0x67, 0x24, 0x98); codecWriteReg(0x67, 0x26, 0x00); @@ -236,9 +370,76 @@ codecWriteReg(0x67, 0x24, 0x18); codecWriteReg(0x67, 0x25, 0x53); + // Not needed? + //I2C_writeReg(I2C_DEV_CTR_MCU, 0x26, I2C_readReg(I2C_DEV_CTR_MCU, 0x26) | 0x10); + codecEnableTouchscreen(); } +bool touchscreenState = false; +bool legacySwitchState = false; + +void CODEC_deinit(void) +{ + GPIO_write(GPIO_4_0, 1); // GPIO bitmask 0x40 + TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO. + legacySwitchState = (codecReadReg(0x67, 0x25) & 0x40u) != 0; + if(!legacySwitchState) codecLegacyStuff(true); + codecMaskReg(0x67, 0x25, 0, 3); + touchscreenState = (codecReadReg(0x67, 0x24)>>7) == 0; + codecDisableTouchscreen(); + codecMaskReg(0x64, 0x76, 0, 0xC0); + TIMER_sleepMs(30); + for(u32 i = 0; i < 100; i++) + { + if(!(codecReadReg(0x64, 0x25) & 0x88u)) break; + TIMER_sleepMs(1); + } + codecMaskReg(0x64, 0x22, 2, 2); + TIMER_sleepMs(30); + for(u32 i = 0; i < 64; i++) + { + if(codecReadReg(0x64, 0x22) & 1u) break; + TIMER_sleepMs(1); + } + *((vu16*)0x10145000) &= ~0x8000u; + *((vu16*)0x10145002) &= ~0x8000u; + *((vu8* )0x10141220) = 0; + GPIO_write(GPIO_4_0, 0); // GPIO bitmask 0x40 + TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO. +} + +void CODEC_wakeup(void) +{ + GPIO_write(GPIO_4_0, 1); // GPIO bitmask 0x40 + TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO. + *((vu8* )0x10141220) = 2u; + *((vu16*)0x10145000) |= 0x8000u; + *((vu16*)0x10145002) |= 0x8000u; + //codecMaskReg(0x64, 0x45, 0, 0x30); // Output select automatic + codecMaskReg(0x64, 0x43, 0, 0x80); + codecMaskReg(0x64, 0x43, 0x80, 0x80); + codecMaskReg(0x64, 0x22, 0, 2); + TIMER_sleepMs(40); + for(u32 i = 0; i < 40; i++) + { + if(!(codecReadReg(0x64, 0x22) & 1u)) break; + TIMER_sleepMs(1); + } + codecMaskReg(0x64, 0x76, 0xC0, 0xC0); + TIMER_sleepMs(10); + for(u32 i = 0; i < 100; i++) + { + if(!(~codecReadReg(0x64, 0x25) & 0x88u)) break; + TIMER_sleepMs(1); + } + codecMaskReg(0x67, 0x25, 3, 3); + codecLegacyStuff(legacySwitchState); + if(touchscreenState) codecEnableTouchscreen(); + GPIO_write(GPIO_4_0, 0); // GPIO bitmask 0x40 + TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO. +} + void CODEC_getRawAdcData(u32 buf[13]) { //codecSwitchBank(0x67); diff --git a/source/arm11/hardware/csnd.c b/source/arm11/hardware/csnd.c new file mode 100644 index 0000000..8eccebf --- /dev/null +++ b/source/arm11/hardware/csnd.c @@ -0,0 +1,63 @@ +/* + * This file is part of fastboot 3DS + * Copyright (C) 2019 Sergi Granell (xerpi), Paul LaMendola (paulguy), derrek, profi200 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types.h" +#include "arm11/hardware/csnd.h" +#include "arm11/hardware/codec.h" + + + +void CSND_init(void) +{ + static bool inited = false; + if(inited) return; + inited = true; + + + CODEC_init(); + + //static const u8 sliderBounds[2] = {0xE, 0xF6}; // Volume slider 0% and 100% offset + //I2C_writeRegBuf(I2C_DEV_CTR_MCU, 0x58, sliderBounds, 2); + REG_CSND_MASTER_VOL = 0x8000; + REG_CSND_UNK_CNT = 1u<<15 | 1u<<14; + + for(u32 i = 0; i < 32; i++) REG_CSND_CH_CNT(i) = 0; + for(u32 i = 0; i < 2; i++) REG_CSND_CAP_CNT(i) = 0; +} + +void CSND_setupCh(u8 ch, s16 sampleRate, u32 vol, const u32 *const data, const u32 *const data2, u32 size, u16 flags) +{ + REG_CSND_CH_SR(ch) = sampleRate; + REG_CSND_CH_VOL(ch) = vol; + REG_CSND_CH_CAPVOL(ch) = vol; + REG_CSND_CH_ST_ADDR(ch) = (u32)data; + REG_CSND_CH_SIZE(ch) = size; + REG_CSND_CH_LP_ADDR(ch) = (u32)data2; + REG_CSND_CH_ST_ADPCM(ch) = 0; // Hardcoded for now. TODO + REG_CSND_CH_LP_ADPCM(ch) = 0; // Hardcoded for now. TODO + REG_CSND_CH_CNT(ch) = CSND_CH_START | flags; // Start in paused state. +} + + +void CSND_startCap(u8 ch, s16 sampleRate, u32 *const data, u32 size, u16 flags) +{ + REG_CSND_CAP_SR(ch) = sampleRate; + REG_CSND_CAP_SIZE(ch) = size; + REG_CSND_CAP_ADDR(ch) = (u32)data; + REG_CSND_CAP_CNT(ch) = CSND_CAP_START | flags; +}