Compare commits

..

No commits in common. "ea570c5728f256d40f27df57e78183bf02c5f7a6" and "a1b7e7b1345f3e3d1f9616581851b1b8807fab68" have entirely different histories.

25 changed files with 219 additions and 681 deletions

View file

@ -12507,6 +12507,20 @@
*: "Средна побитова скорост"
</voice>
</phrase>
<phrase>
id: LANG_PLAYTIME_ERROR
desc: playing time screen
user: core
<source>
*: "Error while gathering info"
</source>
<dest>
*: "Грешка при събирането на информация"
</dest>
<voice>
*: "Грешка при събирането на информация"
</voice>
</phrase>
<phrase>
id: LANG_PLAYING_TIME
desc: onplay menu
@ -14672,7 +14686,7 @@
user: core
<source>
*: "Press LEFT to cancel."
android,hifietma*: "Press BACK to cancel."
android,hifietma*,zenvision: "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."
@ -14687,7 +14701,7 @@
</source>
<dest>
*: "Натиснете LEFT за отказ."
android,hifietma*: "Натиснете BACK за отказ."
android,hifietma*,zenvision: "Натиснете BACK за отказ."
cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Натиснете POWER за отказ."
ihifi760,ihifi960: "Натиснете двукратно RETURN за отказ."
ihifi770,ihifi770c,ihifi800: "Натиснете HOME за отказ."
@ -14702,7 +14716,7 @@
</dest>
<voice>
*: "Натиснете LEFT за отказ."
android,hifietma*: "Натиснете BACK за отказ."
android,hifietma*,zenvision: "Натиснете BACK за отказ."
cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Натиснете POWER за отказ."
ihifi760,ihifi960: "Натиснете двукратно RETURN за отказ."
ihifi770,ihifi770c,ihifi800: "Натиснете HOME за отказ."
@ -15197,13 +15211,13 @@
</phrase>
<phrase>
id: LANG_VOICED_DATE_FORMAT
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
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"
user: core
<source>
*: "dAY"
</source>
<dest>
*: "dAy"
*: "~dAY"
</dest>
<voice>
*: ""
@ -15818,6 +15832,20 @@
*: "Браузър по подразбиране"
</voice>
</phrase>
<phrase>
id: LANG_AMAZE_MENU
desc: Amaze game
user: core
<source>
*: "Amaze Main Menu"
</source>
<dest>
*: "Главно меню на Amaze"
</dest>
<voice>
*: "Главно меню на Amaze"
</voice>
</phrase>
<phrase>
id: LANG_SET_MAZE_SIZE
desc: Maze size in Amaze game
@ -16070,6 +16098,34 @@
*: "Настройки на Mik mod"
</voice>
</phrase>
<phrase>
id: LANG_MIKMOD_MENU
desc: mikmod plugin
user: core
<source>
*: "Mikmod Menu"
</source>
<dest>
*: "Меню на Mikmod"
</dest>
<voice>
*: "Меню на Mik mod"
</voice>
</phrase>
<phrase>
id: LANG_CHESSBOX_MENU
desc: chessbox plugin
user: core
<source>
*: "Chessbox Menu"
</source>
<dest>
*: "Меню на Chessbox"
</dest>
<voice>
*: "Меню на Chess box"
</voice>
</phrase>
<phrase>
id: VOICE_INVALID_VOICE_FILE
desc: played if the voice file fails to load
@ -16931,48 +16987,3 @@
usbdac: "USB ЦАП е активен"
</voice>
</phrase>
<phrase>
id: LANG_ANNOUNCE_STATUS
desc: announnnce_status plugin
user: core
<source>
*: "Announce Status"
</source>
<dest>
*: "Съобщаване на състоянието"
</dest>
<voice>
*: "Съобщаване на състоянието"
</voice>
</phrase>
<phrase>
id: LANG_USE_LED_INDICATORS
desc: LED indicators setting
user: core
<source>
*: none
general_purpose_led: "Use LED indicators"
</source>
<dest>
*: none
general_purpose_led: "Използване на светодиодни индикатори"
</dest>
<voice>
*: none
general_purpose_led: "Използване на светодиодни индикатори"
</voice>
</phrase>
<phrase>
id: LANG_KEEP_DIRECTORY
desc: file browser setting
user: core
<source>
*: "Always remember last folder"
</source>
<dest>
*: "Винаги помни последната папка"
</dest>
<voice>
*: "Винаги помни последната папка"
</voice>
</phrase>

View file

