/*
 *   This file is part of open_agb_firm
 *   Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
 */

// Based on code from https://github.com/xerpi/linux_3ds/blob/master/drivers/input/misc/nintendo3ds_codec_hid.c

#include <assert.h>
#include "types.h"
#include "arm11/drivers/codec.h"
#include "arm11/drivers/codec_regmap.h"
#include "arm11/drivers/spi.h"
#include "arm11/drivers/pdn.h"
#include "arm11/drivers/timer.h"
#include "arm11/drivers/gpio.h"


#define LIBN3DS_LEGACY (1) // TODO: Pass this via makefile.
#define SWAP_HALF(b0, b1, a1)          __builtin_bswap16(b0),__builtin_bswap16(b1),__builtin_bswap16(a1)
#define SWAP_FULL(b0, b1, b2, a1, a2)  __builtin_bswap16(b0),__builtin_bswap16(b1),__builtin_bswap16(b2),__builtin_bswap16(a1),__builtin_bswap16(a2)


typedef enum
{
	I2S_LINE_1 = 0u,
	I2S_LINE_2 = 1u
} I2sLine;

typedef enum
{
	I2S_FREQ_32KHZ = 0u, // 32728.498046875 Hz.
	I2S_FREQ_47KHZ = 1u  // 47605.08806818181818182 Hz.
} I2sFreq;

typedef enum
{
	MIC_FILTER_HALF  = 0u,
	MIC_FILTER_32KHZ = 1u, // I2S1?
	MIC_FILTER_47KHZ = 2u  // I2S2?
} MicFilter;


// All coefficients are 16 bit fixed-point numbers.
// 1 sign bit (bit 15) and 15 fraction bits. Big endian.
typedef struct
{
	s16 b0;
	s16 b1;
	// a0 always 1.0.
	s16 a1; // Inverted.
} IirBiquadHalf;

typedef struct
{
	s16 b0;
	s16 b1; // Halved.
	s16 b2;
	// a0 always 1.0.
	s16 a1; // Halved and inverted.
	s16 a2; // Inverted.
} IirBiquad; // Second order biquad.

typedef struct
{
	IirBiquadHalf half;
	IirBiquad biquads[5];
} IirBiquadHalfAnd5Full;

// Caution:
// Don't change the struct layout without
// adjusting swapCalibrationData().
typedef struct
{
	u8 driverGainHp;
	u8 driverGainSp;
	u8 analogVolumeHp;
	u8 analogVolumeSp;
	s8 shutterVolume[2];
	u8 microphoneBias;
	u8 quickCharge; // Microphone related.
	u8 pgaGain;     // Microphone gain.
	u8 reserved[3];
	IirBiquad filterHp32[3];
	IirBiquad filterHp47[3];
	IirBiquad filterSp32[3];
	IirBiquad filterSp47[3];
	IirBiquadHalfAnd5Full filterMic32;
	IirBiquadHalfAnd5Full filterMic47;
	IirBiquadHalfAnd5Full filterFree;
	u8 analogInterval;
	u8 analogStabilize;
	u8 analogPrecharge;
	u8 analogSense;
	u8 analogDebounce;
	u8 analogXpPullup;
	u8 ymDriver;
	u8 reserved2;
} CodecCal;
static_assert(offsetof(CodecCal, analogInterval) - offsetof(CodecCal, filterHp32) == 288, "Error: Filters not contiguous in CodecCal!");


alignas(4) static const CodecCal g_fallbackCal =
{
	0u, // 0 dB
	1u, // 12 dB
	0u, // 0 dB
	7u, // -3.5 dB
	{-3, -20},
	3u,
	2u,
	0u,
	{0, 0, 0},
	// Note:
	// The sample rate used for all filters
	// is the word clock of the I2S line.
	{ // filterHp32 I2S1.
		{SWAP_FULL( 32767,      0,      0,      0,      0)},
		{SWAP_FULL( 32767,      0,      0,      0,      0)},
		{SWAP_FULL( 32736, -16368,      0,  16352,      0)}  // First order 10 Hz highpass at 32730 Hz.
	},
	{ // filterHp47 I2S2.
		{SWAP_FULL( 32767,      0,      0,      0,      0)},
		{SWAP_FULL( 32767,      0,      0,      0,      0)},
		{SWAP_FULL( 32745, -16372,      0,  16361,      0)}  // First order 10 Hz highpass at 47610 Hz.
	},
	{ // filterSp32 I2S1.
		{SWAP_FULL( 32767, -27535,  22413,  30870, -29096)}, // Customized peak filter with negative offset?
		{SWAP_FULL(-14000,  30000, -14000,      0,      0)}, // Customized high shelf filter?
		{SWAP_FULL( 32736, -16368,      0,  16352,      0)}  // First order 10 Hz highpass at 32730 Hz.
	},
	{ // filterSp47 I2S2.
		{SWAP_FULL( 32767, -28995,  25277,  31456, -30200)}, // Customized peak filter with negative offset?
		{SWAP_FULL(-14402,  30000, -14402,      0,      0)}, // Customized high shelf filter?
		{SWAP_FULL( 32745, -16372,      0,  16361,      0)}  // First order 10 Hz highpass at 47610 Hz.
	},
	{ // filterMic32 I2S1?
		{SWAP_HALF( 32767,      0,      0)},
		{
			{SWAP_FULL( 32767,      0,      0,      0,      0)},
			{SWAP_FULL( 32767,      0,      0,      0,      0)},
			{SWAP_FULL( 32767,      0,      0,      0,      0)},
			{SWAP_FULL( 32767,      0,      0,      0,      0)},
			{SWAP_FULL( 32767,      0,      0,      0,      0)}
		}
	},
	{ // filterMic47 I2S2?
		{SWAP_HALF( 32767,      0,      0)},
		{
			{SWAP_FULL( 32767,       0,      0,      0,      0)},
			{SWAP_FULL( 32767,       0,      0,      0,      0)},
			{SWAP_FULL( 32767,       0,      0,      0,      0)},
			{SWAP_FULL( 32767,       0,      0,      0,      0)},
			{SWAP_FULL( 32767,       0,      0,      0,      0)}
		}
	},
	{ // filterFree all I2S lines.
		{SWAP_HALF( 32767,      0,      0)},
		{
			{SWAP_FULL(-12959,  -8785,  32767,   8785,  12959)}, // Phase correction?
			{SWAP_FULL(-12959,  -8785,  32767,   8785,  12959)}, // Phase correction?
			{SWAP_FULL(-12959,  -8785,  32767,   8785,  12959)}, // Phase correction?
			{SWAP_FULL( 32767,      0,      0,      0,      0)},
			{SWAP_FULL( 32767,      0,      0,      0,      0)},
		}
	},
	1u,
	9u,
	4u,
	3u,
	0u,
	6u,
	1u,
	0u
};



static void switchPage(u16 pageReg)
{
	pageReg >>= 8;

	static u8 currentPage = 0x63;
	if(currentPage != pageReg)
	{
		currentPage = pageReg;

		alignas(4) u8 inBuf[4];
		inBuf[0] = CDC_REG_PAGE_CTRL; // Bit 0: 0 = write, 1 = read.
		inBuf[1] = pageReg;
		NSPI_writeRead(NSPI_DEV_CS_HIGH | NSPI_DEV_CTR_CODEC, (u32*)inBuf, NULL, 2, 0);
	}
}

static void readRegBuf(u16 pageReg, u32 *buf, u32 size)
{
	switchPage(pageReg);

	alignas(4) u8 inBuf[4];
	inBuf[0] = pageReg<<1 | 1u; // Bit 0: 0 = write, 1 = read.
	NSPI_writeRead(NSPI_DEV_CS_HIGH | NSPI_DEV_CTR_CODEC, (u32*)inBuf, buf, 1, size);
}

static u8 readReg(u16 pageReg)
{
	alignas(4) u8 outBuf[4];
	readRegBuf(pageReg, (u32*)outBuf, 1);

	return outBuf[0];
}

static void writeRegBuf(u16 pageReg, u32 *buf, u32 size)
{
	switchPage(pageReg);

	alignas(4) u8 inBuf[4];
	inBuf[0] = pageReg<<1; // Bit 0: 0 = write, 1 = read.
	NSPI_writeRead(NSPI_DEV_CTR_CODEC, (u32*)inBuf, NULL, 1, 0);
	NSPI_writeRead(NSPI_DEV_CS_HIGH | NSPI_DEV_CTR_CODEC, buf, NULL, size, 0);
}

static void writeReg(u16 pageReg, u8 val)
{
	switchPage(pageReg);

	alignas(4) u8 inBuf[4];
	inBuf[0] = pageReg<<1; // Bit 0: 0 = write, 1 = read.
	inBuf[1] = val;
	NSPI_writeRead(NSPI_DEV_CS_HIGH | NSPI_DEV_CTR_CODEC, (u32*)inBuf, NULL, 2, 0);
}

#ifdef LIBN3DS_LEGACY
static void writeRegPowerman(u8 reg, u8 val)
{
	alignas(4) u8 inBuf[4];
	inBuf[0] = reg & 0x7Fu; // Bit 7: 0 = write, 1 = read.
	inBuf[1] = val;
	NSPI_writeRead(NSPI_DEV_CS_HIGH | NSPI_DEV_POWERMAN, (u32*)inBuf, NULL, 2, 0);
}
#endif

static void maskReg(u16 pageReg, u8 val, u8 mask)
{
	u8 data = readReg(pageReg);
	data = (data & ~mask) | (val & mask);
	writeReg(pageReg, data);
}

static void maskWaitReg(u16 pageReg, u8 val, u8 mask)
{
	for(u32 i = 0; i < 64; i++) // Some kind of timeout? No error checking.
	{
		maskReg(pageReg, val, mask);
		if((readReg(pageReg) & mask) == val) break;
	}
}


// Helpers
/*static void swapCalibrationData(CodecCal *cal)
{
	// Caution: This relies on struct layout.
	const u32 size = (sizeof(IirBiquad) * 3 * 4 + sizeof(IirBiquadHalfAnd5Full) * 3) / 2;
	u16 *tmp = (u16*)cal->filterHp32;
	for(u32 i = 0; i < size; i++)
	{
		tmp[i] = __builtin_bswap16(tmp[i]);
	}
}*/

static inline void pdnControlMclk(bool enable)
{
	// CODEC MCLK output enable(s).
	// CODEC is only connected to clock 2.
	getPdnRegs()->i2s_cnt = (enable ? PDN_I2S_CNT_I2S_CLK2_EN : 0);
}

static void softReset(void)
{
	writeReg(CDC_REG_SOFT_RST_CTR, 1);
	TIMER_sleepMs(40);
	switchPage(0); // What? Dummy switch after reset?
}

static inline void setI2sFreq(I2sLine i2sLine, I2sFreq freq)
{
	if(i2sLine == I2S_LINE_1) // I2S1
	{
		u8 val;
		if(freq == I2S_FREQ_32KHZ) val = 0x87;
		else                       val = 0x85;

		writeReg(CDC_REG_DAC_NDAC_VAL, val);
	}
	else // I2S2
	{
		u8 val;
		if(freq == I2S_FREQ_32KHZ) val = 1;
		else                       val = 0;

		maskReg(CDC_REG_100_124, val, 1);
	}
}

// TODO: Make this available as public API?
static void setIirFilterMic(MicFilter filter, const void *const coeff)
{
	u16 pageReg;
	u32 size;
	switch(filter)
	{
		case MIC_FILTER_HALF:
		{
			pageReg = 4<<8 | 8;
			size = sizeof(IirBiquadHalf);
			break;
		}
		case MIC_FILTER_32KHZ:
		{
			pageReg = 5<<8 | 8;
			size = sizeof(IirBiquadHalfAnd5Full);
			break;
		}
		case MIC_FILTER_47KHZ:
		{
			pageReg = 5<<8 | 72;
			size = sizeof(IirBiquadHalfAnd5Full);
			break;
		}
		default:
			return;
	}

	writeRegBuf(pageReg, (u32*)coeff, size);
}

static inline bool isDacMuted(I2sLine i2sLine)
{
	if(i2sLine == I2S_LINE_1)
		return (~readReg(CDC_REG_DAC_VOLUME_CTRL) & 0xCu) == 0; // I2S1
	else
		return (~readReg(CDC_REG_100_119) & 0xCu) == 0; // I2S2
}

