From c9417e24c1fc5ff95ada5b8c53dccb339e7b1bd5 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 3 Mar 2026 06:59:40 -0500 Subject: [PATCH 1/6] FS#13807 - Updated German translation (Wilfried Winkler) Change-Id: I01f4ba343de72386cf07efe9afdf1535659cb19f --- apps/lang/deutsch.lang | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/lang/deutsch.lang b/apps/lang/deutsch.lang index 8a8f5ae5a1..b006e5d3c4 100644 --- a/apps/lang/deutsch.lang +++ b/apps/lang/deutsch.lang @@ -14690,7 +14690,7 @@ user: core *: "Press LEFT to cancel." - android,hifietma*,zenvision: "Press BACK to cancel." + android,hifietma*: "Press BACK to cancel." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel." ihifi760,ihifi960: "Double tap RETURN to cancel." ihifi770,ihifi770c,ihifi800: "Press HOME to cancel." @@ -14705,7 +14705,7 @@ *: "Zum Abbrechen LINKS drücken." - android,hifietma*,vibe500,zenvision: "Zum Abbrechen ZURÜCK drücken." + android,hifietma*,vibe500: "Zum Abbrechen ZURÜCK drücken." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Zum Abbrechen POWER drücken." ihifi760,ihifi960: "Zum Abbrechen ZURÜCK doppeldrücken." ihifi770,ihifi770c,ihifi800: "Zum Abbrechen STARTMENÜ drücken." @@ -14719,7 +14719,7 @@ *: "Zum Abbrechen LINKS drücken." - android,hifietma*,vibe500,zenvision: "Zum Abbrechen ZURÜCK drücken." + android,hifietma*,vibe500: "Zum Abbrechen ZURÜCK drücken." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Zum Abbrechen POWER drücken." ihifi760,ihifi960: "Zum Abbrechen ZURÜCK doppeldrücken." ihifi770,ihifi770c,ihifi800: "Zum Abbrechen STARTMENÜ drücken." @@ -15213,7 +15213,7 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" @@ -16978,3 +16978,17 @@ *: "Status mitteilen" + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "Zuletzt verwendeten Ordner immer merken" + + + *: "Zuletzt verwendeten Ordner immer merken" + + From b782f15eaa1800877f2f6821eb1d73033e3cb76c Mon Sep 17 00:00:00 2001 From: Vencislav Atanasov Date: Tue, 3 Mar 2026 14:16:33 +0200 Subject: [PATCH 2/6] Update Bulgarian translation Change-Id: Iac4ef4b9ae6e5778dc37fb423297336480f9b8a0 --- apps/lang/bulgarian.lang | 111 ++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 61 deletions(-) diff --git a/apps/lang/bulgarian.lang b/apps/lang/bulgarian.lang index 06f75d50a4..76b058e6f9 100644 --- a/apps/lang/bulgarian.lang +++ b/apps/lang/bulgarian.lang @@ -12507,20 +12507,6 @@ *: "Средна побитова скорост" - - id: LANG_PLAYTIME_ERROR - desc: playing time screen - user: core - - *: "Error while gathering info" - - - *: "Грешка при събирането на информация" - - - *: "Грешка при събирането на информация" - - id: LANG_PLAYING_TIME desc: onplay menu @@ -14686,7 +14672,7 @@ user: core *: "Press LEFT to cancel." - android,hifietma*,zenvision: "Press BACK to cancel." + android,hifietma*: "Press BACK to cancel." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel." ihifi760,ihifi960: "Double tap RETURN to cancel." ihifi770,ihifi770c,ihifi800: "Press HOME to cancel." @@ -14701,7 +14687,7 @@ *: "Натиснете LEFT за отказ." - android,hifietma*,zenvision: "Натиснете BACK за отказ." + android,hifietma*: "Натиснете BACK за отказ." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Натиснете POWER за отказ." ihifi760,ihifi960: "Натиснете двукратно RETURN за отказ." ihifi770,ihifi770c,ihifi800: "Натиснете HOME за отказ." @@ -14716,7 +14702,7 @@ *: "Натиснете LEFT за отказ." - android,hifietma*,zenvision: "Натиснете BACK за отказ." + android,hifietma*: "Натиснете BACK за отказ." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Натиснете POWER за отказ." ihifi760,ihifi960: "Натиснете двукратно RETURN за отказ." ihifi770,ihifi770c,ihifi800: "Натиснете HOME за отказ." @@ -15211,13 +15197,13 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" - *: "~dAY" + *: "dAy" *: "" @@ -15832,20 +15818,6 @@ *: "Браузър по подразбиране" - - id: LANG_AMAZE_MENU - desc: Amaze game - user: core - - *: "Amaze Main Menu" - - - *: "Главно меню на Amaze" - - - *: "Главно меню на Amaze" - - id: LANG_SET_MAZE_SIZE desc: Maze size in Amaze game @@ -16098,34 +16070,6 @@ *: "Настройки на Mik mod" - - id: LANG_MIKMOD_MENU - desc: mikmod plugin - user: core - - *: "Mikmod Menu" - - - *: "Меню на Mikmod" - - - *: "Меню на Mik mod" - - - - id: LANG_CHESSBOX_MENU - desc: chessbox plugin - user: core - - *: "Chessbox Menu" - - - *: "Меню на Chessbox" - - - *: "Меню на Chess box" - - id: VOICE_INVALID_VOICE_FILE desc: played if the voice file fails to load @@ -16987,3 +16931,48 @@ usbdac: "USB ЦАП е активен" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Съобщаване на състоянието" + + + *: "Съобщаване на състоянието" + + + + id: LANG_USE_LED_INDICATORS + desc: LED indicators setting + user: core + + *: none + general_purpose_led: "Use LED indicators" + + + *: none + general_purpose_led: "Използване на светодиодни индикатори" + + + *: none + general_purpose_led: "Използване на светодиодни индикатори" + + + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "Винаги помни последната папка" + + + *: "Винаги помни последната папка" + + From 5c1ae5119378b772b3dfdb7cd30d649364b26adb Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 7 Feb 2026 10:54:55 +0000 Subject: [PATCH 3/6] echoplayer: implement audio playback Playback is implemented using a target-specific PCM layer, using the STM32H7 SAI & DMA registers directly. There are a number of pop/click issues: 1. Slight click when powering up the amplifiers 2. Click when starting and stopping playback 3. Popping when changing playback frequency 4. Popping when shutting down It should be possible to eliminate or at least mitigate (2) to (4) in software, but (1) happens as a result of powering on the amplifiers while everything is muted so might be unavoidable. Change-Id: I398b66596176fb2341beb7deba7bf6f4f3fb82b3 --- firmware/SOURCES | 4 +- firmware/export/config/echor1.h | 5 + firmware/export/echoplayer_codec.h | 4 + .../arm/stm32/echoplayer/audiohw-echoplayer.c | 134 ++++++- .../arm/stm32/echoplayer/clock-echoplayer.c | 3 +- .../arm/stm32/echoplayer/pcm-echoplayer.c | 354 ++++++++++++++++++ firmware/target/arm/stm32/pcm-stm32h7.c | 105 ------ 7 files changed, 500 insertions(+), 109 deletions(-) create mode 100644 firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c delete mode 100644 firmware/target/arm/stm32/pcm-stm32h7.c 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 From 84fa5389796f366dd6f098bef45a5f4bc6b17d8d Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 27 Feb 2026 20:33:06 +0000 Subject: [PATCH 4/6] echoplayer: add PCM debug menu Display DMA error counters and the SAI underrun counter in the debug menu. Change-Id: I235bfcb0fa965d87c30deeb300535141eda5f974 --- firmware/target/arm/stm32/debug-stm32h7.c | 57 +++++++++++++++++++ .../arm/stm32/echoplayer/pcm-echoplayer.c | 27 +++++++++ 2 files changed, 84 insertions(+) diff --git a/firmware/target/arm/stm32/debug-stm32h7.c b/firmware/target/arm/stm32/debug-stm32h7.c index 99a3849b41..f8cd7200c3 100644 --- a/firmware/target/arm/stm32/debug-stm32h7.c +++ b/firmware/target/arm/stm32/debug-stm32h7.c @@ -86,11 +86,68 @@ static bool swd_menu(void) return simplelist_show_list(&info); } +#if defined(ECHO_R1) +extern volatile int pcm_sai_xrun_count; +extern volatile int pcm_dma_teif_count; +extern volatile int pcm_dma_dmeif_count; +extern volatile int pcm_dma_feif_count; + +struct pcmdebug_counter +{ + const char *name; + volatile int *value; +}; + +static const struct pcmdebug_counter pcmdebug_counters[] = +{ + {"SAI xrun count", &pcm_sai_xrun_count}, + {"DMA TEIF count", &pcm_dma_teif_count}, + {"DMA DMEIF count", &pcm_dma_dmeif_count}, + {"DMA FEIF count", &pcm_dma_feif_count}, +}; + +static int pcmdebug_menu_action_cb(int action, struct gui_synclist *lists) +{ + if (action == ACTION_STD_OK) + { + int item = gui_synclist_get_sel_pos(lists); + const struct pcmdebug_counter *counter = &pcmdebug_counters[item]; + + *counter->value = 0; + action = ACTION_REDRAW; + } + + if (action == ACTION_NONE) + action = ACTION_REDRAW; + + return action; +} + +static const char *pcmdebug_menu_get_name(int item, void *data, char *buf, size_t bufsz) +{ + const struct pcmdebug_counter *counter = &pcmdebug_counters[item]; + (void)data; + + snprintf(buf, bufsz, "%s: %d", counter->name, *counter->value); + return buf; +} + +static bool pcm_debug_menu(void) +{ + struct simplelist_info info; + simplelist_info_init(&info, "PCM debug", ARRAYLEN(pcmdebug_counters), NULL); + info.action_callback = pcmdebug_menu_action_cb; + info.get_name = pcmdebug_menu_get_name; + return simplelist_show_list(&info); +} +#endif + /* Menu definition */ static const struct { const char *name; bool (*function) (void); } menuitems[] = { + {"PCM debug", pcm_debug_menu}, {"SWD/JTAG", swd_menu}, }; diff --git a/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c b/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c index d85acad377..c1d457ef8d 100644 --- a/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c @@ -65,6 +65,11 @@ static volatile int play_active; static int pcm_last_freq = -1; +volatile int pcm_sai_xrun_count; +volatile int pcm_dma_teif_count; +volatile int pcm_dma_dmeif_count; +volatile int pcm_dma_feif_count; + static void play_dma_start(const void *addr, size_t size) { commit_dcache_range(addr, size); @@ -151,8 +156,12 @@ static void sai_init(void) SLOTSZ_V(DATASZ), /* slot size = data size */ FBOFF(0)); /* no bit offset in slot */ + /* Enable xrun error interrupt */ + reg_writelf(sai1a, SAI_SUBBLOCK_IM, OVRUDR(1)); + /* Enable interrupts in NVIC */ nvic_enable_irq(NVIC_IRQN_DMA1_STR0); + nvic_enable_irq(NVIC_IRQN_SAI1); } struct div_settings @@ -326,6 +335,15 @@ void dma1_ch0_irq_handler(void) const void *addr; size_t size; + if (reg_vreadf(lisr, DMA_LISR, TEIF0)) + pcm_dma_teif_count++; + + if (reg_vreadf(lisr, DMA_LISR, DMEIF0)) + pcm_dma_dmeif_count++; + + if (reg_vreadf(lisr, DMA_LISR, FEIF0)) + pcm_dma_feif_count++; + if (reg_vreadf(lisr, DMA_LISR, TEIF0) || reg_vreadf(lisr, DMA_LISR, DMEIF0) || reg_vreadf(lisr, DMA_LISR, FEIF0)) @@ -352,3 +370,12 @@ void dma1_ch0_irq_handler(void) panicf("%s: %08lx", __func__, lisr); } } + +void sai1_irq_handler(void) +{ + if (reg_readlf(sai1a, SAI_SUBBLOCK_SR, OVRUDR)) + { + reg_assignlf(sai1a, SAI_SUBBLOCK_CLRFR, OVRUDR(1)); + pcm_sai_xrun_count++; + } +} From be1e0748008775cadda729f5e8fc2a7ff99bef2d Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 3 Mar 2026 12:26:00 -0500 Subject: [PATCH 5/6] doom: minor code cleanup (replace magic constant with an existing define) Change-Id: I39dd9accb0bb116c0c23035b75b7be141a236c01 --- apps/plugins/doom/i_sound.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/plugins/doom/i_sound.c b/apps/plugins/doom/i_sound.c index 68fb8bdc6c..3581b0e301 100644 --- a/apps/plugins/doom/i_sound.c +++ b/apps/plugins/doom/i_sound.c @@ -462,7 +462,7 @@ void get_more(const void** start, size_t* size) I_UpdateSound(); // Force sound update *start = mixbuffer; - *size = SAMPLECOUNT*2*sizeof(short); + *size = MIXBUFFERSIZE*sizeof(short); } From ea570c5728f256d40f27df57e78183bf02c5f7a6 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Mon, 2 Mar 2026 12:14:34 -0500 Subject: [PATCH 6/6] pcm: Make more of the low-level PCM API private * pcm_play_data * pcm_play_stop * pcm_play_stop_int * pcm_is_playing * pcm_set_frequency * pcm_get_frequency * pcm_apply_settings Now, the only user of these functions are the mixer and recording layers that provide a higher-level API to plugins and the main [playback] application. Outside of the PCM core, pcm_apply_settings() was only used immediately following a call to mixer_set_frequency(), so the latter function now always calls the former. Change-Id: I61c3144dc156b9de9b7963160b525c6d10c6ad4b --- apps/plugin.c | 1 - apps/plugin.h | 3 +-- apps/plugins/lua/include_lua/pcm.lua | 1 - apps/plugins/lua/rocklib.c | 7 ++----- apps/plugins/pdbox/PDa/src/s_audio_rockbox.c | 2 -- apps/plugins/test_sampr.c | 1 - apps/plugins/xrick/system/syssnd_rockbox.c | 2 -- apps/recorder/pcm_record.c | 6 +++++- docs/PLUGIN_API | 4 ---- firmware/export/pcm-internal.h | 15 ++++++++++++++- firmware/export/pcm.h | 19 +------------------ firmware/pcm.c | 2 -- firmware/pcm_mixer.c | 3 +++ firmware/usbstack/usb_audio.c | 6 ++---- 14 files changed, 28 insertions(+), 44 deletions(-) diff --git a/apps/plugin.c b/apps/plugin.c index d15f88c376..2c0e2e778d 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -644,7 +644,6 @@ static const struct plugin_api rockbox_api = { #endif &audio_master_sampr_list[0], &hw_freq_sampr[0], - pcm_apply_settings, pcm_play_lock, pcm_play_unlock, pcm_current_sink_caps, diff --git a/apps/plugin.h b/apps/plugin.h index 69cf7659c9..2dcbdbe39f 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -179,7 +179,7 @@ int plugin_open(const char *plugin, const char *parameter); * when this happens please take the opportunity to sort in * any new functions "waiting" at the end of the list. */ -#define PLUGIN_API_VERSION 279 +#define PLUGIN_API_VERSION 280 /* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */ @@ -743,7 +743,6 @@ struct plugin_api { #endif const unsigned long *audio_master_sampr_list; const unsigned long *hw_freq_sampr; - void (*pcm_apply_settings)(void); void (*pcm_play_lock)(void); void (*pcm_play_unlock)(void); const struct pcm_sink_caps* (*pcm_current_sink_caps)(void); diff --git a/apps/plugins/lua/include_lua/pcm.lua b/apps/plugins/lua/include_lua/pcm.lua index 23f8eb308e..9652c5e3a7 100644 --- a/apps/plugins/lua/include_lua/pcm.lua +++ b/apps/plugins/lua/include_lua/pcm.lua @@ -24,6 +24,5 @@ -- [[ conversion to old style pcm_ functions ]] if not rb.pcm then rb.splash(rb.HZ, "No Support!") return nil end -rb.pcm_apply_settings = function() rb.pcm("apply_settings") end rb.pcm_play_lock = function() rb.pcm("play_lock") end rb.pcm_play_unlock = function() rb.pcm("play_unlock") end diff --git a/apps/plugins/lua/rocklib.c b/apps/plugins/lua/rocklib.c index 0ee104e7fd..4d77afedc0 100644 --- a/apps/plugins/lua/rocklib.c +++ b/apps/plugins/lua/rocklib.c @@ -549,10 +549,10 @@ RB_WRAP(sound) RB_WRAP(pcm) { - enum e_pcm {PCM_APPLYSETTINGS = 0, PCM_PLAYLOCK, PCM_PLAYUNLOCK, + enum e_pcm {PCM_PLAYLOCK = 0, PCM_PLAYUNLOCK, PCM_ECOUNT}; - const char *pcm_option[] = {"apply_settings", "play_lock", "play_unlock", + const char *pcm_option[] = {"play_lock", "play_unlock", NULL}; lua_pushnil(L); /*push nil so options w/o return have something to return */ @@ -560,9 +560,6 @@ RB_WRAP(pcm) int option = luaL_checkoption (L, 1, NULL, pcm_option); switch(option) { - case PCM_APPLYSETTINGS: - rb->pcm_apply_settings(); - break; case PCM_PLAYLOCK: rb->pcm_play_lock(); break; diff --git a/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c b/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c index 17ef90b1d4..5f82f6d393 100644 --- a/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c +++ b/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c @@ -64,7 +64,6 @@ void rockbox_open_audio(int rate) /* Set sample rate of the audio buffer. */ rb->mixer_set_frequency(rate); - rb->pcm_apply_settings(); /* Initialize output buffer. */ for(i = 0; i < OUTBUFSIZE; i++) @@ -86,7 +85,6 @@ void rockbox_close_audio(void) /* Restore default sampling rate. */ rb->mixer_set_frequency(HW_SAMPR_DEFAULT); - rb->pcm_apply_settings(); } /* Rockbox audio callback. */ diff --git a/apps/plugins/test_sampr.c b/apps/plugins/test_sampr.c index 10571a2d98..c73e131072 100644 --- a/apps/plugins/test_sampr.c +++ b/apps/plugins/test_sampr.c @@ -168,7 +168,6 @@ static void set_frequency(int index) update_gen_step(); rb->mixer_set_frequency(hw_sampr); - rb->pcm_apply_settings(); } #ifndef HAVE_VOLUME_IN_LIST diff --git a/apps/plugins/xrick/system/syssnd_rockbox.c b/apps/plugins/xrick/system/syssnd_rockbox.c index dd522cd6f7..afd0766863 100644 --- a/apps/plugins/xrick/system/syssnd_rockbox.c +++ b/apps/plugins/xrick/system/syssnd_rockbox.c @@ -253,7 +253,6 @@ bool syssnd_init(void) #endif rb->mixer_set_frequency(HW_FREQ_44); - rb->pcm_apply_settings(); rb->memset(channels, 0, sizeof(channels)); rb->memset(mixBuffers, 0, sizeof(mixBuffers)); @@ -286,7 +285,6 @@ void syssnd_shutdown(void) /* Restore default sampling rate. */ rb->mixer_set_frequency(HW_SAMPR_DEFAULT); - rb->pcm_apply_settings(); rb->talk_disable(false); diff --git a/apps/recorder/pcm_record.c b/apps/recorder/pcm_record.c index 01f0e604dd..0d5581563f 100644 --- a/apps/recorder/pcm_record.c +++ b/apps/recorder/pcm_record.c @@ -616,6 +616,10 @@ static void init_state(void) record_status = RECORD_STOPPED; } +/* To avoid having to pull in all of pcm-internal.h */ +void pcm_set_frequency(unsigned int samplerate); +void pcm_apply_settings(void); + /* Set hardware samplerate and save it */ static void update_samplerate_config(unsigned long sampr) { @@ -876,7 +880,7 @@ copy_buffer_mono_lr(void *dst, const void *src, size_t src_size) int16_t *d = (int16_t*) dst; int16_t const *s = (int16_t const*) src; ssize_t copy_size = src_size; - + /* mono = (L + R) / 2 */ while(copy_size > 0) { *d++ = ((int32_t)s[0] + (int32_t)s[1] + 1) >> 1; diff --git a/docs/PLUGIN_API b/docs/PLUGIN_API index 8fdc10f9eb..65bf9d4c5b 100644 --- a/docs/PLUGIN_API +++ b/docs/PLUGIN_API @@ -1754,10 +1754,6 @@ void pcmbuf_set_low_latency(bool state) \param state \description -void pcm_apply_settings(void) - \group sound - \description - void pcm_calculate_rec_peaks(int *left, int *right) \group sound \conditions (defined(HAVE_RECORDING)) diff --git a/firmware/export/pcm-internal.h b/firmware/export/pcm-internal.h index 4e9cb58c91..4b4890d35a 100644 --- a/firmware/export/pcm-internal.h +++ b/firmware/export/pcm-internal.h @@ -71,6 +71,20 @@ void pcm_sync_pcm_factors(void); ({ (start) = (void *)(((uintptr_t)(start) + 3) & ~3); \ (size) &= ~3; }) +/* Internal PCM API calls for playback */ +void pcm_play_data(pcm_play_callback_type get_more, + pcm_status_callback_type status_cb, + const void *start, size_t size); + +void pcm_play_stop(void); +void pcm_play_stop_int(void); /* requires PCM lock held */ +bool pcm_is_playing(void); + +void pcm_set_frequency(unsigned int samplerate); +unsigned int pcm_get_frequency(void); +/* apply settings to hardware immediately */ +void pcm_apply_settings(void); + void pcm_do_peak_calculation(struct pcm_peaks *peaks, bool active, const void *addr, int count); @@ -125,7 +139,6 @@ pcm_play_dma_status_callback(enum pcm_dma_status status) #if defined(HAVE_SW_VOLUME_CONTROL) && !defined(PCM_SW_VOLUME_UNBUFFERED) void pcm_play_dma_start_int(const void *addr, size_t size); void pcm_play_dma_stop_int(void); -void pcm_play_stop_int(void); #endif /* HAVE_SW_VOLUME_CONTROL && !PCM_SW_VOLUME_UNBUFFERED */ /* Called by the bottom layer ISR when more data is needed. Returns true diff --git a/firmware/export/pcm.h b/firmware/export/pcm.h index 8c5170ac0e..5f1509ad97 100644 --- a/firmware/export/pcm.h +++ b/firmware/export/pcm.h @@ -44,7 +44,7 @@ typedef void (*pcm_play_callback_type)(const void **start, size_t *size); /* Typedef for registered status callback */ typedef enum pcm_dma_status (*pcm_status_callback_type)(enum pcm_dma_status status); -/* set the pcm frequency - use values in hw_sampr_list +/* set the pcm frequency - use values in hw_sampr_list * when CONFIG_SAMPR_TYPES is #defined, or-in SAMPR_TYPE_* fields with * frequency value. SAMPR_TYPE_PLAY is 0 and the default if none is * specified. */ @@ -54,15 +54,6 @@ unsigned int pcm_sampr_type_rec_to_play(unsigned int samplerate); #endif #endif /* CONFIG_SAMPR_TYPES */ -/* set next frequency to be used */ -void pcm_set_frequency(unsigned int samplerate); -/* return last-set frequency */ -unsigned int pcm_get_frequency(void); -/* apply settings to hardware immediately */ -void pcm_apply_settings(void); - -/** RAW PCM playback routines **/ - /* Reenterable locks for locking and unlocking the playback interrupt */ void pcm_play_lock(void); void pcm_play_unlock(void); @@ -77,11 +68,6 @@ const struct pcm_sink_caps* pcm_sink_caps(enum pcm_sink_ids sink); /* shortcut for plugins */ const struct pcm_sink_caps* pcm_current_sink_caps(void); -/* This is for playing "raw" PCM data */ -void pcm_play_data(pcm_play_callback_type get_more, - pcm_status_callback_type status_cb, - const void *start, size_t size); - /* Kept internally for global PCM and used by mixer's verion of peak calculation */ struct pcm_peaks @@ -92,9 +78,6 @@ struct pcm_peaks long tick; /* Last tick called */ }; -void pcm_play_stop(void); -bool pcm_is_playing(void); - #ifdef HAVE_RECORDING /** RAW PCM recording routines **/ diff --git a/firmware/pcm.c b/firmware/pcm.c index c19ed8e070..0cee855fcc 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c @@ -94,8 +94,6 @@ volatile pcm_status_callback_type /* PCM playback state */ volatile bool pcm_playing SHAREDBSS_ATTR = false; -void pcm_play_stop_int(void); - struct pcm_sink* pcm_get_current_sink(void) { return sinks[cur_sink]; diff --git a/firmware/pcm_mixer.c b/firmware/pcm_mixer.c index ca48e3ba10..d36560f149 100644 --- a/firmware/pcm_mixer.c +++ b/firmware/pcm_mixer.c @@ -475,6 +475,9 @@ void mixer_set_frequency(unsigned int samplerate) mix_frame_size = 1; mix_frame_size *= MIX_FRAME_SAMPLES * 4; + + if (pcm_is_initialized()) + pcm_apply_settings(); } /* Get output samplerate */ diff --git a/firmware/usbstack/usb_audio.c b/firmware/usbstack/usb_audio.c index 3e7bab7f45..f8d431705f 100644 --- a/firmware/usbstack/usb_audio.c +++ b/firmware/usbstack/usb_audio.c @@ -493,7 +493,6 @@ static void set_playback_sampling_frequency(unsigned long f) hw_freq_sampr[as_playback_freq_idx], f); mixer_set_frequency(hw_freq_sampr[as_playback_freq_idx]); - pcm_apply_settings(); } unsigned long usb_audio_get_playback_sampling_frequency(void) @@ -706,7 +705,6 @@ static void usb_audio_start_playback(void) #endif logf("usbaudio: start playback at %lu Hz", hw_freq_sampr[as_playback_freq_idx]); mixer_set_frequency(hw_freq_sampr[as_playback_freq_idx]); - pcm_apply_settings(); mixer_channel_set_amplitude(PCM_MIXER_CHAN_USBAUDIO, MIX_AMP_UNITY); usb_drv_recv_nonblocking(EP_ISO_OUT, rx_buffer, BUFFER_SIZE); @@ -787,7 +785,7 @@ int usb_audio_get_alt_intf(void) { return usb_as_playback_intf_alt; } - + int32_t usb_audio_get_samplesperframe(void) { return samples_fb; @@ -1348,7 +1346,7 @@ bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length) { retval = false; } - + // send feedback value every N frames! // NOTE: important that we need to queue this up _the frame before_ it's needed - on MacOS especially! if ((usb_drv_get_frame_number()+1) % FEEDBACK_UPDATE_RATE_FRAMES == 0 && send_fb)