@ -14690,7 +14690,7 @@
user: core
<source>
*: "Press LEFT to cancel."
android,hifietma*: "Press BACK to cancel."
android,hifietma*,zenvision: "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 @@
</source>
<dest>
*: "Zum Abbrechen LINKS drücken."
android,hifietma*,vibe500: "Zum Abbrechen ZURÜCK drücken."
android,hifietma*,vibe500,zenvision: "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 @@
</dest>
<voice>
*: "Zum Abbrechen LINKS drücken."
android,hifietma*,vibe500: "Zum Abbrechen ZURÜCK drücken."
android,hifietma*,vibe500,zenvision: "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 @@
</phrase>
<phrase>
id: LANG_VOICED_DATE_FORMAT
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
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"
user: core
<source>
*: "dAY"
@ -16978,17 +16978,3 @@
*: "Status mitteilen"
</voice>
</phrase>
<phrase>
id: LANG_KEEP_DIRECTORY
desc: file browser setting
user: core
<source>
*: "Always remember last folder"
</source>
<dest>
*: "Zuletzt verwendeten Ordner immer merken"
</dest>
<voice>
*: "Zuletzt verwendeten Ordner immer merken"
</voice>
</phrase>

View file

@ -644,6 +644,7 @@ 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,

View file

@ -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 280
#define PLUGIN_API_VERSION 279
/* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */
@ -743,6 +743,7 @@ 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);

View file

@ -462,7 +462,7 @@ void get_more(const void** start, size_t* size)
I_UpdateSound(); // Force sound update
*start = mixbuffer;
*size = MIXBUFFERSIZE*sizeof(short);
*size = SAMPLECOUNT*2*sizeof(short);
}

View file

@ -24,5 +24,6 @@
-- [[ 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

View file

@ -549,10 +549,10 @@ RB_WRAP(sound)
RB_WRAP(pcm)
{
enum e_pcm {PCM_PLAYLOCK = 0, PCM_PLAYUNLOCK,
enum e_pcm {PCM_APPLYSETTINGS = 0, PCM_PLAYLOCK, PCM_PLAYUNLOCK,
PCM_ECOUNT};
const char *pcm_option[] = {"play_lock", "play_unlock",
const char *pcm_option[] = {"apply_settings", "play_lock", "play_unlock",
NULL};
lua_pushnil(L); /*push nil so options w/o return have something to return */
@ -560,6 +560,9 @@ 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;

View file

@ -64,6 +64,7 @@ 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++)
@ -85,6 +86,7 @@ void rockbox_close_audio(void)
/* Restore default sampling rate. */
rb->mixer_set_frequency(HW_SAMPR_DEFAULT);
rb->pcm_apply_settings();
}
/* Rockbox audio callback. */

View file

@ -168,6 +168,7 @@ static void set_frequency(int index)
update_gen_step();
rb->mixer_set_frequency(hw_sampr);
rb->pcm_apply_settings();
}
#ifndef HAVE_VOLUME_IN_LIST

View file

@ -253,6 +253,7 @@ 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));
@ -285,6 +286,7 @@ void syssnd_shutdown(void)
/* Restore default sampling rate. */
rb->mixer_set_frequency(HW_SAMPR_DEFAULT);
rb->pcm_apply_settings();
rb->talk_disable(false);

View file

@ -616,10 +616,6 @@ 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)
{
@ -880,7 +876,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;

View file

@ -1754,6 +1754,10 @@ 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))

View file

@ -1942,6 +1942,7 @@ 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
@ -1958,9 +1959,6 @@ 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

View file

@ -51,14 +51,9 @@
/* 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

View file

@ -21,13 +21,9 @@
#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__ */

View file

@ -71,20 +71,6 @@ 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);
@ -139,6 +125,7 @@ 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

View file

@ -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,6 +54,15 @@ 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);
@ -68,6 +77,11 @@ 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
@ -78,6 +92,9 @@ 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 **/

View file

@ -94,6 +94,8 @@ 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];

View file

@ -475,9 +475,6 @@ 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 */

View file

@ -86,68 +86,11 @@ 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},
};

View file

@ -19,151 +19,21 @@
*
****************************************************************************/
#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)
{
pcm_set_master_volume(vol_l, vol_r);
(void)vol_l;
(void)vol_r;
}

View file

@ -52,7 +52,6 @@ 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),
@ -165,7 +164,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, SAI1SEL_V(PLL2P), SPI45SEL_V(HSE));
reg_writef(RCC_D2CCIP1R, SPI45SEL_V(HSE));
reg_writef(RCC_D2CCIP2R, I2C123SEL_V(HSI));
/* Enable AXI SRAM in sleep mode to allow DMA'ing out of it */

View file

@ -1,381 +0,0 @@
/***************************************************************************
* __________ __ ___.
* 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;
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);
/*
* 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 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
{
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))
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))
{
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);
}
}
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++;
}
}

View file

@ -0,0 +1,105 @@
/***************************************************************************
* __________ __ ___.
* 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

View file

@ -493,6 +493,7 @@ 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)
@ -705,6 +706,7 @@ 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);
@ -785,7 +787,7 @@ int usb_audio_get_alt_intf(void)
{
return usb_as_playback_intf_alt;
}
int32_t usb_audio_get_samplesperframe(void)
{
return samples_fb;
@ -1346,7 +1348,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)