static void muteUnmuteDac(I2sLine i2sLine, bool mute)
{
	if(i2sLine == I2S_LINE_1) // I2S1
	{
		maskReg(CDC_REG_DAC_DATA_PATH_SETUP, (mute ? 0u : 0xC0u), 0xC0);
		writeReg(CDC_REG_DAC_VOLUME_CTRL, (mute ? 0xCu : 0));
	}
	else // I2S2
	{
		maskReg(CDC_REG_100_119, (mute ? 0xCu : 0u), 0xC);
	}

	if(mute)
	{
		// Wait until muted.
		const u8 waitVal = (i2sLine == I2S_LINE_1 ? 0x44u : 0x88u);
		for(u32 i = 0; i < 100; i++) // Some kind of timeout? No error checking.
		{
			if(!(~readReg(CDC_REG_100_38) & waitVal)) break;
			TIMER_sleepMs(1);
		}
	}
}

/*static void setIirFilterSound(u8 type, const void *const coeff)
{
}*/

static void powerOnDac(void)
{
	// Power on DAC.
	maskReg(CDC_REG_100_118, 0xC0, 0xC0);

	// 10 ms wait time for safety?
	TIMER_sleepMs(10);

	// Also check the flags for extra safety.
	for(u32 i = 0; i < 100; i++)
	{
		if(!(~readReg(CDC_REG_100_37) & 0x88u)) break;
		TIMER_sleepMs(1);
	}
}

static void enableTouchscreen(void)
{
	maskReg(CDC_REG_103_38, 0x80, 0x80);
	maskReg(CDC_REG_103_36, 0, 0x80);
	maskReg(CDC_REG_103_37, 0x10, 0x3C);
}

static void disableTouchscreen(void)
{
	maskReg(CDC_REG_103_38, 0, 0x80);
	maskReg(CDC_REG_103_36, 0x80, 0x80);
}

static void legacyTouchscreenMode(bool enabled)
{
	if(enabled)
	{
		*((vu16*)0x10141114) |= 2u;
		*((vu16*)0x10141116) |= 2u;
		maskReg(CDC_REG_103_37, 0x40, 0x40);
	}
	else
	{
		maskReg(CDC_REG_103_37, 0, 0x40);
		*((vu16*)0x10141114) &= ~2u;
	}
}


// TODO: Implement manual output switching or fix auto switching.
static void headsetInit(void)
{
	// Headset detection stuff.
	GPIO_config(GPIO_2_HEADPH_JACK, GPIO_IRQ_ENABLE | GPIO_EDGE_RISING | GPIO_INPUT); // Headphone jack IRQ.
	//maskReg(CDC_REG_HEADSET_SEL, GPIO_read(GPIO_2_HEADPH_JACK)<<HEADSET_SEL_HP_SHIFT | HEADSET_SEL_HP_EN, 0x30); // GPIO bitmask 8.
	maskReg(CDC_REG_HEADSET_SEL, 0, 0x30); // With automatic output switching.
	maskReg(CDC_REG_100_67, 0, 0x80); // TODO: Can we remove this?
	maskReg(CDC_REG_100_67, 0x80, 0x80);
}

static void microphoneInit(const CodecCal *const cal)
{
	// Microphone ADC output select stuff.
	maskReg(CDC_REG_100_34, 0, 4);

	// 10 Hz highpass for 32730 Hz.
	alignas(4) static const IirBiquadHalf highPass10Hz = {SWAP_HALF(32737, -32737, 32705)};
	setIirFilterMic(MIC_FILTER_HALF, &highPass10Hz);

	setIirFilterMic(MIC_FILTER_32KHZ, &cal->filterMic32);
	setIirFilterMic(MIC_FILTER_47KHZ, &cal->filterMic47);

	// Microphone impedance settings.
	maskReg(CDC_REG_ADC_IN_SEL_FOR_P_TERMINAL, 0x40, 0xC0);
	maskReg(CDC_REG_ADC_IN_SEL_FOR_M_TERMINAL, 0x40, 0xC0);

	// Microphone bias voltage.
	writeReg(CDC_REG_101_51, cal->microphoneBias);

	// Microphone gain correction.
	maskWaitReg(CDC_REG_101_65, cal->pgaGain, 0x3F);

	// Quick charge? What does that even mean here?
	maskWaitReg(CDC_REG_101_66, cal->quickCharge, 3);

	// Microphone PGA.
	writeReg(CDC_REG_MIC_PGA, 43u & 0x7Fu); // TODO: Should be a global function?
}

