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;
+}