diff --git a/firmware/SOURCES b/firmware/SOURCES index 8ff0c20a9c..73804c24e9 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -1942,7 +1942,6 @@ target/arm/stm32/debug-stm32h7.c #endif target/arm/stm32/gpio-stm32h7.c target/arm/stm32/i2c-stm32h7.c -target/arm/stm32/pcm-stm32h7.c target/arm/stm32/sdmmc-stm32h7.c target/arm/stm32/spi-stm32h7.c target/arm/stm32/system-stm32h7.c @@ -1959,6 +1958,9 @@ target/arm/stm32/echoplayer/button-echoplayer.c target/arm/stm32/echoplayer/clock-echoplayer.c target/arm/stm32/echoplayer/i2c-echoplayer.c target/arm/stm32/echoplayer/lcd-echoplayer.c +#ifndef BOOTLOADER +target/arm/stm32/echoplayer/pcm-echoplayer.c +#endif target/arm/stm32/echoplayer/power-echoplayer.c target/arm/stm32/echoplayer/sdmmc-echoplayer.c target/arm/stm32/echoplayer/system-echoplayer.c diff --git a/firmware/export/config/echor1.h b/firmware/export/config/echor1.h index 7727f03da7..dd8ad30f66 100644 --- a/firmware/export/config/echor1.h +++ b/firmware/export/config/echor1.h @@ -51,9 +51,14 @@ /* Codec / audio hardware defines */ #define HW_SAMPR_CAPS SAMPR_CAP_ALL_96 // FIXME: check this section #define HAVE_ECHOPLAYER_CODEC +#define HAVE_AIC310X #define HAVE_SW_TONE_CONTROLS #define HAVE_SW_VOLUME_CONTROL +#ifndef SIMULATOR +#define PCM_NATIVE_BITDEPTH 32 +#endif + /* Button defines */ #define CONFIG_KEYPAD ECHO_R1_PAD #define HAVE_HEADPHONE_DETECTION diff --git a/firmware/export/echoplayer_codec.h b/firmware/export/echoplayer_codec.h index 3be3e9c015..4f3d1e0f49 100644 --- a/firmware/export/echoplayer_codec.h +++ b/firmware/export/echoplayer_codec.h @@ -21,9 +21,13 @@ #ifndef __ECHOPLAYER_CODEC_H__ #define __ECHOPLAYER_CODEC_H__ +#include + /* -79 to 0 dB in 0.5 dB steps; software volume control * is used because the hardware volume controls "click" * when changing the volume */ AUDIOHW_SETTING(VOLUME, "dB", 1, 5, -790, 0, -200); +void audiohw_mute(bool mute); + #endif /* __ECHOPLAYER_CODEC_H__ */ diff --git a/firmware/target/arm/stm32/echoplayer/audiohw-echoplayer.c b/firmware/target/arm/stm32/echoplayer/audiohw-echoplayer.c index cf336ee41e..2fb913481e 100644 --- a/firmware/target/arm/stm32/echoplayer/audiohw-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/audiohw-echoplayer.c @@ -19,21 +19,151 @@ * ****************************************************************************/ #include "audiohw.h" +#include "kernel.h" +#include "panic.h" +#include "aic310x.h" +#include "pcm_sw_volume.h" +#include "power-echoplayer.h" +#include "gpio-stm32h7.h" +#include "i2c-echoplayer.h" + +static int tlv_read_multiple(uint8_t reg, uint8_t *data, size_t count) +{ + return stm32_i2c_read_mem(&i2c1_ctl, AIC310X_I2C_ADDR, reg, data, count); +} + +static int tlv_write_multiple(uint8_t reg, const uint8_t *data, size_t count) +{ + return stm32_i2c_write_mem(&i2c1_ctl, AIC310X_I2C_ADDR, reg, data, count); +} + +static struct aic310x tlv_codec = { + .read_multiple = tlv_read_multiple, + .write_multiple = tlv_write_multiple, +}; + +struct codec_reg_data +{ + uint8_t reg; + uint8_t val; +}; + +static const struct codec_reg_data codec_init_seq[] = { + /* Set output driver configuration to stereo psuedo-differential */ + { .reg = AIC310X_HEADSET_DETECT_B, .val = 0x08 }, + + /* Clock input is CODEC_CLKIN (bypass codec PLL) */ + { .reg = AIC310X_CLOCK, .val = 0x01 }, + + /* Route left ch. -> left DAC, right ch -> right DAC */ + { .reg = AIC310X_DATA_PATH, .val = 0x0A }, + + /* Power on DACs, set HPLCOM = VCM, set HPRCOM = VCM, + * enable short circuit protection */ + { .reg = AIC310X_HPOUT_DRIVER_CTRL, .val = 0x0C }, + { .reg = AIC310X_DAC_POWER_CTRL, .val = 0xD0 }, + + /* Unmute the DACs */ + { .reg = AIC310X_LEFT_DAC_DIGITAL_VOLUME, .val = 0x00 }, + { .reg = AIC310X_RIGHT_DAC_DIGITAL_VOLUME, .val = 0x00 }, + + /* Route DACs to output drivers */ + { .reg = AIC310X_DAC_L1_TO_HPLOUT_VOLUME, .val = 0x80 }, + { .reg = AIC310X_DAC_R1_TO_HPROUT_VOLUME, .val = 0x80 }, + + /* Route DACs to line out port */ + { .reg = AIC310X_DAC_L1_TO_LEFT_LO_VOLUME, .val = 0x80 }, + { .reg = AIC310X_DAC_R1_TO_RIGHT_LO_VOLUME, .val = 0x80 }, + + /* Power on headphone output drivers */ + { .reg = AIC310X_HPLCOM_LEVEL_CONTROL, .val = 0x01 }, + { .reg = AIC310X_HPRCOM_LEVEL_CONTROL, .val = 0x01 }, + { .reg = AIC310X_HPLOUT_LEVEL_CONTROL, .val = 0x01 }, + { .reg = AIC310X_HPROUT_LEVEL_CONTROL, .val = 0x01 }, + + /* Power up line out drivers */ + { .reg = AIC310X_RIGHT_LO_LEVEL_CONTROL, .val = 0x01 }, + { .reg = AIC310X_LEFT_LO_LEVEL_CONTROL, .val = 0x01 }, +}; + +static void write_aic310x_seq(const struct codec_reg_data *seq, size_t count) +{ + while (count > 0) + { + if (aic310x_write(&tlv_codec, seq->reg, seq->val)) + panicf("aic310x wr fail: %02x", (unsigned int)seq->reg); + + seq++; + count--; + } +} + +static void wait_aic310x_active(void) +{ + long end_tick = current_tick + HZ; + while (TIME_BEFORE(current_tick, end_tick)) + { + uint8_t tmp; + int err = tlv_read_multiple(AIC310X_PAGE_SELECT, &tmp, 1); + if (err == 0) + return; + + sleep(1); + } + + panicf("aic310x init timeout"); +} void audiohw_init(void) { + /* initialize driver */ + aic310x_init(&tlv_codec); + + /* bring 1.8v regulator up */ + echoplayer_enable_1v8_regulator(true); + + /* apply AVDD/DRVDD and DVDD */ + gpio_set_level(GPIO_CODEC_AVDD_EN, 0); + gpio_set_level(GPIO_CODEC_DVDD_EN, 0); + + /* delay for power stabilization */ + sleep(HZ/100); + + /* bring codec out of reset */ + gpio_set_level(GPIO_CODEC_RESET, 1); } void audiohw_postinit(void) { + wait_aic310x_active(); + write_aic310x_seq(codec_init_seq, ARRAYLEN(codec_init_seq)); } void audiohw_close(void) { + /* apply reset */ + gpio_set_level(GPIO_CODEC_RESET, 0); + + /* remove power */ + gpio_set_level(GPIO_CODEC_AVDD_EN, 1); + gpio_set_level(GPIO_CODEC_DVDD_EN, 1); + + /* disable regulator */ + echoplayer_enable_1v8_regulator(false); +} + +void audiohw_mute(bool mute) +{ + uint8_t wr_val = (mute ? 0x01 : 0x09); + + /* Mute and unmute at the output drivers */ + aic310x_write(&tlv_codec, AIC310X_HPLOUT_LEVEL_CONTROL, wr_val); + aic310x_write(&tlv_codec, AIC310X_HPROUT_LEVEL_CONTROL, wr_val); + aic310x_write(&tlv_codec, AIC310X_LEFT_LO_LEVEL_CONTROL, wr_val); + aic310x_write(&tlv_codec, AIC310X_RIGHT_LO_LEVEL_CONTROL, wr_val); } void audiohw_set_volume(int vol_l, int vol_r) { - (void)vol_l; - (void)vol_r; + pcm_set_master_volume(vol_l, vol_r); } diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c index f83314a2e6..9f1b158902 100644 --- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c @@ -52,6 +52,7 @@ INIT_ATTR static void init_pll(void) /* * Use HSE/4 input for PLL1 * Use HSE/16 input for PLL3 + * PLL2 reserved for audio; configured in target PCM code */ reg_writef(RCC_PLLCKSELR, PLLSRC_V(HSE), @@ -164,7 +165,7 @@ INIT_ATTR static void init_lse(void) INIT_ATTR static void init_periph_clock(void) { reg_writef(RCC_D1CCIPR, SDMMCSEL_V(PLL1Q)); - reg_writef(RCC_D2CCIP1R, SPI45SEL_V(HSE)); + reg_writef(RCC_D2CCIP1R, SAI1SEL_V(PLL2P), SPI45SEL_V(HSE)); reg_writef(RCC_D2CCIP2R, I2C123SEL_V(HSI)); /* Enable AXI SRAM in sleep mode to allow DMA'ing out of it */ diff --git a/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c b/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c new file mode 100644 index 0000000000..d85acad377 --- /dev/null +++ b/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c @@ -0,0 +1,354 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 Aidan MacDonald + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "pcm.h" +#include "pcm_sampr.h" +#include "pcm-internal.h" +#include "audiohw.h" +#include "panic.h" +#include "nvic-arm.h" +#include "dmamux-stm32h743.h" +#include "regs/stm32h743/rcc.h" +#include "regs/stm32h743/sai.h" +#include "regs/stm32h743/dma.h" +#include "regs/stm32h743/dmamux.h" +#include + +/* Configurable bit depth */ +#if PCM_NATIVE_BITDEPTH == 32 +# define PCM_NATIVE_SAMPLE_SIZE 4 +# define DMA_PSIZE_VAL BV_DMA_CHN_CR_PSIZE_32BIT +# define SAI_DSIZE_VAL BV_SAI_SUBBLOCK_CR1_DS_32BIT +# define SAI_FIFO_THRESH BV_SAI_SUBBLOCK_CR2_FTH_QUARTER +# define SAI_FRL_VAL 64 +#elif PCM_NATIVE_BITDEPTH == 24 +# define PCM_NATIVE_SAMPLE_SIZE 4 +# define DMA_PSIZE_VAL BV_DMA_CHN_CR_PSIZE_32BIT +# define SAI_DSIZE_VAL BV_SAI_SUBBLOCK_CR1_DS_24BIT +# define SAI_FIFO_THRESH BV_SAI_SUBBLOCK_CR2_FTH_QUARTER +# define SAI_FRL_VAL 64 +#elif PCM_NATIVE_BITDEPTH == 16 +# define PCM_NATIVE_SAMPLE_SIZE 2 +# define DMA_PSIZE_VAL BV_DMA_CHN_CR_PSIZE_16BIT +# define SAI_DSIZE_VAL BV_SAI_SUBBLOCK_CR1_DS_16BIT +# define SAI_FIFO_THRESH BV_SAI_SUBBLOCK_CR2_FTH_HALF +# define SAI_FRL_VAL 32 +#else +# error "Unsupported PCM bit depth" +#endif + +static const uintptr_t sai1 = ITA_SAI1; +static const uintptr_t sai1a = sai1 + ITO_SAI_A; +static const uintptr_t dmamux1 = ITA_DMAMUX1; +static const uintptr_t dma1 = ITA_DMA1; +static const uintptr_t dma1_ch0 = dma1 + ITO_DMA_CHN(0); + +static atomic_size_t play_lock; +static volatile int play_active; + +static int pcm_last_freq = -1; + +static void play_dma_start(const void *addr, size_t size) +{ + commit_dcache_range(addr, size); + + /* + * The number of transfers is defined by PSIZE, which may + * be 16-bit or 32-bit depending on PCM native sample size. + * Each FIFO write transfers 1 sample, so we want to set + * NDT = size / PCM_NATIVE_SAMPLE_SIZE. + * + * When MSIZE == 32-bit and PSIZE == 16-bit, the DMA will + * take each 32-bit word from memory and write the lower/upper + * halfwords separately to the FIFO. The number of reads from + * memory in this case is NDT/2, so we're still transferring + * the correct amount of data. + */ + reg_varl(dma1_ch0, DMA_CHN_NDTR) = size / PCM_NATIVE_SAMPLE_SIZE; + reg_varl(dma1_ch0, DMA_CHN_M0AR) = (uintptr_t)addr; + reg_writelf(dma1_ch0, DMA_CHN_CR, EN(1)); + + pcm_play_dma_status_callback(PCM_DMAST_STARTED); +} + +static void sai_init(void) +{ + /* Enable SAI1 and DMA1 */ + reg_writef(RCC_APB2ENR, SAI1EN(1)); + reg_writef(RCC_AHB1ENR, DMA1EN(1)); + + /* Link DMA1_CH0 to SAI1A */ + reg_writelf(dmamux1, DMAMUX_CR(0), DMAREQ_ID(DMAMUX1_REQ_SAI1A_DMA)); + + /* Configure DMA1 channel 0 */ + reg_writelf(dma1_ch0, DMA_CHN_CR, + MBURST_V(INCR4), /* 32-bit x 4 burst = 16 bytes */ + PBURST_V(INCR4), /* (16|32)-bit x 4 burst = 8-16 bytes */ + TRBUFF(0), /* bufferable mode not used for SAI */ + DBM(0), /* double buffer mode off */ + PL_V(VERYHIGH), /* highest priority */ + MSIZE_V(32BIT), /* read 32-bit words from memory */ + PSIZE(DMA_PSIZE_VAL), /* set according to PCM bit depth */ + MINC(1), /* increment memory address */ + PINC(0), /* don't increment peripheral address */ + CIRC(0), /* circular mode off */ + DIR_V(MEM_TO_PERIPH), /* read from memory, write to SAI */ + PFCTRL_V(DMA), /* DMA is flow controller */ + TCIE(1), /* transfer complete interrupt */ + TEIE(1), /* transfer error interrupt */ + DMEIE(1)); /* direct mode error interrupt */ + reg_writelf(dma1_ch0, DMA_CHN_FCR, + FEIE(1), /* fifo error interrupt */ + DMDIS(1), /* enable fifo mode */ + FTH_V(FULL)); /* fifo threshold = 4 words */ + + /* Set peripheral address here since it's constant */ + reg_varl(dma1_ch0, DMA_CHN_PAR) = + (uintptr_t)reg_ptrl(sai1a, SAI_SUBBLOCK_DR); + + /* Configure SAI for playback */ + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, + OSR_V(256FS), /* MCLK is 256 x Fs */ + NOMCK(0), /* MCLK enabled */ + DMAEN(0), /* DMA request disabled */ + SAIEN(0), /* SAI disabled */ + OUTDRIV(0), /* drive outputs when SAIEN=1 */ + MONO(0), /* stereo mode */ + SYNCEN_V(ASYNC), /* no sync with other SAIs */ + CKSTR_V(TX_FALLING_RX_RISING), /* clock edge for sampling */ + LSBFIRST(0), /* transmit samples MSB first */ + DS(SAI_DSIZE_VAL), /* sample size according to bit depth */ + PRTCFG_V(FREE), /* free protocol */ + MODE_V(MASTER_TX)); /* operate as bus master */ + reg_writelf(sai1a, SAI_SUBBLOCK_CR2, + MUTEVAL_V(ZERO_SAMPLE), /* send zero sample on mute */ + MUTE(1), /* mute output initially */ + TRIS(0), /* don't tri-state outputs */ + FTH(SAI_FIFO_THRESH)); /* fifo threshold (2 or 4 samples) */ + reg_writelf(sai1a, SAI_SUBBLOCK_FRCR, + FSOFF(1), FSPOL(0), FSDEF(1), /* I2S format */ + FSALL(SAI_FRL_VAL/2 - 1), /* FS active for half the frame */ + FRL(SAI_FRL_VAL - 1)); /* set frame length */ + reg_writelf(sai1a, SAI_SUBBLOCK_SLOTR, + SLOTEN(3), NBSLOT(2 - 1), /* enable first two slots */ + SLOTSZ_V(DATASZ), /* slot size = data size */ + FBOFF(0)); /* no bit offset in slot */ + + /* Enable interrupts in NVIC */ + nvic_enable_irq(NVIC_IRQN_DMA1_STR0); +} + +struct div_settings +{ + uint8_t pllm; + uint8_t plln; + uint8_t pllp; + uint8_t mckdiv; +}; + +_Static_assert(STM32_HSE_FREQ == 24000000, + "Audio clock settings only valid for 24MHz HSE"); + +static const struct div_settings div_settings[] = { + [HW_FREQ_96] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 0 }, /* exact */ + [HW_FREQ_48] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 2 }, /* exact */ + [HW_FREQ_24] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 4 }, /* exact */ + [HW_FREQ_12] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 8 }, /* exact */ + + [HW_FREQ_88] = { .pllm = 4, .plln = 143 - 1, .pllp = 38 - 1, .mckdiv = 0 }, /* -11ppm error */ + [HW_FREQ_44] = { .pllm = 4, .plln = 143 - 1, .pllp = 38 - 1, .mckdiv = 2 }, /* -11ppm error */ + [HW_FREQ_22] = { .pllm = 5, .plln = 147 - 1, .pllp = 125 - 1, .mckdiv = 0 }, /* exact */ + [HW_FREQ_11] = { .pllm = 5, .plln = 147 - 1, .pllp = 125 - 1, .mckdiv = 2 }, /* exact */ + + [HW_FREQ_64] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 0 }, /* exact */ + [HW_FREQ_32] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 2 }, /* exact */ + [HW_FREQ_16] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 4 }, /* exact */ + [HW_FREQ_8 ] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 8 }, /* exact */ +}; + +static void sai_set_pll(const struct div_settings *div) +{ + /* Disable the PLL so it can be reconfigured */ + if (reg_readf(RCC_CR, PLL2RDY)) + { + reg_writef(RCC_CR, PLL2ON(0)); + while (reg_readf(RCC_CR, PLL2RDY)); + while (reg_readf(RCC_CR, PLL2ON)); + } + + /* Set the PLL configuration */ + uint32_t pllcfgr = reg_var(RCC_PLLCFGR); + + reg_writef(RCC_PLLCKSELR, + DIVM2(div->pllm)); + reg_writef(RCC_PLL2DIVR, + DIVN(div->plln), + DIVP(div->pllp), + DIVQ(0), DIVR(0)); + reg_vwritef(pllcfgr, RCC_PLLCFGR, + DIVP2EN(1), + DIVQ2EN(0), + DIVR2EN(0), + PLL2FRACEN(0), + PLL2VCOSEL_V(WIDE)); + + if (div->pllm <= 6) + reg_vwritef(pllcfgr, RCC_PLLCFGR, PLL2RGE_V(4_8MHZ)); + else if (div->pllm <= 12) + reg_vwritef(pllcfgr, RCC_PLLCFGR, PLL2RGE_V(2_4MHZ)); + else + reg_vwritef(pllcfgr, RCC_PLLCFGR, PLL2RGE_V(1_2MHZ)); + + reg_var(RCC_PLLCFGR) = pllcfgr; + + /* Enable the PLL again */ + reg_writef(RCC_CR, PLL2ON(1)); + while (!reg_readf(RCC_CR, PLL2RDY)); +} + +static void sai_set_frequency(int freq) +{ + /* Can't change frequency while the SAI is active */ + if (reg_readlf(sai1a, SAI_SUBBLOCK_CR1, SAIEN)) + panicf("%s while SAI active", __func__); + + const struct div_settings *div = &div_settings[freq]; + const struct div_settings *old_div = NULL; + + if (pcm_last_freq >= 0) + old_div = &div_settings[pcm_last_freq]; + + if (old_div == NULL || + div->pllm != old_div->pllm || + div->plln != old_div->plln || + div->pllp != old_div->pllp) + { + sai_set_pll(div); + } + + /* Configure the MCK divider in SAI */ + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, MCKDIV(div->mckdiv)); +} + +static void sink_dma_init(void) +{ + sai_init(); + audiohw_init(); +} + +static void sink_set_freq(uint16_t freq) +{ + /* + * Muting here doesn't seem to help clicks when switching + * tracks of different frequencies; the audio may need to + * be soft-muted in software before the switch. + */ + audiohw_mute(true); + + sai_set_frequency(freq); + pcm_last_freq = freq; + + audiohw_mute(false); +} + +static void sink_dma_start(const void *addr, size_t size) +{ + play_active = true; + + play_dma_start(addr, size); + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, SAIEN(1), DMAEN(1)); + reg_writelf(sai1a, SAI_SUBBLOCK_CR2, MUTE(0)); +} + +static void sink_dma_stop(void) +{ + play_active = false; + + reg_writelf(sai1a, SAI_SUBBLOCK_CR2, MUTE(1)); + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, SAIEN(0), DMAEN(0)); + reg_writelf(dma1_ch0, DMA_CHN_CR, EN(0)); + + /* Must wait for SAIEN bit to be cleared */ + while (reg_readlf(sai1a, SAI_SUBBLOCK_CR1, SAIEN)); +} + +static void sink_lock(void) +{ + /* Disable IRQ on first lock */ + if (atomic_fetch_add_explicit(&play_lock, 1, memory_order_relaxed) == 0) + nvic_disable_irq_sync(NVIC_IRQN_DMA1_STR0); +} + +static void sink_unlock(void) +{ + /* Enable IRQ on release of last lock */ + if (atomic_fetch_sub_explicit(&play_lock, 1, memory_order_relaxed) == 1) + nvic_enable_irq(NVIC_IRQN_DMA1_STR0); +} + +struct pcm_sink builtin_pcm_sink = { + .caps = { + .samprs = hw_freq_sampr, + .num_samprs = HW_NUM_FREQ, + .default_freq = HW_FREQ_DEFAULT, + }, + .ops = { + .init = sink_dma_init, + .postinit = audiohw_postinit, + .set_freq = sink_set_freq, + .lock = sink_lock, + .unlock = sink_unlock, + .play = sink_dma_start, + .stop = sink_dma_stop, + }, +}; + +void dma1_ch0_irq_handler(void) +{ + uint32_t lisr = reg_varl(dma1, DMA_LISR); + const void *addr; + size_t size; + + if (reg_vreadf(lisr, DMA_LISR, TEIF0) || + reg_vreadf(lisr, DMA_LISR, DMEIF0) || + reg_vreadf(lisr, DMA_LISR, FEIF0)) + { + reg_assignlf(dma1, DMA_LIFCR, TEIF0(1), DMEIF0(1), FEIF0(1)); + reg_writelf(dma1_ch0, DMA_CHN_CR, EN(0)); + + pcm_play_dma_status_callback(PCM_DMAST_ERR_DMA); + } + else if (reg_vreadf(lisr, DMA_LISR, TCIF0)) + { + reg_assignlf(dma1, DMA_LIFCR, TCIF0(1)); + + /* If we call the complete callback while not playing + * it can cause the PCM layer to get stuck... somehow */ + if (!play_active) + return; + + if (pcm_play_dma_complete_callback(PCM_DMAST_OK, &addr, &size)) + play_dma_start(addr, size); + } + else + { + panicf("%s: %08lx", __func__, lisr); + } +} diff --git a/firmware/target/arm/stm32/pcm-stm32h7.c b/firmware/target/arm/stm32/pcm-stm32h7.c deleted file mode 100644 index 472ab4a3b9..0000000000 --- a/firmware/target/arm/stm32/pcm-stm32h7.c +++ /dev/null @@ -1,105 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2025 Aidan MacDonald - * - * 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 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -#include "pcm.h" -#include "pcm-internal.h" -#include "pcm_sampr.h" -#include "pcm_sink.h" - -static void sink_set_freq(uint16_t freq) -{ - (void)freq; -} - -static void sink_dma_init(void) -{ -} - -static void sink_dma_postinit(void) -{ -} - -static void sink_dma_start(const void *addr, size_t size) -{ - (void)addr; - (void)size; -} - -static void sink_dma_stop(void) -{ -} - -static void sink_lock(void) -{ -} - -static void sink_unlock(void) -{ -} - -struct pcm_sink builtin_pcm_sink = { - .caps = { - .samprs = hw_freq_sampr, - .num_samprs = HW_NUM_FREQ, - .default_freq = HW_FREQ_DEFAULT, - }, - .ops = { - .init = sink_dma_init, - .postinit = sink_dma_postinit, - .set_freq = sink_set_freq, - .lock = sink_lock, - .unlock = sink_unlock, - .play = sink_dma_start, - .stop = sink_dma_stop, - }, -}; - -#ifdef HAVE_RECORDING -void pcm_rec_dma_init(void) -{ -} - -void pcm_rec_dma_close(void) -{ -} - -void pcm_rec_dma_start(void *addr, size_t size) -{ - (void)addr; - (void)size; -} - -void pcm_rec_dma_stop(void) -{ -} - -void pcm_rec_lock(void) -{ -} - -void pcm_rec_unlock(void) -{ -} - -const void *pcm_rec_dma_get_peak_buffer(void) -{ - return NULL; -} -#endif