static void shutterSoundInit(const CodecCal *const cal)
{
	// I2S mute and volume settings on shutter sound playback.
	maskReg(CDC_REG_100_49, 0x44, 0x44); // TODO: TwlBg/AgbBg uses val = 0 here.

	// I2S1 volumes.
	writeReg(CDC_REG_DAC_L_VOLUME_CTRL, cal->shutterVolume[0]);
	writeReg(CDC_REG_DAC_R_VOLUME_CTRL, cal->shutterVolume[0]);

	// I2S2 volume.
	writeReg(CDC_REG_100_123, cal->shutterVolume[1]);
}

static void soundInit(const CodecCal *const cal)
{
	// TODO: Depop circuit stuff is CTR only.
	// Speaker depop. Probably to suppress the noise when the driver turns on.
	// But this doesn't stop the pop noise on o3DS (CTR) at all?
	GPIO_config(GPIO_3_0, GPIO_OUTPUT);
	GPIO_write(GPIO_3_0, 1); // GPIO bitmask 0x40
	TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO.

	// TODO: Clean this up.
	// Before enabling the I2S interfaces make sure they are fully
	// off so we can write to all bits in these registers.
	*((vu16*)0x10145000) = 0;
	*((vu16*)0x10145002) = 0;
	// Bit 14 is MCLK1 multiplier (8 and 16 MHz)? I2S1: DSP and GBA 32728.498046875 Hz.
	*((vu16*)0x10145000) = 1u<<15 | 2u<<13 | 32u<<6;
	// Bit 14 is MCLK2 multiplier (8 and 16 MHz)? I2S2: CSND 47605.08806818181818182 Hz.
	*((vu16*)0x10145002) = 1u<<15 | 3u<<13;

	// Speaker driver powerup time?
	maskReg(CDC_REG_101_17, 0x10, 0x1C);

	// I2S1 volume?
	writeReg(CDC_REG_100_122, 0);
	// I2S2 volume?
	writeReg(CDC_REG_100_120, 0);

	// TODO: Function for setting sound filters.

	{
		// Missing in Twl-/AgbBg but present in codec module.
		// Omiting these filters actually makes sound slightly worse for GBA mode.
		const bool dacMuted = isDacMuted(I2S_LINE_1);
		muteUnmuteDac(I2S_LINE_1, true); // Mute.
		writeRegBuf(9<<8 | 2, (u32*)&cal->filterFree.half, 6);
		writeRegBuf(8<<8 | 12, (u32*)cal->filterFree.biquads, 50);
		writeRegBuf(9<<8 | 8, (u32*)&cal->filterFree.half, 6);
		writeRegBuf(8<<8 | 76, (u32*)cal->filterFree.biquads, 50);
		if(!dacMuted) muteUnmuteDac(I2S_LINE_1, false); // Unmute.
	}
	{
		const bool dacMuted = isDacMuted(I2S_LINE_2);
		muteUnmuteDac(I2S_LINE_2, true); // Mute.
		writeRegBuf(10<<8 | 2, (u32*)&cal->filterFree.half, 6);
		writeRegBuf(10<<8 | 12, (u32*)cal->filterFree.biquads, 50);
		if(!dacMuted) muteUnmuteDac(I2S_LINE_2, true); // Unmute.
	}

	writeRegBuf(12<<8 | 2, (u32*)cal->filterSp32, 30);
	writeRegBuf(12<<8 | 66, (u32*)cal->filterSp32, 30);
	writeRegBuf(12<<8 | 32, (u32*)cal->filterSp47, 30);
	writeRegBuf(12<<8 | 96, (u32*)cal->filterSp47, 30);
	writeRegBuf(11<<8 | 2, (u32*)cal->filterHp32, 30);
	writeRegBuf(11<<8 | 66, (u32*)cal->filterHp32, 30);
	writeRegBuf(11<<8 | 32, (u32*)cal->filterHp47, 30);
	writeRegBuf(11<<8 | 96, (u32*)cal->filterHp47, 30);

	// Power on DAC?
	powerOnDac();

	// Route DAC?
	writeReg(CDC_REG_101_10, 0xA);

	// TODO: Can we omit this since the DACs should already be unmuted?
	muteUnmuteDac(I2S_LINE_1, false); // Unmute I2S1.
	muteUnmuteDac(I2S_LINE_2, false); // Unmute I2S2.

	{ // Headphone.
		// Power on headphone driver?
		// Different settings depending on vendor and revision?
		// It seems we have 2 vendors and 3 revisions. Revsions: 1=B?, 2=C?, 3=D?
		u8 val;
		if((readReg(CDC_REG_0_2) & 0xFu) <= 1u && ((readReg(CDC_REG_0_3) & 0x70u)>>4 <= 2u))
		{
			val = 0x3C;
		}
		else val = 0x1C;
		writeReg(CDC_REG_101_11, val);

		// Unmute headphone driver?
		writeReg(CDC_REG_101_12, (cal->driverGainHp<<3) | 4);

		// Set/unmute analog volume?
		// TODO: We can combine these reg writes.
		writeReg(CDC_REG_101_22, cal->analogVolumeHp); // Left?
		writeReg(CDC_REG_101_23, cal->analogVolumeHp); // Right?
	}
	{ // Speaker.
		// Power on speaker driver?
		maskReg(CDC_REG_101_17, 0xC0, 0xC0);

		// Unmute speaker driver?
		// TODO: We can combine these reg writes.
		writeReg(CDC_REG_101_18, (cal->driverGainSp<<2) | 2); // Left?
		writeReg(CDC_REG_101_19, (cal->driverGainSp<<2) | 2); // Right?

		// // Set/unmute analog volume?
		writeReg(CDC_REG_101_27, cal->analogVolumeSp);
		writeReg(CDC_REG_101_28, cal->analogVolumeSp);
	}

	// Some delay waiting for headphone and speaker
	// outputs to be fully up and running?
	TIMER_sleepMs(38);

	// TODO: Depop circuit stuff is CTR only.
	// Speaker depop. Probably to suppress the noise when the driver turns on.
	// But this doesn't stop the pop noise on o3DS (CTR) at all?
	GPIO_write(GPIO_3_0, 0); // GPIO bitmask 0x40
	TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO.
}

