mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-04-11 16:37:45 -04:00
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
This commit is contained in:
parent
b782f15eaa
commit
5c1ae51193
7 changed files with 500 additions and 109 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -21,9 +21,13 @@
|
|||
#ifndef __ECHOPLAYER_CODEC_H__
|
||||
#define __ECHOPLAYER_CODEC_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/* -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__ */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
354
firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c
Normal file
354
firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c
Normal file
|
|
@ -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 <stdatomic.h>
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue