Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Aidan MacDonald and committed by
Solomon Peachy
5c1ae511 b782f15e

+500 -109
+3 -1
firmware/SOURCES
··· 1942 1942 #endif 1943 1943 target/arm/stm32/gpio-stm32h7.c 1944 1944 target/arm/stm32/i2c-stm32h7.c 1945 - target/arm/stm32/pcm-stm32h7.c 1946 1945 target/arm/stm32/sdmmc-stm32h7.c 1947 1946 target/arm/stm32/spi-stm32h7.c 1948 1947 target/arm/stm32/system-stm32h7.c ··· 1959 1958 target/arm/stm32/echoplayer/clock-echoplayer.c 1960 1959 target/arm/stm32/echoplayer/i2c-echoplayer.c 1961 1960 target/arm/stm32/echoplayer/lcd-echoplayer.c 1961 + #ifndef BOOTLOADER 1962 + target/arm/stm32/echoplayer/pcm-echoplayer.c 1963 + #endif 1962 1964 target/arm/stm32/echoplayer/power-echoplayer.c 1963 1965 target/arm/stm32/echoplayer/sdmmc-echoplayer.c 1964 1966 target/arm/stm32/echoplayer/system-echoplayer.c
+5
firmware/export/config/echor1.h
··· 51 51 /* Codec / audio hardware defines */ 52 52 #define HW_SAMPR_CAPS SAMPR_CAP_ALL_96 // FIXME: check this section 53 53 #define HAVE_ECHOPLAYER_CODEC 54 + #define HAVE_AIC310X 54 55 #define HAVE_SW_TONE_CONTROLS 55 56 #define HAVE_SW_VOLUME_CONTROL 57 + 58 + #ifndef SIMULATOR 59 + #define PCM_NATIVE_BITDEPTH 32 60 + #endif 56 61 57 62 /* Button defines */ 58 63 #define CONFIG_KEYPAD ECHO_R1_PAD
+4
firmware/export/echoplayer_codec.h
··· 21 21 #ifndef __ECHOPLAYER_CODEC_H__ 22 22 #define __ECHOPLAYER_CODEC_H__ 23 23 24 + #include <stdbool.h> 25 + 24 26 /* -79 to 0 dB in 0.5 dB steps; software volume control 25 27 * is used because the hardware volume controls "click" 26 28 * when changing the volume */ 27 29 AUDIOHW_SETTING(VOLUME, "dB", 1, 5, -790, 0, -200); 30 + 31 + void audiohw_mute(bool mute); 28 32 29 33 #endif /* __ECHOPLAYER_CODEC_H__ */
+132 -2
firmware/target/arm/stm32/echoplayer/audiohw-echoplayer.c
··· 19 19 * 20 20 ****************************************************************************/ 21 21 #include "audiohw.h" 22 + #include "kernel.h" 23 + #include "panic.h" 24 + #include "aic310x.h" 25 + #include "pcm_sw_volume.h" 26 + #include "power-echoplayer.h" 27 + #include "gpio-stm32h7.h" 28 + #include "i2c-echoplayer.h" 29 + 30 + static int tlv_read_multiple(uint8_t reg, uint8_t *data, size_t count) 31 + { 32 + return stm32_i2c_read_mem(&i2c1_ctl, AIC310X_I2C_ADDR, reg, data, count); 33 + } 34 + 35 + static int tlv_write_multiple(uint8_t reg, const uint8_t *data, size_t count) 36 + { 37 + return stm32_i2c_write_mem(&i2c1_ctl, AIC310X_I2C_ADDR, reg, data, count); 38 + } 39 + 40 + static struct aic310x tlv_codec = { 41 + .read_multiple = tlv_read_multiple, 42 + .write_multiple = tlv_write_multiple, 43 + }; 44 + 45 + struct codec_reg_data 46 + { 47 + uint8_t reg; 48 + uint8_t val; 49 + }; 50 + 51 + static const struct codec_reg_data codec_init_seq[] = { 52 + /* Set output driver configuration to stereo psuedo-differential */ 53 + { .reg = AIC310X_HEADSET_DETECT_B, .val = 0x08 }, 54 + 55 + /* Clock input is CODEC_CLKIN (bypass codec PLL) */ 56 + { .reg = AIC310X_CLOCK, .val = 0x01 }, 57 + 58 + /* Route left ch. -> left DAC, right ch -> right DAC */ 59 + { .reg = AIC310X_DATA_PATH, .val = 0x0A }, 60 + 61 + /* Power on DACs, set HPLCOM = VCM, set HPRCOM = VCM, 62 + * enable short circuit protection */ 63 + { .reg = AIC310X_HPOUT_DRIVER_CTRL, .val = 0x0C }, 64 + { .reg = AIC310X_DAC_POWER_CTRL, .val = 0xD0 }, 65 + 66 + /* Unmute the DACs */ 67 + { .reg = AIC310X_LEFT_DAC_DIGITAL_VOLUME, .val = 0x00 }, 68 + { .reg = AIC310X_RIGHT_DAC_DIGITAL_VOLUME, .val = 0x00 }, 69 + 70 + /* Route DACs to output drivers */ 71 + { .reg = AIC310X_DAC_L1_TO_HPLOUT_VOLUME, .val = 0x80 }, 72 + { .reg = AIC310X_DAC_R1_TO_HPROUT_VOLUME, .val = 0x80 }, 73 + 74 + /* Route DACs to line out port */ 75 + { .reg = AIC310X_DAC_L1_TO_LEFT_LO_VOLUME, .val = 0x80 }, 76 + { .reg = AIC310X_DAC_R1_TO_RIGHT_LO_VOLUME, .val = 0x80 }, 77 + 78 + /* Power on headphone output drivers */ 79 + { .reg = AIC310X_HPLCOM_LEVEL_CONTROL, .val = 0x01 }, 80 + { .reg = AIC310X_HPRCOM_LEVEL_CONTROL, .val = 0x01 }, 81 + { .reg = AIC310X_HPLOUT_LEVEL_CONTROL, .val = 0x01 }, 82 + { .reg = AIC310X_HPROUT_LEVEL_CONTROL, .val = 0x01 }, 83 + 84 + /* Power up line out drivers */ 85 + { .reg = AIC310X_RIGHT_LO_LEVEL_CONTROL, .val = 0x01 }, 86 + { .reg = AIC310X_LEFT_LO_LEVEL_CONTROL, .val = 0x01 }, 87 + }; 88 + 89 + static void write_aic310x_seq(const struct codec_reg_data *seq, size_t count) 90 + { 91 + while (count > 0) 92 + { 93 + if (aic310x_write(&tlv_codec, seq->reg, seq->val)) 94 + panicf("aic310x wr fail: %02x", (unsigned int)seq->reg); 95 + 96 + seq++; 97 + count--; 98 + } 99 + } 100 + 101 + static void wait_aic310x_active(void) 102 + { 103 + long end_tick = current_tick + HZ; 104 + while (TIME_BEFORE(current_tick, end_tick)) 105 + { 106 + uint8_t tmp; 107 + int err = tlv_read_multiple(AIC310X_PAGE_SELECT, &tmp, 1); 108 + if (err == 0) 109 + return; 110 + 111 + sleep(1); 112 + } 113 + 114 + panicf("aic310x init timeout"); 115 + } 22 116 23 117 void audiohw_init(void) 24 118 { 119 + /* initialize driver */ 120 + aic310x_init(&tlv_codec); 121 + 122 + /* bring 1.8v regulator up */ 123 + echoplayer_enable_1v8_regulator(true); 124 + 125 + /* apply AVDD/DRVDD and DVDD */ 126 + gpio_set_level(GPIO_CODEC_AVDD_EN, 0); 127 + gpio_set_level(GPIO_CODEC_DVDD_EN, 0); 128 + 129 + /* delay for power stabilization */ 130 + sleep(HZ/100); 131 + 132 + /* bring codec out of reset */ 133 + gpio_set_level(GPIO_CODEC_RESET, 1); 25 134 } 26 135 27 136 void audiohw_postinit(void) 28 137 { 138 + wait_aic310x_active(); 139 + write_aic310x_seq(codec_init_seq, ARRAYLEN(codec_init_seq)); 29 140 } 30 141 31 142 void audiohw_close(void) 32 143 { 144 + /* apply reset */ 145 + gpio_set_level(GPIO_CODEC_RESET, 0); 146 + 147 + /* remove power */ 148 + gpio_set_level(GPIO_CODEC_AVDD_EN, 1); 149 + gpio_set_level(GPIO_CODEC_DVDD_EN, 1); 150 + 151 + /* disable regulator */ 152 + echoplayer_enable_1v8_regulator(false); 153 + } 154 + 155 + void audiohw_mute(bool mute) 156 + { 157 + uint8_t wr_val = (mute ? 0x01 : 0x09); 158 + 159 + /* Mute and unmute at the output drivers */ 160 + aic310x_write(&tlv_codec, AIC310X_HPLOUT_LEVEL_CONTROL, wr_val); 161 + aic310x_write(&tlv_codec, AIC310X_HPROUT_LEVEL_CONTROL, wr_val); 162 + aic310x_write(&tlv_codec, AIC310X_LEFT_LO_LEVEL_CONTROL, wr_val); 163 + aic310x_write(&tlv_codec, AIC310X_RIGHT_LO_LEVEL_CONTROL, wr_val); 33 164 } 34 165 35 166 void audiohw_set_volume(int vol_l, int vol_r) 36 167 { 37 - (void)vol_l; 38 - (void)vol_r; 168 + pcm_set_master_volume(vol_l, vol_r); 39 169 }
+2 -1
firmware/target/arm/stm32/echoplayer/clock-echoplayer.c
··· 52 52 /* 53 53 * Use HSE/4 input for PLL1 54 54 * Use HSE/16 input for PLL3 55 + * PLL2 reserved for audio; configured in target PCM code 55 56 */ 56 57 reg_writef(RCC_PLLCKSELR, 57 58 PLLSRC_V(HSE), ··· 164 165 INIT_ATTR static void init_periph_clock(void) 165 166 { 166 167 reg_writef(RCC_D1CCIPR, SDMMCSEL_V(PLL1Q)); 167 - reg_writef(RCC_D2CCIP1R, SPI45SEL_V(HSE)); 168 + reg_writef(RCC_D2CCIP1R, SAI1SEL_V(PLL2P), SPI45SEL_V(HSE)); 168 169 reg_writef(RCC_D2CCIP2R, I2C123SEL_V(HSI)); 169 170 170 171 /* Enable AXI SRAM in sleep mode to allow DMA'ing out of it */
+354
firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2026 Aidan MacDonald 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #include "pcm.h" 22 + #include "pcm_sampr.h" 23 + #include "pcm-internal.h" 24 + #include "audiohw.h" 25 + #include "panic.h" 26 + #include "nvic-arm.h" 27 + #include "dmamux-stm32h743.h" 28 + #include "regs/stm32h743/rcc.h" 29 + #include "regs/stm32h743/sai.h" 30 + #include "regs/stm32h743/dma.h" 31 + #include "regs/stm32h743/dmamux.h" 32 + #include <stdatomic.h> 33 + 34 + /* Configurable bit depth */ 35 + #if PCM_NATIVE_BITDEPTH == 32 36 + # define PCM_NATIVE_SAMPLE_SIZE 4 37 + # define DMA_PSIZE_VAL BV_DMA_CHN_CR_PSIZE_32BIT 38 + # define SAI_DSIZE_VAL BV_SAI_SUBBLOCK_CR1_DS_32BIT 39 + # define SAI_FIFO_THRESH BV_SAI_SUBBLOCK_CR2_FTH_QUARTER 40 + # define SAI_FRL_VAL 64 41 + #elif PCM_NATIVE_BITDEPTH == 24 42 + # define PCM_NATIVE_SAMPLE_SIZE 4 43 + # define DMA_PSIZE_VAL BV_DMA_CHN_CR_PSIZE_32BIT 44 + # define SAI_DSIZE_VAL BV_SAI_SUBBLOCK_CR1_DS_24BIT 45 + # define SAI_FIFO_THRESH BV_SAI_SUBBLOCK_CR2_FTH_QUARTER 46 + # define SAI_FRL_VAL 64 47 + #elif PCM_NATIVE_BITDEPTH == 16 48 + # define PCM_NATIVE_SAMPLE_SIZE 2 49 + # define DMA_PSIZE_VAL BV_DMA_CHN_CR_PSIZE_16BIT 50 + # define SAI_DSIZE_VAL BV_SAI_SUBBLOCK_CR1_DS_16BIT 51 + # define SAI_FIFO_THRESH BV_SAI_SUBBLOCK_CR2_FTH_HALF 52 + # define SAI_FRL_VAL 32 53 + #else 54 + # error "Unsupported PCM bit depth" 55 + #endif 56 + 57 + static const uintptr_t sai1 = ITA_SAI1; 58 + static const uintptr_t sai1a = sai1 + ITO_SAI_A; 59 + static const uintptr_t dmamux1 = ITA_DMAMUX1; 60 + static const uintptr_t dma1 = ITA_DMA1; 61 + static const uintptr_t dma1_ch0 = dma1 + ITO_DMA_CHN(0); 62 + 63 + static atomic_size_t play_lock; 64 + static volatile int play_active; 65 + 66 + static int pcm_last_freq = -1; 67 + 68 + static void play_dma_start(const void *addr, size_t size) 69 + { 70 + commit_dcache_range(addr, size); 71 + 72 + /* 73 + * The number of transfers is defined by PSIZE, which may 74 + * be 16-bit or 32-bit depending on PCM native sample size. 75 + * Each FIFO write transfers 1 sample, so we want to set 76 + * NDT = size / PCM_NATIVE_SAMPLE_SIZE. 77 + * 78 + * When MSIZE == 32-bit and PSIZE == 16-bit, the DMA will 79 + * take each 32-bit word from memory and write the lower/upper 80 + * halfwords separately to the FIFO. The number of reads from 81 + * memory in this case is NDT/2, so we're still transferring 82 + * the correct amount of data. 83 + */ 84 + reg_varl(dma1_ch0, DMA_CHN_NDTR) = size / PCM_NATIVE_SAMPLE_SIZE; 85 + reg_varl(dma1_ch0, DMA_CHN_M0AR) = (uintptr_t)addr; 86 + reg_writelf(dma1_ch0, DMA_CHN_CR, EN(1)); 87 + 88 + pcm_play_dma_status_callback(PCM_DMAST_STARTED); 89 + } 90 + 91 + static void sai_init(void) 92 + { 93 + /* Enable SAI1 and DMA1 */ 94 + reg_writef(RCC_APB2ENR, SAI1EN(1)); 95 + reg_writef(RCC_AHB1ENR, DMA1EN(1)); 96 + 97 + /* Link DMA1_CH0 to SAI1A */ 98 + reg_writelf(dmamux1, DMAMUX_CR(0), DMAREQ_ID(DMAMUX1_REQ_SAI1A_DMA)); 99 + 100 + /* Configure DMA1 channel 0 */ 101 + reg_writelf(dma1_ch0, DMA_CHN_CR, 102 + MBURST_V(INCR4), /* 32-bit x 4 burst = 16 bytes */ 103 + PBURST_V(INCR4), /* (16|32)-bit x 4 burst = 8-16 bytes */ 104 + TRBUFF(0), /* bufferable mode not used for SAI */ 105 + DBM(0), /* double buffer mode off */ 106 + PL_V(VERYHIGH), /* highest priority */ 107 + MSIZE_V(32BIT), /* read 32-bit words from memory */ 108 + PSIZE(DMA_PSIZE_VAL), /* set according to PCM bit depth */ 109 + MINC(1), /* increment memory address */ 110 + PINC(0), /* don't increment peripheral address */ 111 + CIRC(0), /* circular mode off */ 112 + DIR_V(MEM_TO_PERIPH), /* read from memory, write to SAI */ 113 + PFCTRL_V(DMA), /* DMA is flow controller */ 114 + TCIE(1), /* transfer complete interrupt */ 115 + TEIE(1), /* transfer error interrupt */ 116 + DMEIE(1)); /* direct mode error interrupt */ 117 + reg_writelf(dma1_ch0, DMA_CHN_FCR, 118 + FEIE(1), /* fifo error interrupt */ 119 + DMDIS(1), /* enable fifo mode */ 120 + FTH_V(FULL)); /* fifo threshold = 4 words */ 121 + 122 + /* Set peripheral address here since it's constant */ 123 + reg_varl(dma1_ch0, DMA_CHN_PAR) = 124 + (uintptr_t)reg_ptrl(sai1a, SAI_SUBBLOCK_DR); 125 + 126 + /* Configure SAI for playback */ 127 + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, 128 + OSR_V(256FS), /* MCLK is 256 x Fs */ 129 + NOMCK(0), /* MCLK enabled */ 130 + DMAEN(0), /* DMA request disabled */ 131 + SAIEN(0), /* SAI disabled */ 132 + OUTDRIV(0), /* drive outputs when SAIEN=1 */ 133 + MONO(0), /* stereo mode */ 134 + SYNCEN_V(ASYNC), /* no sync with other SAIs */ 135 + CKSTR_V(TX_FALLING_RX_RISING), /* clock edge for sampling */ 136 + LSBFIRST(0), /* transmit samples MSB first */ 137 + DS(SAI_DSIZE_VAL), /* sample size according to bit depth */ 138 + PRTCFG_V(FREE), /* free protocol */ 139 + MODE_V(MASTER_TX)); /* operate as bus master */ 140 + reg_writelf(sai1a, SAI_SUBBLOCK_CR2, 141 + MUTEVAL_V(ZERO_SAMPLE), /* send zero sample on mute */ 142 + MUTE(1), /* mute output initially */ 143 + TRIS(0), /* don't tri-state outputs */ 144 + FTH(SAI_FIFO_THRESH)); /* fifo threshold (2 or 4 samples) */ 145 + reg_writelf(sai1a, SAI_SUBBLOCK_FRCR, 146 + FSOFF(1), FSPOL(0), FSDEF(1), /* I2S format */ 147 + FSALL(SAI_FRL_VAL/2 - 1), /* FS active for half the frame */ 148 + FRL(SAI_FRL_VAL - 1)); /* set frame length */ 149 + reg_writelf(sai1a, SAI_SUBBLOCK_SLOTR, 150 + SLOTEN(3), NBSLOT(2 - 1), /* enable first two slots */ 151 + SLOTSZ_V(DATASZ), /* slot size = data size */ 152 + FBOFF(0)); /* no bit offset in slot */ 153 + 154 + /* Enable interrupts in NVIC */ 155 + nvic_enable_irq(NVIC_IRQN_DMA1_STR0); 156 + } 157 + 158 + struct div_settings 159 + { 160 + uint8_t pllm; 161 + uint8_t plln; 162 + uint8_t pllp; 163 + uint8_t mckdiv; 164 + }; 165 + 166 + _Static_assert(STM32_HSE_FREQ == 24000000, 167 + "Audio clock settings only valid for 24MHz HSE"); 168 + 169 + static const struct div_settings div_settings[] = { 170 + [HW_FREQ_96] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 0 }, /* exact */ 171 + [HW_FREQ_48] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 2 }, /* exact */ 172 + [HW_FREQ_24] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 4 }, /* exact */ 173 + [HW_FREQ_12] = { .pllm = 5, .plln = 128 - 1, .pllp = 25 - 1, .mckdiv = 8 }, /* exact */ 174 + 175 + [HW_FREQ_88] = { .pllm = 4, .plln = 143 - 1, .pllp = 38 - 1, .mckdiv = 0 }, /* -11ppm error */ 176 + [HW_FREQ_44] = { .pllm = 4, .plln = 143 - 1, .pllp = 38 - 1, .mckdiv = 2 }, /* -11ppm error */ 177 + [HW_FREQ_22] = { .pllm = 5, .plln = 147 - 1, .pllp = 125 - 1, .mckdiv = 0 }, /* exact */ 178 + [HW_FREQ_11] = { .pllm = 5, .plln = 147 - 1, .pllp = 125 - 1, .mckdiv = 2 }, /* exact */ 179 + 180 + [HW_FREQ_64] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 0 }, /* exact */ 181 + [HW_FREQ_32] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 2 }, /* exact */ 182 + [HW_FREQ_16] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 4 }, /* exact */ 183 + [HW_FREQ_8 ] = { .pllm = 15, .plln = 256 - 1, .pllp = 25 - 1, .mckdiv = 8 }, /* exact */ 184 + }; 185 + 186 + static void sai_set_pll(const struct div_settings *div) 187 + { 188 + /* Disable the PLL so it can be reconfigured */ 189 + if (reg_readf(RCC_CR, PLL2RDY)) 190 + { 191 + reg_writef(RCC_CR, PLL2ON(0)); 192 + while (reg_readf(RCC_CR, PLL2RDY)); 193 + while (reg_readf(RCC_CR, PLL2ON)); 194 + } 195 + 196 + /* Set the PLL configuration */ 197 + uint32_t pllcfgr = reg_var(RCC_PLLCFGR); 198 + 199 + reg_writef(RCC_PLLCKSELR, 200 + DIVM2(div->pllm)); 201 + reg_writef(RCC_PLL2DIVR, 202 + DIVN(div->plln), 203 + DIVP(div->pllp), 204 + DIVQ(0), DIVR(0)); 205 + reg_vwritef(pllcfgr, RCC_PLLCFGR, 206 + DIVP2EN(1), 207 + DIVQ2EN(0), 208 + DIVR2EN(0), 209 + PLL2FRACEN(0), 210 + PLL2VCOSEL_V(WIDE)); 211 + 212 + if (div->pllm <= 6) 213 + reg_vwritef(pllcfgr, RCC_PLLCFGR, PLL2RGE_V(4_8MHZ)); 214 + else if (div->pllm <= 12) 215 + reg_vwritef(pllcfgr, RCC_PLLCFGR, PLL2RGE_V(2_4MHZ)); 216 + else 217 + reg_vwritef(pllcfgr, RCC_PLLCFGR, PLL2RGE_V(1_2MHZ)); 218 + 219 + reg_var(RCC_PLLCFGR) = pllcfgr; 220 + 221 + /* Enable the PLL again */ 222 + reg_writef(RCC_CR, PLL2ON(1)); 223 + while (!reg_readf(RCC_CR, PLL2RDY)); 224 + } 225 + 226 + static void sai_set_frequency(int freq) 227 + { 228 + /* Can't change frequency while the SAI is active */ 229 + if (reg_readlf(sai1a, SAI_SUBBLOCK_CR1, SAIEN)) 230 + panicf("%s while SAI active", __func__); 231 + 232 + const struct div_settings *div = &div_settings[freq]; 233 + const struct div_settings *old_div = NULL; 234 + 235 + if (pcm_last_freq >= 0) 236 + old_div = &div_settings[pcm_last_freq]; 237 + 238 + if (old_div == NULL || 239 + div->pllm != old_div->pllm || 240 + div->plln != old_div->plln || 241 + div->pllp != old_div->pllp) 242 + { 243 + sai_set_pll(div); 244 + } 245 + 246 + /* Configure the MCK divider in SAI */ 247 + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, MCKDIV(div->mckdiv)); 248 + } 249 + 250 + static void sink_dma_init(void) 251 + { 252 + sai_init(); 253 + audiohw_init(); 254 + } 255 + 256 + static void sink_set_freq(uint16_t freq) 257 + { 258 + /* 259 + * Muting here doesn't seem to help clicks when switching 260 + * tracks of different frequencies; the audio may need to 261 + * be soft-muted in software before the switch. 262 + */ 263 + audiohw_mute(true); 264 + 265 + sai_set_frequency(freq); 266 + pcm_last_freq = freq; 267 + 268 + audiohw_mute(false); 269 + } 270 + 271 + static void sink_dma_start(const void *addr, size_t size) 272 + { 273 + play_active = true; 274 + 275 + play_dma_start(addr, size); 276 + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, SAIEN(1), DMAEN(1)); 277 + reg_writelf(sai1a, SAI_SUBBLOCK_CR2, MUTE(0)); 278 + } 279 + 280 + static void sink_dma_stop(void) 281 + { 282 + play_active = false; 283 + 284 + reg_writelf(sai1a, SAI_SUBBLOCK_CR2, MUTE(1)); 285 + reg_writelf(sai1a, SAI_SUBBLOCK_CR1, SAIEN(0), DMAEN(0)); 286 + reg_writelf(dma1_ch0, DMA_CHN_CR, EN(0)); 287 + 288 + /* Must wait for SAIEN bit to be cleared */ 289 + while (reg_readlf(sai1a, SAI_SUBBLOCK_CR1, SAIEN)); 290 + } 291 + 292 + static void sink_lock(void) 293 + { 294 + /* Disable IRQ on first lock */ 295 + if (atomic_fetch_add_explicit(&play_lock, 1, memory_order_relaxed) == 0) 296 + nvic_disable_irq_sync(NVIC_IRQN_DMA1_STR0); 297 + } 298 + 299 + static void sink_unlock(void) 300 + { 301 + /* Enable IRQ on release of last lock */ 302 + if (atomic_fetch_sub_explicit(&play_lock, 1, memory_order_relaxed) == 1) 303 + nvic_enable_irq(NVIC_IRQN_DMA1_STR0); 304 + } 305 + 306 + struct pcm_sink builtin_pcm_sink = { 307 + .caps = { 308 + .samprs = hw_freq_sampr, 309 + .num_samprs = HW_NUM_FREQ, 310 + .default_freq = HW_FREQ_DEFAULT, 311 + }, 312 + .ops = { 313 + .init = sink_dma_init, 314 + .postinit = audiohw_postinit, 315 + .set_freq = sink_set_freq, 316 + .lock = sink_lock, 317 + .unlock = sink_unlock, 318 + .play = sink_dma_start, 319 + .stop = sink_dma_stop, 320 + }, 321 + }; 322 + 323 + void dma1_ch0_irq_handler(void) 324 + { 325 + uint32_t lisr = reg_varl(dma1, DMA_LISR); 326 + const void *addr; 327 + size_t size; 328 + 329 + if (reg_vreadf(lisr, DMA_LISR, TEIF0) || 330 + reg_vreadf(lisr, DMA_LISR, DMEIF0) || 331 + reg_vreadf(lisr, DMA_LISR, FEIF0)) 332 + { 333 + reg_assignlf(dma1, DMA_LIFCR, TEIF0(1), DMEIF0(1), FEIF0(1)); 334 + reg_writelf(dma1_ch0, DMA_CHN_CR, EN(0)); 335 + 336 + pcm_play_dma_status_callback(PCM_DMAST_ERR_DMA); 337 + } 338 + else if (reg_vreadf(lisr, DMA_LISR, TCIF0)) 339 + { 340 + reg_assignlf(dma1, DMA_LIFCR, TCIF0(1)); 341 + 342 + /* If we call the complete callback while not playing 343 + * it can cause the PCM layer to get stuck... somehow */ 344 + if (!play_active) 345 + return; 346 + 347 + if (pcm_play_dma_complete_callback(PCM_DMAST_OK, &addr, &size)) 348 + play_dma_start(addr, size); 349 + } 350 + else 351 + { 352 + panicf("%s: %08lx", __func__, lisr); 353 + } 354 + }
-105
firmware/target/arm/stm32/pcm-stm32h7.c
··· 1 - /*************************************************************************** 2 - * __________ __ ___. 3 - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 - * \/ \/ \/ \/ \/ 8 - * $Id$ 9 - * 10 - * Copyright (C) 2025 Aidan MacDonald 11 - * 12 - * This program is free software; you can redistribute it and/or 13 - * modify it under the terms of the GNU General Public License 14 - * as published by the Free Software Foundation; either version 2 15 - * of the License, or (at your option) any later version. 16 - * 17 - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 - * KIND, either express or implied. 19 - * 20 - ****************************************************************************/ 21 - #include "pcm.h" 22 - #include "pcm-internal.h" 23 - #include "pcm_sampr.h" 24 - #include "pcm_sink.h" 25 - 26 - static void sink_set_freq(uint16_t freq) 27 - { 28 - (void)freq; 29 - } 30 - 31 - static void sink_dma_init(void) 32 - { 33 - } 34 - 35 - static void sink_dma_postinit(void) 36 - { 37 - } 38 - 39 - static void sink_dma_start(const void *addr, size_t size) 40 - { 41 - (void)addr; 42 - (void)size; 43 - } 44 - 45 - static void sink_dma_stop(void) 46 - { 47 - } 48 - 49 - static void sink_lock(void) 50 - { 51 - } 52 - 53 - static void sink_unlock(void) 54 - { 55 - } 56 - 57 - struct pcm_sink builtin_pcm_sink = { 58 - .caps = { 59 - .samprs = hw_freq_sampr, 60 - .num_samprs = HW_NUM_FREQ, 61 - .default_freq = HW_FREQ_DEFAULT, 62 - }, 63 - .ops = { 64 - .init = sink_dma_init, 65 - .postinit = sink_dma_postinit, 66 - .set_freq = sink_set_freq, 67 - .lock = sink_lock, 68 - .unlock = sink_unlock, 69 - .play = sink_dma_start, 70 - .stop = sink_dma_stop, 71 - }, 72 - }; 73 - 74 - #ifdef HAVE_RECORDING 75 - void pcm_rec_dma_init(void) 76 - { 77 - } 78 - 79 - void pcm_rec_dma_close(void) 80 - { 81 - } 82 - 83 - void pcm_rec_dma_start(void *addr, size_t size) 84 - { 85 - (void)addr; 86 - (void)size; 87 - } 88 - 89 - void pcm_rec_dma_stop(void) 90 - { 91 - } 92 - 93 - void pcm_rec_lock(void) 94 - { 95 - } 96 - 97 - void pcm_rec_unlock(void) 98 - { 99 - } 100 - 101 - const void *pcm_rec_dma_get_peak_buffer(void) 102 - { 103 - return NULL; 104 - } 105 - #endif