static void touchAndCirclePadInit(const CodecCal *const cal)
{
	// Stop conversion/sampling?
	writeReg(CDC_REG_103_36, 0x98);

	writeReg(CDC_REG_103_38, 0x00);
	writeReg(CDC_REG_103_37, 0x43);
	writeReg(CDC_REG_103_36, 0x18);
	writeReg(CDC_REG_103_23, cal->analogPrecharge<<4 | cal->analogSense);
	writeReg(CDC_REG_103_25, cal->analogXpPullup<<4 | cal->analogStabilize);
	writeReg(CDC_REG_103_27, cal->ymDriver<<7 | cal->analogDebounce);
	writeReg(CDC_REG_103_39, 0x10u | cal->analogInterval);
	writeReg(CDC_REG_103_38, 0xEC);
	writeReg(CDC_REG_103_36, 0x18);
	writeReg(CDC_REG_103_37, 0x53);

	// Not needed?
	// Console dependent.
	//I2C_writeReg(I2C_DEV_CTR_MCU, 0x26, I2C_readReg(I2C_DEV_CTR_MCU, 0x26) | 0x10);

	// TODO: This should be called externally.
	enableTouchscreen();
}

#ifdef LIBN3DS_LEGACY
static void legacyWorkaround(void)
{
	// CODEC emulates the old DS power management chip.
	// Looks like there is a bug where it doesn't generate
	// IRQs if these regs are not properly initialized.
	writeRegPowerman(0u, 0u);
	writeRegPowerman(4u, 0u);
	writeRegPowerman(16u, 0u);
	writeRegPowerman(0u, 0x0Cu); // Triggers IRQ?
}
#endif

void CODEC_init(void)
{
	static bool inited = false;
	if(inited) return;
	inited = true;


	NSPI_init();

	// TODO: Load calibration from HWCAL files on eMMC.
	const CodecCal *const cal = &g_fallbackCal;

	// Turn on CODEC MCLK and reset it.
	pdnControlMclk(true); // Enable MCLK.
	softReset();

	// Headset detection timing stuff.
	writeReg(CDC_REG_100_67, 0x11);
	maskReg(CDC_REG_101_119, 1, 1);

	// Don't force speaker output.
	maskReg(CDC_REG_GPI1_GPI2_PIN_CTRL, 0x66, 0x66);

	// VREF stuff.
	writeReg(CDC_REG_101_122, 1);

	// PLL stuff.
	maskReg(CDC_REG_100_34, 0x18, 0x18);

	headsetInit();

	// Set CODEC side I2S frequencies (dividers?).
	setI2sFreq(I2S_LINE_1, I2S_FREQ_32KHZ);
	setI2sFreq(I2S_LINE_2, I2S_FREQ_47KHZ);

	microphoneInit(cal);

	shutterSoundInit(cal);

	soundInit(cal);

	touchAndCirclePadInit(cal);

	// TODO: For TWL legacy mode we need to set some uninitialized regs here (bug workaround?).
	//       We also need to watch for a certain GPIO to detect the TWL side
	//       changing I2S frequency.
#ifdef LIBN3DS_LEGACY
	legacyWorkaround();
#endif
}

bool g_touchscreenState = false;
bool g_legacySwitchState = false;

void CODEC_deinit(void)
{
	GPIO_write(GPIO_3_0, 1); // GPIO bitmask 0x40
	TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO.
	g_legacySwitchState = (readReg(CDC_REG_103_37) & 0x40u) != 0;
	if(!g_legacySwitchState) legacyTouchscreenMode(true);
	maskReg(CDC_REG_103_37, 0, 3);
	g_touchscreenState = (readReg(CDC_REG_103_36)>>7) == 0;
	disableTouchscreen();
	maskReg(CDC_REG_100_118, 0, 0xC0);
	TIMER_sleepMs(30);
	for(u32 i = 0; i < 100; i++)
	{
		if(!(readReg(CDC_REG_100_37) & 0x88u)) break;
		TIMER_sleepMs(1);
	}
	maskReg(CDC_REG_100_34, 2, 2);
	TIMER_sleepMs(30);
	for(u32 i = 0; i < 64; i++)
	{
		if(readReg(CDC_REG_100_34) & 1u) break;
		TIMER_sleepMs(1);
	}
	*((vu16*)0x10145000) &= ~0x8000u;
	*((vu16*)0x10145002) &= ~0x8000u;
	getPdnRegs()->i2s_cnt = 0;
	GPIO_write(GPIO_3_0, 0); // GPIO bitmask 0x40
	TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO.
}

void CODEC_wakeup(void)
{
	GPIO_write(GPIO_3_0, 1); // GPIO bitmask 0x40
	TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO.
	getPdnRegs()->i2s_cnt = PDN_I2S_CNT_I2S_CLK2_EN;
	*((vu16*)0x10145000) |= 0x8000u;
	*((vu16*)0x10145002) |= 0x8000u;
	//maskReg(0x64, 0x45, 0, 0x30); // Output select automatic
	maskReg(CDC_REG_100_67, 0, 0x80);
	maskReg(CDC_REG_100_67, 0x80, 0x80);
	maskReg(CDC_REG_100_34, 0, 2);
	TIMER_sleepMs(40);
	for(u32 i = 0; i < 40; i++)
	{
		if(!(readReg(CDC_REG_100_34) & 1u)) break;
		TIMER_sleepMs(1);
	}
	maskReg(CDC_REG_100_118, 0xC0, 0xC0);
	TIMER_sleepMs(10);
	for(u32 i = 0; i < 100; i++)
	{
		if(!(~readReg(CDC_REG_100_37) & 0x88u)) break;
		TIMER_sleepMs(1);
	}
	maskReg(CDC_REG_103_37, 3, 3);
	legacyTouchscreenMode(g_legacySwitchState);
	if(g_touchscreenState) enableTouchscreen();
	GPIO_write(GPIO_3_0, 0); // GPIO bitmask 0x40
	TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO.
}

bool CODEC_getRawAdcData(CdcAdcData *data)
{
	if((readReg(CDC_REG_103_38) & 2u) == 0)
	{
		readRegBuf(251<<8 | 1, (u32*)data, sizeof(CdcAdcData));

		return true;
	}

	// Codec module does this when data is unavailable. Why?
	//switchPage(0);

	return false;
}
