pcm: introduce pcm_sink

rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]>
Newsgroups gmane.comp.systems.archos.rockbox.cvs
Message-ID <[email protected]>
commit dfa33c246b5f0244e0703722c63fe91f94633a97
Author: mojyack <[email protected]>
Date:   Wed Dec 17 16:19:07 2025 +0900

    pcm: introduce pcm_sink
    
    move target-specific pcm operations into builtin_pcm_sink.
    in subsequent commits, another pcm_sink is added, and it becomes
    possible to switch between them.
    
    Change-Id: I8f8b9661e01d6e6472f34224ddc3760856778457

diff --git a/apps/plugin.c b/apps/plugin.c
index 0aa800b5cd..d15f88c376 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -647,6 +647,7 @@ static const struct plugin_api rockbox_api = {
     pcm_apply_settings,
     pcm_play_lock,
     pcm_play_unlock,
+    pcm_current_sink_caps,
     beep_play,
 #ifdef HAVE_RECORDING
     &rec_freq_sampr[0],
diff --git a/apps/plugin.h b/apps/plugin.h
index 52c11a5af3..69cf7659c9 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -93,6 +93,7 @@ int plugin_open(const char *plugin, const char *parameter);
 #include "misc.h"
 #include "pathfuncs.h"
 #include "pcm_mixer.h"
+#include "pcm_sink.h"
 #include "dsp-util.h"
 #include "dsp_core.h"
 #include "dsp_proc_settings.h"
@@ -745,6 +746,7 @@ struct plugin_api {
     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);
     void (*beep_play)(unsigned int frequency, unsigned int duration,
                       unsigned int amplitude);
 #ifdef HAVE_RECORDING
diff --git a/docs/PLUGIN_API b/docs/PLUGIN_API
index af999d317b..8fdc10f9eb 100644
--- a/docs/PLUGIN_API
+++ b/docs/PLUGIN_API
@@ -1783,6 +1783,10 @@ void pcm_play_unlock(void)
     \group sound
     \description
 
+const struct pcm_sink_caps* pcm_current_sink_caps(enum pcm_sink_ids sink)
+    \group sound
+    \description
+
 void pcm_record_data(pcm_rec_callback_type more_ready, pcm_status_callback_type status_cb, void *start, size_t size)
     \group sound
     \conditions (defined(HAVE_RECORDING))
diff --git a/firmware/export/pcm-internal.h b/firmware/export/pcm-internal.h
index fde7446014..4e9cb58c91 100644
--- a/firmware/export/pcm-internal.h
+++ b/firmware/export/pcm-internal.h
@@ -22,7 +22,11 @@
 #ifndef PCM_INTERNAL_H
 #define PCM_INTERNAL_H
 
+#include <stdbool.h>
+
 #include "config.h"
+#include "pcm.h"
+#include "pcm_sink.h"
 #include "gcc_extensions.h" /* for FORCE_INLINE */
 
 #ifdef HAVE_SW_VOLUME_CONTROL
@@ -129,24 +133,12 @@ void pcm_play_stop_int(void);
 bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
                                     const void **addr, size_t *size);
 
-extern unsigned long pcm_curr_sampr;
-extern unsigned long pcm_sampr;
-extern int pcm_fsel;
-
 #ifdef HAVE_PCM_DMA_ADDRESS
 void * pcm_dma_addr(void *addr);
 #endif
 
 extern volatile bool pcm_playing;
-
-void pcm_play_dma_lock(void);
-void pcm_play_dma_unlock(void);
-void pcm_play_dma_init(void) INIT_ATTR;
-void pcm_play_dma_postinit(void);
-void pcm_play_dma_start(const void *addr, size_t size);
-void pcm_play_dma_stop(void);
-
-void pcm_dma_apply_settings(void);
+struct pcm_sink* pcm_get_current_sink(void);
 
 #ifdef HAVE_RECORDING
 
diff --git a/firmware/export/pcm.h b/firmware/export/pcm.h
index 9a89375c08..8c5170ac0e 100644
--- a/firmware/export/pcm.h
+++ b/firmware/export/pcm.h
@@ -71,6 +71,12 @@ void pcm_init(void) INIT_ATTR;
 void pcm_postinit(void);
 bool pcm_is_initialized(void);
 
+enum pcm_sink_ids pcm_current_sink(void);
+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,
diff --git a/firmware/export/pcm_sink.h b/firmware/export/pcm_sink.h
new file mode 100644
index 0000000000..27c0c31db1
--- /dev/null
+++ b/firmware/export/pcm_sink.h
@@ -0,0 +1,57 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ * Copyright (C) 2025 by Sho Tanimoto
+ *
+ * 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.
+ *
+ ****************************************************************************/
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+struct pcm_sink_caps {
+    const unsigned long* samprs;
+    uint16_t             num_samprs;
+    uint16_t             default_freq;
+};
+
+struct pcm_sink_ops {
+    void (*init)(void);
+    void (*postinit)(void);
+    void (*set_freq)(uint16_t freq);
+    void (*lock)(void);
+    void (*unlock)(void);
+    void (*play)(const void* addr, size_t size);
+    void (*stop)(void);
+};
+
+struct pcm_sink {
+    /* characteristics */
+    const struct pcm_sink_caps caps;
+
+    /* operations */
+    const struct pcm_sink_ops ops;
+
+    /* runtime states */
+    unsigned long pending_freq;
+    unsigned long configured_freq;
+};
+
+enum pcm_sink_ids {
+    PCM_SINK_BUILTIN = 0,
+};
+
+/* defined in each platform pcm source */
+extern struct pcm_sink builtin_pcm_sink;
diff --git a/firmware/pcm.c b/firmware/pcm.c
index de01af484f..c19ed8e070 100644
--- a/firmware/pcm.c
+++ b/firmware/pcm.c
@@ -42,21 +42,19 @@
  *   Semi-private -
  *      pcm_play_dma_complete_callback
  *      pcm_play_dma_status_callback
- *      pcm_play_dma_init
- *      pcm_play_dma_postinit
- *      pcm_play_dma_start
- *      pcm_play_dma_stop
+ *      pcm_get_current_sink
+ *      pcm_sink.init
+ *      pcm_sink.postinit
+ *      pcm_sink.play
+ *      pcm_sink.stop
  *   Data Read/Written within TSP -
- *      pcm_sampr (R)
- *      pcm_fsel (R)
- *      pcm_curr_sampr (R)
  *      pcm_playing (R)
  *
  * ==Playback/Recording==
  *   Public -
  *      pcm_dma_addr
  *   Semi-private -
- *      pcm_dma_apply_settings
+ *      pcm_sink.set_freq
  *
  * ==Recording==
  *   Public -
@@ -81,6 +79,12 @@
 /* 'true' when all stages of pcm initialization have completed */
 static bool pcm_is_ready = false;
 
+static struct pcm_sink* sinks[1] = {
+    [PCM_SINK_BUILTIN] = &builtin_pcm_sink,
+};
+static enum pcm_sink_ids cur_sink = PCM_SINK_BUILTIN;
+static struct mutex sink_mutex; /* protects sinks and cur_sink */
+
 /* The registered callback function to ask for more mp3 data */
 volatile pcm_play_callback_type
     pcm_callback_for_more SHAREDBSS_ATTR = NULL;
@@ -89,16 +93,14 @@ volatile pcm_status_callback_type
     pcm_play_status_callback SHAREDBSS_ATTR = NULL;
 /* PCM playback state */
 volatile bool pcm_playing SHAREDBSS_ATTR = false;
-/* samplerate of currently playing audio - undefined if stopped */
-unsigned long pcm_curr_sampr SHAREDBSS_ATTR = 0;
-/* samplerate waiting to be set */
-unsigned long pcm_sampr SHAREDBSS_ATTR = HW_SAMPR_DEFAULT;
-/* samplerate frequency selection index */
-int pcm_fsel SHAREDBSS_ATTR = HW_FREQ_DEFAULT;
-
-static void pcm_play_data_start_int(const void *addr, size_t size);
+
 void pcm_play_stop_int(void);
 
+struct pcm_sink* pcm_get_current_sink(void)
+{
+    return sinks[cur_sink];
+}
+
 #if !defined(HAVE_SW_VOLUME_CONTROL) || defined(PCM_SW_VOLUME_UNBUFFERED)
 /** Standard hw volume/unbuffered control functions - otherwise, see
  ** pcm_sw_volume.c **/
@@ -108,12 +110,12 @@ static inline void pcm_play_dma_start_int(const void *addr, size_t size)
     /* Smoothed transition might not have happened so sync now */
     pcm_sync_pcm_factors();
 #endif
-    pcm_play_dma_start(addr, size);
+    sinks[cur_sink]->ops.play(addr, size);
 }
 
 static inline void pcm_play_dma_stop_int(void)
 {
-    pcm_play_dma_stop();
+    sinks[cur_sink]->ops.stop();
 }
 
 bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
@@ -123,8 +125,9 @@ bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
     if (status < PCM_DMAST_OK)
         status = pcm_play_dma_status_callback(status);
 
-    if (status >= PCM_DMAST_OK && pcm_get_more_int(addr, size))
+    if (status >= PCM_DMAST_OK && pcm_get_more_int(addr, size)) {
         return true;
+    }
 
     /* Error, callback missing or no more DMA to do */
     pcm_play_stop_int();
@@ -132,25 +135,6 @@ bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
 }
 #endif /* !HAVE_SW_VOLUME_CONTROL || PCM_SW_VOLUME_UNBUFFERED */
 
-static void pcm_play_data_start_int(const void *addr, size_t size)
-{
-    ALIGN_AUDIOBUF(addr, size);
-
-    if ((addr && size) || pcm_get_more_int(&addr, &size))
-    {
-        pcm_apply_settings();
-        logf(" pcm_play_dma_start_int");
-        pcm_play_dma_start_int(addr, size);
-        pcm_playing = true;
-    }
-    else
-    {
-        /* Force a stop */
-        logf(" pcm_play_stop_int");
-        pcm_play_stop_int();
-    }
-}
-
 void pcm_play_stop_int(void)
 {
     pcm_play_dma_stop_int();
@@ -223,7 +207,15 @@ void pcm_do_peak_calculation(struct pcm_peaks *peaks, bool active,
 
     if (active)
     {
-        int framecount = peaks->period*pcm_curr_sampr / HZ;
+        struct pcm_sink* sink = sinks[cur_sink];
+        if (sink->configured_freq == -1UL)
+        {
+            logf("not configured yet");
+            return;
+        }
+
+        unsigned long sampr = sink->caps.samprs[sink->configured_freq];
+        int framecount = peaks->period * sampr / HZ;
         count = MIN(framecount, count);
 
         if (count > 0)
@@ -247,16 +239,29 @@ bool pcm_is_playing(void)
  * interface
  */
 
+void pcm_play_lock(void) {
+    mutex_lock(&sink_mutex);
+    sinks[cur_sink]->ops.lock();
+    /* hold sink_mutex until pcm_play_unlock() */
+}
+
+void pcm_play_unlock(void) {
+    sinks[cur_sink]->ops.unlock();
+    mutex_unlock(&sink_mutex);
+}
+
 /* This should only be called at startup before any audio playback or
    recording is attempted */
 void pcm_init(void)
 {
     logf("pcm_init");
 
-    pcm_set_frequency(HW_SAMPR_DEFAULT);
-
-    logf(" pcm_play_dma_init");
-    pcm_play_dma_init();
+    mutex_init(&sink_mutex);
+    for(size_t i = 0; i < ARRAYLEN(sinks); i += 1) {
+        sinks[i]->pending_freq = sinks[i]->caps.default_freq;
+        sinks[i]->configured_freq = -1UL;
+        sinks[i]->ops.init();
+    }
 }
 
 /* Finish delayed init */
@@ -264,9 +269,9 @@ void pcm_postinit(void)
 {
     logf("pcm_postinit");
 
-    logf(" pcm_play_dma_postinit");
-
-    pcm_play_dma_postinit();
+    for(size_t i = 0; i < ARRAYLEN(sinks); i += 1) {
+        sinks[i]->ops.postinit();
+    }
 
     pcm_is_ready = true;
 }
@@ -276,6 +281,21 @@ bool pcm_is_initialized(void)
     return pcm_is_ready;
 }
 
+enum pcm_sink_ids pcm_current_sink(void)
+{
+    return cur_sink;
+}
+
+const struct pcm_sink_caps* pcm_sink_caps(enum pcm_sink_ids sink)
+{
+    return &sinks[sink]->caps;
+}
+
+const struct pcm_sink_caps* pcm_current_sink_caps(void)
+{
+    return pcm_sink_caps(pcm_current_sink());
+}
+
 void pcm_play_data(pcm_play_callback_type get_more,
                    pcm_status_callback_type status_cb,
                    const void *start, size_t size)
@@ -287,8 +307,20 @@ void pcm_play_data(pcm_play_callback_type get_more,
     pcm_callback_for_more = get_more;
     pcm_play_status_callback = status_cb;
 
-    logf(" pcm_play_data_start_int");
-    pcm_play_data_start_int(start, size);
+    ALIGN_AUDIOBUF(start, size);
+    if ((start && size) || pcm_get_more_int(&start, &size))
+    {
+        pcm_apply_settings();
+        logf(" pcm_play_dma_start_int");
+        pcm_play_dma_start_int(start, size);
+        pcm_playing = true;
+    }
+    else
+    {
+        /* Force a stop */
+        logf(" pcm_play_stop_int");
+        pcm_play_stop_int();
+    }
 
     pcm_play_unlock();
 }
@@ -329,20 +361,22 @@ void pcm_set_frequency(unsigned int samplerate)
     samplerate = pcm_sampr_to_hw_sampr(samplerate, type);
 #endif /* CONFIG_SAMPR_TYPES */
 
-    index = round_value_to_list32(samplerate, hw_freq_sampr,
-                                  HW_NUM_FREQ, false);
+    mutex_lock(&sink_mutex);
+    struct pcm_sink* sink = sinks[cur_sink];
+    index = round_value_to_list32(samplerate, sink->caps.samprs, sink->caps.num_samprs, false);
 
-    if (samplerate != hw_freq_sampr[index])
-        index = HW_FREQ_DEFAULT; /* Invalid = default */
+    if (samplerate != sink->caps.samprs[index])
+        index = sink->caps.default_freq; /* Invalid = default */
 
-    pcm_sampr = hw_freq_sampr[index];
-    pcm_fsel = index;
+    sink->pending_freq = index;
+    mutex_unlock(&sink_mutex);
 }
 
 /* return last-set frequency */
 unsigned int pcm_get_frequency(void)
 {
-    return pcm_sampr;
+    struct pcm_sink* sink = sinks[cur_sink];
+    return sink->caps.samprs[sink->pending_freq];
 }
 
 /* apply pcm settings to the hardware */
@@ -352,12 +386,14 @@ void pcm_apply_settings(void)
 
     pcm_wait_for_init();
 
-    if (pcm_sampr != pcm_curr_sampr)
-    {
-        logf(" pcm_dma_apply_settings");
-        pcm_dma_apply_settings();
-        pcm_curr_sampr = pcm_sampr;
+    mutex_lock(&sink_mutex);
+    struct pcm_sink* sink = sinks[cur_sink];
+    if(sink->pending_freq != sink->configured_freq) {
+        logf(" sink->set_freq");
+        sink->ops.set_freq(sink->pending_freq);
+        sink->configured_freq = sink->pending_freq;
     }
+    mutex_unlock(&sink_mutex);
 }
 
 #ifdef HAVE_RECORDING
diff --git a/firmware/pcm_sw_volume.c b/firmware/pcm_sw_volume.c
index 61a66d650a..ae07bbd3ec 100644
--- a/firmware/pcm_sw_volume.c
+++ b/firmware/pcm_sw_volume.c
@@ -325,7 +325,7 @@ static void start_pcm(bool reframe)
     pcm_play_dma_status_callback(PCM_DMAST_STARTED);
     pcm_play_dma_status_callback(PCM_DMAST_STARTED);
 
-    pcm_play_dma_start(pcm_dbl_buf[1], pcm_dbl_buf_size[1]);
+    pcm_get_current_sink()->ops.play(pcm_dbl_buf[1], pcm_dbl_buf_size[1]);
 }
 
 void pcm_play_dma_start_int(const void *addr, size_t size)
@@ -338,7 +338,7 @@ void pcm_play_dma_start_int(const void *addr, size_t size)
 
 void pcm_play_dma_stop_int(void)
 {
-    pcm_play_dma_stop();
+    pcm_get_current_sink()->ops.stop();
     src_buf_addr = NULL;
     src_buf_rem = 0;
 }
diff --git a/firmware/target/arm/as3525/pcm-as3525.c b/firmware/target/arm/as3525/pcm-as3525.c
index 8800602b47..a5a9ee0cee 100644
--- a/firmware/target/arm/as3525/pcm-as3525.c
+++ b/firmware/target/arm/as3525/pcm-as3525.c
@@ -31,6 +31,7 @@
 #include "mmu-arm.h"
 #include "cpucache-arm.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 #define MAX_TRANSFER (4*((1<<11)-1)) /* maximum data we can transfer via DMA
                                       * i.e. 32 bits at once (size of I2SO_DATA)
@@ -53,13 +54,13 @@ static bool volatile is_recording = false;
 #endif
 
 /* Mask the DMA interrupt */
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     ++locked;
 }
 
 /* Unmask the DMA interrupt if enabled */
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if(--locked == 0 && is_playing)
     {
@@ -119,7 +120,7 @@ static void dma_callback(void)
     }
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     is_playing = true;
 
@@ -136,7 +137,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
     play_start_pcm();
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     is_playing = false;
 
@@ -153,20 +154,6 @@ void pcm_play_dma_stop(void)
     play_callback_pending = false;
 }
 
-void pcm_play_dma_init(void)
-{
-    bitset32(&CGU_PERI, CGU_I2SOUT_APB_CLOCK_ENABLE);
-    I2SOUT_CONTROL = (1<<6) | (1<<3);  /* enable dma, stereo */
-
-    audiohw_preinit();
-    pcm_dma_apply_settings();
-}
-
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
-}
-
 /* divider is 9 bits but the highest one (for 8kHz) fit in 8 bits */
 static const unsigned char divider[SAMPR_NUM_FREQ] = {
     [HW_FREQ_96] = ((AS3525_MCLK_FREQ/128 + SAMPR_96/2) / SAMPR_96) - 1,
@@ -183,12 +170,7 @@ static const unsigned char divider[SAMPR_NUM_FREQ] = {
     [HW_FREQ_8 ] = ((AS3525_MCLK_FREQ/128 + SAMPR_8 /2) / SAMPR_8 ) - 1,
 };
 
-static inline unsigned char mclk_divider(void)
-{
-    return divider[pcm_fsel];
-}
-
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
     bitmod32(&CGU_AUDIO,
              (0<<24) |               /* I2SI_MCLK2PAD_EN = disabled */
@@ -196,11 +178,20 @@ void pcm_dma_apply_settings(void)
              (0<<14) |               /* I2SI_MCLK_DIV_SEL = unused */
              (0<<12) |               /* I2SI_MCLK_SEL = clk_main */
              (1<<11) |               /* I2SO_MCLK_EN */
-             (mclk_divider() << 2) | /* I2SO_MCLK_DIV_SEL */
+             (divider[freq] << 2) |  /* I2SO_MCLK_DIV_SEL */
              (AS3525_MCLK_SEL << 0), /* I2SO_MCLK_SEL */
              0x01ffffff);
 }
 
+static void sink_dma_init(void)
+{
+    bitset32(&CGU_PERI, CGU_I2SOUT_APB_CLOCK_ENABLE);
+    I2SOUT_CONTROL = (1<<6) | (1<<3);  /* enable dma, stereo */
+
+    audiohw_preinit();
+    sink_set_freq(HW_SAMPR_DEFAULT);
+}
+
 #ifdef HAVE_PCM_DMA_ADDRESS
 void * pcm_dma_addr(void *addr)
 {
@@ -210,6 +201,22 @@ void * pcm_dma_addr(void *addr)
 }
 #endif
 
+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,
+    },
+};
 
 /****************************************************************************
  ** Recording DMA transfer
diff --git a/firmware/target/arm/imx233/pcm-imx233.c b/firmware/target/arm/imx233/pcm-imx233.c
index 1588d2c874..05103f6a98 100644
--- a/firmware/target/arm/imx233/pcm-imx233.c
+++ b/firmware/target/arm/imx233/pcm-imx233.c
@@ -25,6 +25,8 @@
 #include "dma-imx233.h"
 #include "pcm-internal.h"
 #include "audioout-imx233.h"
+#include "pcm_sampr.h"
+#include "pcm_sink.h"
 
 struct pcm_dma_command_t
 {
@@ -47,7 +49,7 @@ static const void *dac_buf; /* current buffer */
 static size_t dac_size; /* remaining size */
 
 /* for both recording and playback: maximum transfer size, see
- * pcm_dma_apply_settings */
+ * sink_set_sampr */
 static size_t dma_max_size = CACHEALIGN_UP(1600);
 
 enum
@@ -110,33 +112,33 @@ void INT_DAC_ERROR(void)
     imx233_dma_clear_channel_interrupt(APB_AUDIO_DAC);
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if(dac_locked++ == 0)
         imx233_dma_enable_channel_interrupt(APB_AUDIO_DAC, false);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if(--dac_locked == 0)
         imx233_dma_enable_channel_interrupt(APB_AUDIO_DAC, true);
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     /* do not interrupt the current transaction because resetting the dma
      * would halt the DAC and clearing RUN causes sound havoc so simply
      * wait for the end of transfer */
-    pcm_play_lock();
+    sink_lock();
     dac_buf = NULL;
     dac_size = 0;
     dac_state = DAC_STOP_PENDING;
-    pcm_play_unlock();
+    sink_unlock();
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
-    pcm_play_lock();
+    sink_lock();
     /* update pending buffer */
     dac_buf = addr;
     dac_size = size;
@@ -145,15 +147,10 @@ void pcm_play_dma_start(const void *addr, size_t size)
         play();
     else
         dac_state = DAC_PLAYING;
-    pcm_play_unlock();
+    sink_unlock();
 }
 
-void pcm_play_dma_init(void)
-{
-    audiohw_preinit();
-}
-
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
     audiohw_postinit();
     imx233_icoll_enable_interrupt(INT_SRC_DAC_DMA, true);
@@ -162,18 +159,35 @@ void pcm_play_dma_postinit(void)
     imx233_dma_enable_channel_interrupt(APB_AUDIO_DAC, true);
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    pcm_play_lock();
+    sink_lock();
     /* update frequency */
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
     /* compute maximum transfer size: aim at ~1/100s stop time maximum, make sure
      * the resulting value is a multiple of cache line. At sample rate F we
      * transfer two samples (2 x 2 bytes) F times per second = 4F b/s */
-    dma_max_size = CACHEALIGN_UP(4 * pcm_sampr / 100);
-    pcm_play_unlock();
+    dma_max_size = CACHEALIGN_UP(4 * hw_freq_sampr[freq] / 100);
+    sink_unlock();
 }
 
+struct pcm_sink builtin_pcm_sink = {
+    .caps = {
+        .samprs       = hw_freq_sampr,
+        .num_samprs   = HW_NUM_FREQ,
+        .default_freq = HW_FREQ_DEFAULT,
+    },
+    .ops = {
+        .init     = audiohw_preinit,
+        .postinit = sink_dma_postinit,
+        .set_freq = sink_set_freq,
+        .lock     = sink_lock,
+        .unlock   = sink_unlock,
+        .play     = sink_dma_start,
+        .stop     = sink_dma_stop,
+    },
+};
+
 /*
  * Recording
  */
diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
index 955301b4da..e9eae4d67c 100644
--- a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
+++ b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
@@ -26,6 +26,7 @@
 #include "sdma-imx31.h"
 #include "mmu-imx31.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 #define DMA_PLAY_CH_NUM 2
 #define DMA_REC_CH_NUM 1
@@ -112,12 +113,12 @@ static void play_dma_callback(void)
     }
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     ++dma_play_data.locked;
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if (--dma_play_data.locked == 0 && dma_play_data.state != 0)
     {
@@ -131,12 +132,12 @@ void pcm_play_unlock(void)
     }
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* Init DMA channel information */
     sdma_channel_init(DMA_PLAY_CH_NUM, &dma_play_cd, &dma_play_bd);
@@ -146,11 +147,6 @@ void pcm_play_dma_init(void)
     audiohw_init();
 }
 
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
-}
-
 static void play_start_pcm(void)
 {
     /* Stop transmission (if in progress) */
@@ -200,7 +196,7 @@ static void play_stop_pcm(void)
     dma_play_data.callback_pending = 0;
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     sdma_channel_stop(DMA_PLAY_CH_NUM);
 
@@ -218,7 +214,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
     play_start_dma(addr, size);
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     sdma_channel_stop(DMA_PLAY_CH_NUM);
     play_stop_pcm();
@@ -229,6 +225,23 @@ void * pcm_dma_addr(void *addr)
     return (void *)addr_virt_to_phys((unsigned long)addr);
 }
 
+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,
+    },
+};
+
 #ifdef HAVE_RECORDING
 static struct buffer_descriptor dma_rec_bd NOCACHEBSS_ATTR;
 
diff --git a/firmware/target/arm/pcm-telechips.c b/firmware/target/arm/pcm-telechips.c
index 45044bc664..7f8829cda4 100644
--- a/firmware/target/arm/pcm-telechips.c
+++ b/firmware/target/arm/pcm-telechips.c
@@ -28,6 +28,7 @@
 #include "i2s.h"
 #include "pcm.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 struct dma_data
 {
@@ -62,7 +63,7 @@ struct dma_data dma_play_data SHAREDBSS_ATTR =
     .state = 0
 };
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     DAVC = 0x0;         /* Digital Volume = max */
 #ifdef COWON_D2
@@ -89,12 +90,7 @@ void pcm_play_dma_init(void)
 #endif
 }
 
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
-}
-
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
 }
 
@@ -125,7 +121,7 @@ static void play_stop_pcm(void)
     dma_play_data.state = 0;
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     dma_play_data.p_r  = addr;
     dma_play_data.size = size;
@@ -140,7 +136,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
     play_start_pcm();
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     play_stop_pcm();
     dma_play_data.size = 0;
@@ -149,7 +145,7 @@ void pcm_play_dma_stop(void)
 #endif
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     int status = disable_fiq_save();
 
@@ -161,7 +157,7 @@ void pcm_play_lock(void)
     restore_fiq(status);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
    int status = disable_fiq_save();
 
@@ -173,6 +169,23 @@ void pcm_play_unlock(void)
    restore_fiq(status);
 }
 
+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,
+    },
+};
+
 #ifdef HAVE_RECORDING
 /* TODO: implement */
 void pcm_rec_dma_init(void)
diff --git a/firmware/target/arm/pp/pcm-pp.c b/firmware/target/arm/pp/pcm-pp.c
index 94b1c5ae10..7909464878 100644
--- a/firmware/target/arm/pp/pcm-pp.c
+++ b/firmware/target/arm/pp/pcm-pp.c
@@ -27,6 +27,7 @@
 #include "pcm.h"
 #include "pcm_sampr.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 /** DMA **/
 
@@ -89,10 +90,12 @@ static struct dma_data dma_play_data IBSS_ATTR =
     .state = 0
 };
 
-void pcm_dma_apply_settings(void)
-{
-    audiohw_set_frequency(pcm_fsel);
-}
+/* DMA status cannot be viewed from outside code in control because that can
+ * clear the interrupt from outside the handler and prevent the handler from
+ * from being called. Split up transfers to a reasonable size that is good as
+ * a timer and peaking yet still keeps the FIQ count low.
+ */
+static uint16_t max_dma_chunk_size;
 
 #if defined(CPU_PP502x)
 /* 16-bit, L-R packed into 32 bits with left in the least significant halfword */
@@ -102,12 +105,6 @@ void pcm_dma_apply_settings(void)
 #define DMA_PLAY_CONFIG ((DMA_REQ_IIS << DMA_CMD_REQ_ID_POS) | \
                           DMA_CMD_RAM_TO_PER | DMA_CMD_SINGLE | \
                           DMA_CMD_WAIT_REQ | DMA_CMD_INTR)
-/* DMA status cannot be viewed from outside code in control because that can
- * clear the interrupt from outside the handler and prevent the handler from
- * from being called. Split up transfers to a reasonable size that is good as
- * a timer and peaking yet still keeps the FIQ count low.
- */
-#define MAX_DMA_CHUNK_SIZE (pcm_curr_sampr >> 6) /* ~1/256 seconds */
 
 static inline void dma_tx_init(void)
 {
@@ -145,9 +142,9 @@ static inline unsigned long dma_tx_buf_prepare(const void *addr)
 
 static inline void dma_tx_start(bool begin)
 {
-    size_t size = MAX_DMA_CHUNK_SIZE;
+    size_t size = max_dma_chunk_size;
 
-    /* Not at least MAX_DMA_CHUNK_SIZE left or there would be less
+    /* Not at least max_dma_chunk_size left or there would be less
      * than a FIFO's worth of data after this transfer? */
     if (size + 16*4 > dma_play_data.size)
         size = dma_play_data.size;
@@ -430,10 +427,15 @@ void fiq_playback(void)
 #endif /* ASM / C selection */
 #endif /* CPU_PP502x */
 
+static void sink_set_freq(uint16_t freq) {
+    max_dma_chunk_size = hw_freq_sampr[freq] >> 6;
+    audiohw_set_frequency(freq);
+}
+
 /* For the locks, FIQ must be disabled because the handler manipulates
    IISCONFIG and the operation is not atomic - dual core support
    will require other measures */
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     int status = disable_fiq_save();
 
@@ -444,7 +446,7 @@ void pcm_play_lock(void)
     restore_fiq(status);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     int status = disable_fiq_save();
 
@@ -455,14 +457,14 @@ void pcm_play_unlock(void)
     restore_fiq(status);
 }
 
-static void play_start_pcm(void)
+static void sink_start_pcm(void)
 {
     fiq_function = fiq_playback;
     dma_play_data.state = 1;
     dma_tx_start(true);
 }
 
-static void play_stop_pcm(void)
+static void sink_stop_pcm(void)
 {
     dma_tx_stop();
 
@@ -472,9 +474,11 @@ static void play_stop_pcm(void)
     dma_play_data.state = 0;
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_stop(void);
+
+static void sink_dma_start(const void *addr, size_t size)
 {
-    pcm_play_dma_stop();
+    sink_dma_stop();
 
 #if NUM_CORES > 1
     /* This will become more important later - and different ! */
@@ -485,13 +489,13 @@ void pcm_play_dma_start(const void *addr, size_t size)
 
     dma_play_data.addr = dma_tx_buf_prepare(addr);
     dma_play_data.size = size;
-    play_start_pcm();
+    sink_start_pcm();
 }
 
 /* Stops the DMA transfer and interrupt */
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
-    play_stop_pcm();
+    sink_stop_pcm();
     dma_play_data.addr = 0;
     dma_play_data.size = 0;
 #if NUM_CORES > 1
@@ -499,7 +503,7 @@ void pcm_play_dma_stop(void)
 #endif
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* Initialize default register values. */
     audiohw_init();
@@ -509,10 +513,22 @@ void pcm_play_dma_init(void)
     IISCONFIG |= IIS_TXFIFOEN;
 }
 
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
-}
+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,
+    },
+};
 
 /****************************************************************************
  ** Recording DMA transfer
diff --git a/firmware/target/arm/rk27xx/pcm-rk27xx.c b/firmware/target/arm/rk27xx/pcm-rk27xx.c
index 26e3ad87ba..af81f1e7e4 100644
--- a/firmware/target/arm/rk27xx/pcm-rk27xx.c
+++ b/firmware/target/arm/rk27xx/pcm-rk27xx.c
@@ -28,11 +28,12 @@
 #include "audiohw.h"
 #include "sound.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 static int locked = 0;
 
 /* Mask the DMA interrupt */
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (++locked == 1)
     {
@@ -43,7 +44,7 @@ void pcm_play_lock(void)
 }
 
 /* Unmask the DMA interrupt if enabled */
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if(--locked == 0)
     {
@@ -53,7 +54,7 @@ void pcm_play_unlock(void)
     }
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     HDMA_CON0 = 0x00;
     HDMA_ISR = 0x00;
@@ -105,10 +106,10 @@ static void hdma_i2s_transfer(const void *addr, size_t size)
                   (1<<0));   /* hardware trigger DMA mode */
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     /* Stop any DMA in progress */
-    pcm_play_dma_stop();
+    sink_dma_stop();
 
     /* kick in DMA transfer */
     hdma_i2s_transfer(addr, size);
@@ -225,7 +226,7 @@ static void set_codec_freq(unsigned int freq)
 }
 #endif
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* unmask HDMA interrupt in INTC */
     INTC_IMR |= IRQ_ARM_HDMA;
@@ -236,18 +237,13 @@ void pcm_play_dma_init(void)
     i2s_init();
 }
 
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
-}
-
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
 #ifdef CODEC_SLAVE
-    set_codec_freq(pcm_fsel);
+    set_codec_freq(freq);
 #endif
 
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
 }
 
 /* audio DMA ISR called when chunk from callers buffer has been transfered */
@@ -263,6 +259,23 @@ void INT_HDMA(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 = audiohw_postinit,
+        .set_freq = sink_set_freq,
+        .lock     = sink_lock,
+        .unlock   = sink_unlock,
+        .play     = sink_dma_start,
+        .stop     = sink_dma_stop,
+    },
+};
+
 /****************************************************************************
  ** Recording DMA transfer
  **/
diff --git a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c
index e75c3fe7c6..7567627213 100644
--- a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c
+++ b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c
@@ -26,6 +26,7 @@
 #include "sound.h"
 #include "file.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 /* PCM interrupt routine lockout */
 static struct
@@ -48,20 +49,20 @@ static struct
 void fiq_handler(void) __attribute__((interrupt ("FIQ")));
 
 /* Mask the DMA interrupt */
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (++dma_play_lock.locked == 1)
         bitset32(&INTMSK, DMA2_MASK);
 }
 
 /* Unmask the DMA interrupt if enabled */
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if (--dma_play_lock.locked == 0)
         bitclr32(&INTMSK, dma_play_lock.state);
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* There seem to be problems when changing the IIS interface configuration
      * when a clock is not present.
@@ -94,14 +95,9 @@ void pcm_play_dma_init(void)
     bitset32(&INTMOD, DMA2_MASK);
 }
 
-void pcm_play_dma_postinit(void)
+static void sink_set_freq(uint16_t freq)
 {
-    audiohw_postinit();
-}
-
-void pcm_dma_apply_settings(void)
-{
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
 }
 
 /* Connect the DMA and start filling the FIFO */
@@ -158,7 +154,7 @@ static void play_stop_pcm(void)
     IISCON &= ~(1<<0);
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     /* Enable the IIS clock */
     bitset32(&CLKCON, 1<<17);
@@ -187,7 +183,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
 }
 
 /* Promptly stop DMA transfers and stop IIS */
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     play_stop_pcm();
 
@@ -219,3 +215,20 @@ void fiq_handler(void)
 
     pcm_play_dma_status_callback(PCM_DMAST_STARTED);
 }
+
+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,
+    },
+};
diff --git a/firmware/target/arm/s5l8700/pcm-s5l8700.c b/firmware/target/arm/s5l8700/pcm-s5l8700.c
index fa7ff75ada..0cc2691719 100644
--- a/firmware/target/arm/s5l8700/pcm-s5l8700.c
+++ b/firmware/target/arm/s5l8700/pcm-s5l8700.c
@@ -32,11 +32,12 @@
 #include "dma-target.h"
 #include "mmu-arm.h"
 #include "cpucache-arm.h"
+#include "pcm_sink.h"
 
 /*  Driver for the IIS/PCM part of the s5l8700 using DMA
 
     Notes:
-    - pcm_play_dma_stop is untested, not sure if implemented the right way
+    - sink_dma_stop is untested, not sure if implemented the right way
     - recording is not implemented
 */
 
@@ -83,7 +84,7 @@ static const struct div_entry {
 };
 
 /* Mask the DMA interrupt */
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (locked++ == 0) {
         INTMSK &= ~(1 << 10);
@@ -91,7 +92,7 @@ void pcm_play_lock(void)
 }
 
 /* Unmask the DMA interrupt if enabled */
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if (--locked == 0) {
         INTMSK |= (1 << 10);
@@ -141,7 +142,7 @@ void INT_DMA(void)
 
 }
 
-void pcm_play_dma_start(const void* addr, size_t size)
+static void sink_dma_start(const void* addr, size_t size)
 {
     /* DMA channel on */
     nextbuf = addr;
@@ -161,7 +162,7 @@ void pcm_play_dma_start(const void* addr, size_t size)
                (0 << 0);    /* 0 = LRCK on */
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     /* DMA channel off */
     DMACOM0 = 5;
@@ -195,7 +196,7 @@ static void pcm_dma_set_freq(enum hw_freq_indexes idx)
              (div.cdiv - 1);    /* MCLK_DIV_VAL */
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* configure IIS pins */
 #ifdef IPOD_NANO2G
@@ -252,15 +253,10 @@ void pcm_play_dma_init(void)
     audiohw_preinit();
 }
 
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
-}
-
 /* set the configured PCM frequency */
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    pcm_dma_set_freq(pcm_fsel);
+    pcm_dma_set_freq(hw_freq_sampr[freq]);
 }
 
 #ifdef HAVE_PCM_DMA_ADDRESS
@@ -272,6 +268,22 @@ void * pcm_dma_addr(void *addr)
 }
 #endif
 
+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,
+    },
+};
 
 /****************************************************************************
  ** Recording DMA transfer
diff --git a/firmware/target/arm/s5l8702/pcm-s5l8702.c b/firmware/target/arm/s5l8702/pcm-s5l8702.c
index 0f532cc1bf..a10479e95e 100644
--- a/firmware/target/arm/s5l8702/pcm-s5l8702.c
+++ b/firmware/target/arm/s5l8702/pcm-s5l8702.c
@@ -31,6 +31,7 @@
 #include "pcm_sampr.h"
 #include "pcm-target.h"
 #include "dma-s5l8702.h"
+#include "pcm_sink.h"
 
 /* DMA configuration */
 
@@ -86,14 +87,14 @@ static int active_dblbuf;
 size_t pcm_remaining;
 
 /* Mask the DMA interrupt */
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (locked++ == 0)
         dmac_ch_lock_int(&dma_play_ch);
 }
 
 /* Unmask the DMA interrupt if enabled */
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if (--locked == 0)
         dmac_ch_unlock_int(&dma_play_ch);
@@ -141,30 +142,29 @@ static void dma_play_callback(void *cb_data)
     pcm_play_dma_status_callback(PCM_DMAST_STARTED);
 }
 
-void pcm_play_dma_start(const void* addr, size_t size)
+static void sink_dma_stop(void)
 {
-    pcm_play_dma_stop();
+    dmac_ch_stop(&dma_play_ch);
+    I2STXCOM = 0xa;
+}
+
+static void sink_dma_start(const void* addr, size_t size)
+{
+    sink_dma_stop();
 
     pcm_remaining = size;
     I2STXCOM = 0xe;
     dma_play_callback((void*)addr);
 }
 
-void pcm_play_dma_stop(void)
-{
-    dmac_ch_stop(&dma_play_ch);
-    I2STXCOM = 0xa;
-}
-
 /* MCLK = 12MHz (MCLKDIV2=1), [CS42L55 DS, s4.8] */
 #define MCLK_FREQ     12000000
 
 /* set the configured PCM frequency */
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
     static uint16_t last_clkcon3l = 0;
     uint16_t clkcon3l;
-    int fsel;
 
     /* For unknown reasons, s5l8702 I2S controller does not synchronize
      * with CS42L55 at 32000 Hz. To fix it, the CODEC is configured with
@@ -172,12 +172,11 @@ void pcm_dma_apply_settings(void)
      * obtaining 32 KHz in LRCK controller input and 8 MHz in SCLK input.
      * OF uses this trick.
      */
-    if (pcm_fsel == HW_FREQ_32) {
-        fsel = HW_FREQ_48;
+    if (freq == HW_FREQ_32) {
+        freq = HW_FREQ_48;
         clkcon3l = 0x3028;  /* PLL2 / 3 / 9 -> 8 MHz */
     }
     else {
-        fsel = pcm_fsel;
         clkcon3l = 0;  /* OSC0 -> 12 MHz */
     }
 
@@ -192,12 +191,12 @@ void pcm_dma_apply_settings(void)
     }
 
     /* configure I2S clock ratio */
-    I2SCLKDIV = MCLK_FREQ / hw_freq_sampr[fsel];
+    I2SCLKDIV = MCLK_FREQ / hw_freq_sampr[freq];
     /* select CS42L55 sample rate */
-    audiohw_set_frequency(fsel);
+    audiohw_set_frequency(freq);
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     PWRCON(1) &= ~(1 << 7);
 
@@ -207,12 +206,7 @@ void pcm_play_dma_init(void)
     I2SCLKCON = 1;
 
     audiohw_preinit();
-    pcm_dma_apply_settings();
-}
-
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
+    sink_set_freq(HW_FREQ_DEFAULT);
 }
 
 #ifdef HAVE_PCM_DMA_ADDRESS
@@ -222,6 +216,22 @@ void * pcm_dma_addr(void *addr)
 }
 #endif
 
+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,
+    },
+};
 
 /****************************************************************************
  ** Recording DMA transfer
diff --git a/firmware/target/arm/stm32/pcm-stm32h7.c b/firmware/target/arm/stm32/pcm-stm32h7.c
index e9d9696d19..472ab4a3b9 100644
--- a/firmware/target/arm/stm32/pcm-stm32h7.c
+++ b/firmware/target/arm/stm32/pcm-stm32h7.c
@@ -20,37 +20,57 @@
  ****************************************************************************/
 #include "pcm.h"
 #include "pcm-internal.h"
+#include "pcm_sampr.h"
+#include "pcm_sink.h"
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
+    (void)freq;
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
 }
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     (void)addr;
     (void)size;
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
 }
 
-void pcm_play_unlock(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)
 {
diff --git a/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c b/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c
index 7befeb40c0..c46fa00022 100644
--- a/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c
+++ b/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c
@@ -28,13 +28,14 @@
 #include "dsp-target.h"
 #include "dsp/ipc.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 /* This is global to save some latency when pcm_play_dma_get_peak_buffer is 
  *  called.
  */
 static const void *start;
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
     /* Configure clock divider */
     tsc2100_writereg(CONTROL_PAGE2, TSPP1_ADDRESS, 0x1120);
@@ -45,7 +46,7 @@ void pcm_play_dma_postinit(void)
     audiohw_postinit();
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     IO_INTC_IRQ0 = INTR_IRQ0_IMGBUF;
     bitset16(&IO_INTC_EINT0, INTR_EINT0_IMGBUF);
@@ -67,15 +68,15 @@ void pcm_play_dma_init(void)
     dsp_wake();
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
 }
 
 /* Note that size is actually limited to the size of a short right now due to
  *  the implementation on the DSP side (and the way that we access it)
  */
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     unsigned long sdem_addr=(unsigned long)addr - CONFIG_SDRAM_START;
     /* Initialize codec. */
@@ -87,18 +88,18 @@ void pcm_play_dma_start(const void *addr, size_t size)
     dsp_wake();
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     DSP_(_dma0_stopped)=1;
     dsp_wake();
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
 
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
 
 }
@@ -156,4 +157,20 @@ void DSPHINT(void)
     
     DEBUGF("DSP: %s", buffer);
 }
-    
+
+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,
+    },
+};
diff --git a/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c b/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c
index 95c3e456fa..7069d0b0e6 100644
--- a/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c
+++ b/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c
@@ -29,6 +29,7 @@
 #include "dsp/ipc.h"
 #include "pcm-internal.h"
 #include "dma-target.h"
+#include "pcm_sink.h"
 
 /* This is global to save some latency when pcm_play_dma_get_peak_buffer is 
  *  called.
@@ -36,12 +37,7 @@
 static const void *start;
 static int dma_channel;
 
-void pcm_play_dma_postinit(void)
-{
-    audiohw_postinit();
-}
-
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* GIO16 is DSP/AIC3X CLK */
     IO_GIO_FSEL0 &= 0x3FFF;
@@ -74,15 +70,15 @@ void pcm_play_dma_init(void)
     dsp_wake();
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
 }
 
 /* Note that size is actually limited to the size of a short right now due to
  *  the implementation on the DSP side (and the way that we access it)
  */
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     unsigned long sdem_addr=(unsigned long)addr - CONFIG_SDRAM_START;
     /* Initialize codec. */
@@ -94,18 +90,18 @@ void pcm_play_dma_start(const void *addr, size_t size)
     dsp_wake();
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     DSP_(_dma0_stopped)=1;
     dsp_wake();
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
 
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
 
 }
@@ -163,4 +159,20 @@ void DSPHINT(void)
     
     DEBUGF("DSP: %s", buffer);
 }
-    
+
+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,
+    },
+};
diff --git a/firmware/target/coldfire/pcm-coldfire.c b/firmware/target/coldfire/pcm-coldfire.c
index 36eb00c31b..6c4d701d5b 100644
--- a/firmware/target/coldfire/pcm-coldfire.c
+++ b/firmware/target/coldfire/pcm-coldfire.c
@@ -29,6 +29,7 @@
 #include "spdif.h"
 #endif
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 
 #define IIS_PLAY_DEFPARM ( (freq_ent[FPARM_CLOCKSEL] << 12) | \
                            (IIS_PLAY & (7 << 8)) | \
@@ -142,12 +143,12 @@ static void iis_play_reset_if_playback(bool if_playback)
 /* This clears the reset bit to enable monitoring immediately if monitoring
    recording sources or always if playback is in progress - we might be 
    switching samplerates on the fly */
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
     int level = set_irq_level(DMA_IRQ_LEVEL);
 
     /* remember table entry */
-    freq_ent = pcm_freq_parms[pcm_fsel];
+    freq_ent = pcm_freq_parms[freq];
  
     /* Reprogramming bits 15-12 requires FIFO to be in a reset
        condition - Users Manual 17-8, Note 11 */
@@ -159,7 +160,7 @@ void pcm_dma_apply_settings(void)
     IIS_PLAY = IIS_PLAY_DEFPARM | IIS_FIFO_RESET;
     restore_irq(level);
 
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
     coldfire_set_pllcr_audio_bits(PLLCR_SET_AUDIO_BITS_DEFPARM);
 
     level = set_irq_level(DMA_IRQ_LEVEL);
@@ -170,11 +171,11 @@ void pcm_dma_apply_settings(void)
         PDOR3 = 0; /* Kick FIFO out of reset by writing to it */
 
     restore_irq(level);
-} /* pcm_dma_apply_settings */
+} /* sink_set_sampr */
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
-    freq_ent = pcm_freq_parms[pcm_fsel];
+    freq_ent = pcm_freq_parms[HW_FREQ_DEFAULT];
 
     AUDIOGLOB = AUDIOGLOB_DEFPARM;
     DIVR0     = 54; /* DMA0 is mapped into vector 54 in system.c */
@@ -195,15 +196,15 @@ void pcm_play_dma_init(void)
 
     audio_input_mux(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
 
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(HW_FREQ_DEFAULT);
     coldfire_set_pllcr_audio_bits(PLLCR_SET_AUDIO_BITS_DEFPARM);
 
 #if defined(HAVE_SPDIF_REC) || defined(HAVE_SPDIF_OUT)
     spdif_init();
 #endif
-} /* pcm_play_dma_init */
+} /* sink_dma_init */
 
-void pcm_play_dma_postinit(void)
+void sink_dma_postinit(void)
 {
     audiohw_postinit();
     iis_play_reset();
@@ -223,23 +224,35 @@ static struct dma_lock dma_play_lock =
     .state = (1 << 14)  /* bit 14 is DMA0 */
 };
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (++dma_play_lock.locked == 1)
         coldfire_imr_mod(1 << 14, 1 << 14);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if (--dma_play_lock.locked == 0)
         coldfire_imr_mod(dma_play_lock.state, 1 << 14);
 }
 
+/* Stops the DMA transfer and interrupt */
+static void sink_dma_stop(void)
+{
+    and_l(~(DMA_EEXT | DMA_INT), &DCR0); /* per request and int OFF */
+    BCR0 = 0; /* No bytes remaining */
+    DSR0 = 1; /* Clear interrupt, errors, stop transfer */
+
+    iis_play_reset_if_playback(true);
+
+    dma_play_lock.state = (1 << 14);
+} /* sink_dma_stop */
+
 /* Set up the DMA transfer that kicks in when the audio FIFO gets empty */
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     /* Stop any DMA in progress */
-    pcm_play_dma_stop();
+    sink_dma_stop();
 
     /* Set up DMA transfer  */
     SAR0 = (unsigned long)addr;   /* Source address      */
@@ -250,19 +263,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
            DMA_SSIZE(DMA_SIZE_LINE) | DMA_START;
 
     dma_play_lock.state = (0 << 14);
-} /* pcm_play_dma_start */
-
-/* Stops the DMA transfer and interrupt */
-void pcm_play_dma_stop(void)
-{
-    and_l(~(DMA_EEXT | DMA_INT), &DCR0); /* per request and int OFF */
-    BCR0 = 0; /* No bytes remaining */
-    DSR0 = 1; /* Clear interrupt, errors, stop transfer */
-
-    iis_play_reset_if_playback(true);
-
-    dma_play_lock.state = (1 << 14);
-} /* pcm_play_dma_stop */
+} /* sink_dma_start */
 
 /* DMA0 Interrupt is called when the DMA has finished transfering a chunk
    from the caller's buffer */
@@ -301,6 +302,23 @@ void DMA0(void)
     /* else inished playing */
 } /* DMA0 */
 
+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
 /****************************************************************************
  ** Recording DMA transfer
diff --git a/firmware/target/hosted/android/pcm-android.c b/firmware/target/hosted/android/pcm-android.c
index c23b802b2d..189099eecc 100644
--- a/firmware/target/hosted/android/pcm-android.c
+++ b/firmware/target/hosted/android/pcm-android.c
@@ -28,6 +28,8 @@
 #include "debug.h"
 #include "pcm.h"
 #include "pcm-internal.h"
+#include "pcm_sampr.h"
+#include "pcm_sink.h"
 
 extern JNIEnv *env_ptr;
 
@@ -133,23 +135,24 @@ Java_org_rockbox_RockboxPCM_nativeWrite(JNIEnv *env, jobject this,
     return max_size - left;
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (++audio_locked == 1)
         lock_audio();
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if (--audio_locked == 0)
         unlock_audio();
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
+    (void)freq;
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     pcm_data_start = addr;
     pcm_data_size = size;
@@ -160,7 +163,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
                                0);
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     /* NOTE: due to how pcm_play_dma_complete_callback() works, this is
      * possibly called from nativeWrite(), i.e. another (host) thread
@@ -171,7 +174,7 @@ void pcm_play_dma_stop(void)
                            stop_method);
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* in order to have background music playing after leaving the activity,
      * we need to allocate the PCM object from the Rockbox thread (the Activity
@@ -194,7 +197,7 @@ void pcm_play_dma_init(void)
     write_method      = e->GetMethodID(env_ptr, RockboxPCM_class, "write", "([BII)I");
 }
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
 }
 
@@ -213,6 +216,23 @@ void pcm_shutdown(void)
     pthread_mutex_destroy(&audio_lock_mutex);
 }
 
+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,
+    },
+};
+
 JNIEXPORT void JNICALL
 Java_org_rockbox_RockboxPCM_postVolumeChangedEvent(JNIEnv *env,
                                                    jobject this,
diff --git a/firmware/target/hosted/ctru/pcm-ctru.c b/firmware/target/hosted/ctru/pcm-ctru.c
index d7c9d36458..da941ecc31 100644
--- a/firmware/target/hosted/ctru/pcm-ctru.c
+++ b/firmware/target/hosted/ctru/pcm-ctru.c
@@ -45,6 +45,7 @@
 #include "pcm-internal.h"
 #include "pcm_sampr.h"
 #include "pcm_mixer.h"
+#include "pcm_sink.h"
 
 #include <3ds/ndsp/ndsp.h>
 #include <3ds/ndsp/channel.h>
@@ -102,14 +103,14 @@ static inline bool is_in_audio_thread(int audio_thread_id)
     return false;
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (!is_in_audio_thread(_pcm_thread_id)) {
         RecursiveLock_Lock(&_pcm_lock_mtx);
     }
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
      if (!is_in_audio_thread(_pcm_thread_id)) {
         RecursiveLock_Unlock(&_pcm_lock_mtx);
@@ -188,13 +189,13 @@ void dsp_callback(void *const nul_) {
     LightEvent_Signal(&_dsp_callback_event);
 }
 
-static void pcm_dma_apply_settings_nolock(void)
+static void sink_set_freq_nolock(uint16_t freq)
 {
     ndspChnReset(0);
     
     ndspSetOutputMode(NDSP_OUTPUT_STEREO);
 
-    ndspChnSetRate(0, pcm_sampr);
+    ndspChnSetRate(0, hw_freq_sampr[freq]);
     ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
     /* ndspChnSetInterp(0, NDSP_INTERP_POLYPHASE); */
     /* ndspChnSetInterp(0, NDSP_INTERP_NONE); */
@@ -235,7 +236,7 @@ static void pcm_dma_apply_settings_nolock(void)
                               -1,              /* run on any core */ false);
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     _pcm_buffer = addr;
     _pcm_buffer_size = size;
@@ -245,7 +246,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
     RecursiveLock_Unlock(&_pcm_lock_mtx);
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     RecursiveLock_Lock(&_pcm_lock_mtx);
     ndspChnSetPaused(0, true);
@@ -303,7 +304,7 @@ unsigned long spdif_measure_frequency(void)
 
 #endif /* HAVE_RECORDING */
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     Result ndsp_init_res = ndspInit();
     if (R_FAILED(ndsp_init_res)) {
@@ -319,15 +320,15 @@ void pcm_play_dma_init(void)
     LightEvent_Init(&_dsp_callback_event, RESET_ONESHOT);
 }
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    pcm_play_lock();
-    pcm_dma_apply_settings_nolock();
-    pcm_play_unlock();
+    sink_lock();
+    sink_set_freq_nolock(freq);
+    sink_unlock();
 }
 
 void pcm_close_device(void)
@@ -357,3 +358,20 @@ void audiohw_close(void)
 {
     pcm_close_device();
 }
+
+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,
+    },
+};
diff --git a/firmware/target/hosted/ibasso/pcm-ibasso.c b/firmware/target/hosted/ibasso/pcm-ibasso.c
index 7857e60b6d..ea5651ac7e 100644
--- a/firmware/target/hosted/ibasso/pcm-ibasso.c
+++ b/firmware/target/hosted/ibasso/pcm-ibasso.c
@@ -31,6 +31,8 @@
 #include "panic.h"
 #include "pcm.h"
 #include "pcm-internal.h"
+#include "pcm_sampr.h"
+#include "pcm_sink.h"
 
 #include "sound/asound.h"
 #include "tinyalsa/asoundlib.h"
@@ -187,26 +189,26 @@ static const unsigned int DEVICE = 0;
 static struct pcm_config _config;
 
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     TRACE;
 
 #ifdef DEBUG
 
     /*
-        DEBUG pcm_play_dma_init: Access: 0x000009
-        DEBUG pcm_play_dma_init: Format[0]: 0x000044
-        DEBUG pcm_play_dma_init: Format[1]: 0x000010
-        DEBUG pcm_play_dma_init: Format: S16_LE
-        DEBUG pcm_play_dma_init: Format: S24_LE
-        DEBUG pcm_play_dma_init: Format: S20_3LE
-        DEBUG pcm_play_dma_init: Subformat: 0x000001
-        DEBUG pcm_play_dma_init: Rate: min = 8000Hz, max = 192000Hz
-        DEBUG pcm_play_dma_init: Channels: min = 2, max = 2
-        DEBUG pcm_play_dma_init: Sample bits: min=16, max=32
-        DEBUG pcm_play_dma_init: Period size: min=8, max=10922
-        DEBUG pcm_play_dma_init: Period count: min=3, max=128
-        DEBUG pcm_play_dma_init: 0 mixer controls.
+        DEBUG sink_dma_init: Access: 0x000009
+        DEBUG sink_dma_init: Format[0]: 0x000044
+        DEBUG sink_dma_init: Format[1]: 0x000010
+        DEBUG sink_dma_init: Format: S16_LE
+        DEBUG sink_dma_init: Format: S24_LE
+        DEBUG sink_dma_init: Format: S20_3LE
+        DEBUG sink_dma_init: Subformat: 0x000001
+        DEBUG sink_dma_init: Rate: min = 8000Hz, max = 192000Hz
+        DEBUG sink_dma_init: Channels: min = 2, max = 2
+        DEBUG sink_dma_init: Sample bits: min=16, max=32
+        DEBUG sink_dma_init: Period size: min=8, max=10922
+        DEBUG sink_dma_init: Period count: min=3, max=128
+        DEBUG sink_dma_init: 0 mixer controls.
     */
 
     struct pcm_params* params = pcm_params_get(CARD, DEVICE, PCM_OUT);
@@ -310,7 +312,7 @@ void pcm_play_dma_init(void)
         pcm_thread_run relies on this size match. See pcm_mixer.h.
     */
     _config.channels          = 2;
-    _config.rate              = pcm_sampr;
+    _config.rate              = hw_freq_sampr[HW_FREQ_DEFAULT];
     _config.period_size       = 256;
     _config.period_count      = 4;
     _config.format            = PCM_FORMAT_S16_LE;
@@ -337,7 +339,7 @@ void pcm_play_dma_init(void)
 }
 
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     TRACE;
 
@@ -364,7 +366,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
     pthread_mutex_unlock(&_dma_suspended_mtx);
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     TRACE;
 
@@ -375,11 +377,11 @@ void pcm_play_dma_stop(void)
 }
 
 
-/* Unessecary play locks before pcm_play_dma_postinit. */
+/* Unessecary play locks before sink_dma_postinit. */
 static int _play_lock_recursion_count = -10000;
 
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
     TRACE;
 
@@ -387,7 +389,7 @@ void pcm_play_dma_postinit(void)
 }
 
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     TRACE;
 
@@ -402,7 +404,7 @@ void pcm_play_lock(void)
 }
 
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     TRACE;
 
@@ -418,9 +420,9 @@ void pcm_play_unlock(void)
 }
 
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    unsigned int rate = pcm_get_frequency();
+    unsigned int rate = hw_freq_sampr[freq];
 
     DEBUGF("DEBUG %s: Current sample rate: %u, next sampe rate: %u.", __func__, _config.rate, rate);
 
@@ -450,3 +452,20 @@ void pcm_close_device(void)
     pcm_close(_alsa_handle);
     _alsa_handle = NULL;
 }
+
+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,
+    },
+};
diff --git a/firmware/target/hosted/pcm-alsa.c b/firmware/target/hosted/pcm-alsa.c
index 9286369a25..7e4833fdfe 100644
--- a/firmware/target/hosted/pcm-alsa.c
+++ b/firmware/target/hosted/pcm-alsa.c
@@ -48,6 +48,7 @@
 #include "pcm-internal.h"
 #include "pcm_mixer.h"
 #include "pcm_sampr.h"
+#include "pcm_sink.h"
 #include "audiohw.h"
 #include "pcm-alsa.h"
 #include "fixedpoint.h"
@@ -122,7 +123,7 @@ void pcm_alsa_set_capture_device(const char *device)
 }
 #endif
 
-static int set_hwparams(snd_pcm_t *handle)
+static int set_hwparams(snd_pcm_t *handle, unsigned long sampr)
 {
     int err;
     unsigned int srate;
@@ -136,10 +137,10 @@ static int set_hwparams(snd_pcm_t *handle)
        Note these are in FRAMES, and are sized to be about 8.5ms
        for the buffer and 2.1ms for the period
      */
-    if (pcm_sampr > SAMPR_96) {
+    if (sampr > SAMPR_96) {
         buffer_size = MIX_FRAME_SAMPLES * 4 * 4;
         period_size = MIX_FRAME_SAMPLES * 4;
-    } else if (pcm_sampr > SAMPR_48) {
+    } else if (sampr > SAMPR_48) {
         buffer_size = MIX_FRAME_SAMPLES * 2 * 4;
         period_size = MIX_FRAME_SAMPLES * 2;
     } else {
@@ -176,17 +177,17 @@ static int set_hwparams(snd_pcm_t *handle)
         goto error;
     }
     /* set the stream rate */
-    srate = pcm_sampr;
+    srate = sampr;
     err = snd_pcm_hw_params_set_rate_near(handle, params, &srate, 0);
     if (err < 0)
     {
-        logf("Rate %luHz not available for playback: %s", pcm_sampr, snd_strerror(err));
+        logf("Rate %luHz not available for playback: %s", sampr, snd_strerror(err));
         goto error;
     }
     real_sample_rate = srate;
-    if (real_sample_rate != pcm_sampr)
+    if (real_sample_rate != sampr)
     {
-        logf("Rate doesn't match (requested %luHz, get %dHz)", pcm_sampr, real_sample_rate);
+        logf("Rate doesn't match (requested %luHz, get %dHz)", sampr, real_sample_rate);
         err = -EINVAL;
         goto error;
     }
@@ -595,7 +596,7 @@ static void open_hwdev(const char *device, snd_pcm_stream_t mode)
     atexit(alsadev_cleanup);
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     logf("PCM DMA Init");
 
@@ -606,48 +607,50 @@ void pcm_play_dma_init(void)
     return;
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     pthread_mutex_lock(&pcm_mtx);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     pthread_mutex_unlock(&pcm_mtx);
 }
 
-static void pcm_dma_apply_settings_nolock(void)
+static void sink_set_freq_nolock(uint16_t freq)
 {
-    logf("PCM DMA Settings %d %lu", last_sample_rate, pcm_sampr);
+    unsigned int sampr = hw_freq_sampr[freq];
 
-    if (last_sample_rate != pcm_sampr)
+    logf("PCM DMA Settings %d %lu", last_sample_rate, sampr);
+
+    if (last_sample_rate != sampr)
     {
-        last_sample_rate = pcm_sampr;
+        last_sample_rate = sampr;
 
 #ifdef AUDIOHW_MUTE_ON_SRATE_CHANGE
         audiohw_mute(true);
 #endif
         snd_pcm_drop(handle);
 
-        set_hwparams(handle); // FIXME: check return code?
+        set_hwparams(handle, sampr); // FIXME: check return code?
         set_swparams(handle); // FIXME: check return code?
 
 #if defined(HAVE_NWZ_LINUX_CODEC)
         /* Sony NWZ linux driver uses a nonstandard mecanism to set the sampling rate */
-        audiohw_set_frequency(pcm_sampr);
+        audiohw_set_frequency(sampr);
 #endif
         /* (Will be unmuted by pcm resuming) */
     }
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    pcm_play_lock();
-    pcm_dma_apply_settings_nolock();
-    pcm_play_unlock();
+    sink_lock();
+    sink_set_freq_nolock(freq);
+    sink_unlock();
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     logf("PCM DMA stop (%d)", snd_pcm_state(handle));
 
@@ -660,10 +663,9 @@ void pcm_play_dma_stop(void)
 #endif
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     logf("PCM DMA start (%p %d)", addr, size);
-    pcm_dma_apply_settings_nolock();
 
     pcm_data = addr;
     pcm_size = size;
@@ -749,7 +751,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
     }
 }
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
     audiohw_postinit();
 
@@ -768,15 +770,32 @@ unsigned int pcm_alsa_get_xruns(void)
     return xruns;
 }
 
+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_lock(void)
 {
-    pcm_play_lock();
+    sink_lock();
 }
 
 void pcm_rec_unlock(void)
 {
-    pcm_play_unlock();
+    sink_unlock();
 }
 
 void pcm_rec_dma_init(void)
@@ -796,7 +815,6 @@ void pcm_rec_dma_close(void)
 void pcm_rec_dma_start(void *start, size_t size)
 {
     logf("PCM REC DMA start (%p %d)", start, size);
-    pcm_dma_apply_settings_nolock();
     pcm_data_rec = start;
     pcm_size = size;
 
diff --git a/firmware/target/hosted/sdl/pcm-sdl.c b/firmware/target/hosted/sdl/pcm-sdl.c
index 2b96c6a252..26c0bef563 100644
--- a/firmware/target/hosted/sdl/pcm-sdl.c
+++ b/firmware/target/hosted/sdl/pcm-sdl.c
@@ -44,6 +44,7 @@
 #include "pcm-internal.h"
 #include "pcm_sampr.h"
 #include "pcm_mixer.h"
+#include "pcm_sink.h"
 
 /*#define LOGF_ENABLE*/
 #include "logf.h"
@@ -57,6 +58,8 @@ extern const char      *audiodev;
 
 static int cvt_status = -1;
 
+static unsigned long pcm_sampr;
+
 static const void *pcm_data;
 static size_t pcm_data_size;
 static size_t pcm_sample_bytes;
@@ -80,13 +83,13 @@ static SDL_AudioCVT cvt;
 static int audio_locked = 0;
 static SDL_mutex *audio_lock;
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     if (++audio_locked == 1)
         SDL_LockMutex(audio_lock);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     if (--audio_locked == 0)
         SDL_UnlockMutex(audio_lock);
@@ -97,8 +100,11 @@ void pcm_play_unlock(void)
 #endif
 
 static void sdl_audio_callback(void *handle, Uint8 *stream, int len);
-static void pcm_dma_apply_settings_nolock(void)
+
+static void sink_set_freq_nolock(uint16_t freq)
 {
+    pcm_sampr = hw_freq_sampr[freq];
+
     SDL_AudioSpec wanted_spec;
     wanted_spec.freq = pcm_sampr;
     wanted_spec.format = AUDIO_S16SYS;
@@ -161,14 +167,14 @@ static void pcm_dma_apply_settings_nolock(void)
     }
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    pcm_play_lock();
-    pcm_dma_apply_settings_nolock();
-    pcm_play_unlock();
+    sink_lock();
+    sink_set_freq_nolock(freq);
+    sink_unlock();
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_start(const void *addr, size_t size)
 {
     pcm_data = addr;
     pcm_data_size = size;
@@ -180,7 +186,7 @@ void pcm_play_dma_start(const void *addr, size_t size)
 #endif
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
 #if SDL_MAJOR_VERSION > 1
     SDL_PauseAudioDevice(pcm_devid, 1);
@@ -397,7 +403,7 @@ unsigned long spdif_measure_frequency(void)
 
 #endif /* HAVE_RECORDING */
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     if (SDL_InitSubSystem(SDL_INIT_AUDIO))
     {
@@ -435,6 +441,23 @@ void pcm_play_dma_init(void)
 #endif
 }
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(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,
+    },
+};
diff --git a/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c b/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c
index 178da0b153..b0cbefc66f 100644
--- a/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c
@@ -26,14 +26,14 @@
 #include "sound.h"
 #include "pcm.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 #include "jz4740.h"
 
-
 /****************************************************************************
  ** Playback DMA transfer
  **/
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
     audiohw_postinit();
 
@@ -47,7 +47,7 @@ void pcm_play_dma_postinit(void)
     __aic_flush_fifo();
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* TODO */
 
@@ -57,10 +57,10 @@ void pcm_play_dma_init(void)
     audiohw_init();
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
     /* TODO */
-    audiohw_set_frequency(pcm_sampr);
+    audiohw_set_frequency(freq);
 }
 
 static const void* playback_address;
@@ -135,36 +135,36 @@ void DMA_CALLBACK(DMA_AIC_TX_CHANNEL)(void)
     }
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_stop(void)
 {
-    pcm_play_dma_stop();
+    int flags = disable_irq_save();
 
-    dma_enable();
+    REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) = (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) | DMAC_DCCSR_HLT) & ~DMAC_DCCSR_EN;
 
-    set_dma(addr, size);
+    dma_disable();
 
-    __aic_enable_transmit_dma();
-    __aic_enable_replay();
+    __aic_disable_transmit_dma();
+    __aic_disable_replay();
 
-    REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) |= DMAC_DCCSR_EN;
+    restore_irq(flags);
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_start(const void *addr, size_t size)
 {
-    int flags = disable_irq_save();
+    sink_dma_stop();
 
-    REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) = (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) | DMAC_DCCSR_HLT) & ~DMAC_DCCSR_EN;
+    dma_enable();
 
-    dma_disable();
+    set_dma(addr, size);
 
-    __aic_disable_transmit_dma();
-    __aic_disable_replay();
+    __aic_enable_transmit_dma();
+    __aic_enable_replay();
 
-    restore_irq(flags);
+    REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) |= DMAC_DCCSR_EN;
 }
 
 static unsigned int play_lock = 0;
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     int flags = disable_irq_save();
 
@@ -174,7 +174,7 @@ void pcm_play_lock(void)
     restore_irq(flags);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     int flags = disable_irq_save();
 
@@ -189,6 +189,23 @@ void audiohw_close(void)
     /* TODO: prevent pop */
 }
 
+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
 /* TODO */
 void pcm_rec_dma_init(void)
diff --git a/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c b/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c
index 72bcbeccd0..fa4102a846 100644
--- a/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c
+++ b/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c
@@ -26,13 +26,14 @@
 #include "sound.h"
 #include "pcm.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 #include "cpu.h"
 
 /****************************************************************************
  ** Playback DMA transfer
  **/
 
-void pcm_play_dma_postinit(void)
+static void sink_dma_postinit(void)
 {
     audiohw_postinit();
 
@@ -40,7 +41,7 @@ void pcm_play_dma_postinit(void)
     __aic_flush_tfifo();
 }
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     system_enable_irq(DMA_IRQ(DMA_AIC_TX_CHANNEL));
 
@@ -48,9 +49,9 @@ void pcm_play_dma_init(void)
     audiohw_init();
 }
 
-void pcm_dma_apply_settings(void)
+static void sink_set_freq(uint16_t freq)
 {
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
 }
 
 static const void* playback_address;
@@ -126,34 +127,34 @@ void DMA_CALLBACK(DMA_AIC_TX_CHANNEL)(void)
     }
 }
 
-void pcm_play_dma_start(const void *addr, size_t size)
+static void sink_dma_stop(void)
 {
-    pcm_play_dma_stop();
+    int flags = disable_irq_save();
 
-    __dmac_channel_enable_clk(DMA_AIC_TX_CHANNEL);
+    REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) = (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) | DMAC_DCCSR_HLT) & ~DMAC_DCCSR_EN;
 
-    set_dma(addr, size);
+    __dmac_channel_disable_clk(DMA_AIC_TX_CHANNEL);
 
-    __aic_enable_replay();
+    __aic_disable_replay();
 
-    __dmac_channel_enable_irq(DMA_AIC_TX_CHANNEL);
+    restore_irq(flags);
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_start(const void *addr, size_t size)
 {
-    int flags = disable_irq_save();
+    sink_dma_stop();
 
-    REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) = (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) | DMAC_DCCSR_HLT) & ~DMAC_DCCSR_EN;
+    __dmac_channel_enable_clk(DMA_AIC_TX_CHANNEL);
 
-    __dmac_channel_disable_clk(DMA_AIC_TX_CHANNEL);
+    set_dma(addr, size);
 
-    __aic_disable_replay();
+    __aic_enable_replay();
 
-    restore_irq(flags);
+    __dmac_channel_enable_irq(DMA_AIC_TX_CHANNEL);
 }
 
 static unsigned int play_lock = 0;
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     int flags = disable_irq_save();
 
@@ -163,7 +164,7 @@ void pcm_play_lock(void)
     restore_irq(flags);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     int flags = disable_irq_save();
 
@@ -172,3 +173,20 @@ void pcm_play_unlock(void)
 
     restore_irq(flags);
 }
+
+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,
+    },
+};
diff --git a/firmware/target/mips/ingenic_x1000/pcm-x1000.c b/firmware/target/mips/ingenic_x1000/pcm-x1000.c
index 4b9cb85b57..9bd6f4ae0f 100644
--- a/firmware/target/mips/ingenic_x1000/pcm-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/pcm-x1000.c
@@ -25,6 +25,7 @@
 #include "audiohw.h"
 #include "pcm.h"
 #include "pcm-internal.h"
+#include "pcm_sink.h"
 #include "panic.h"
 #include "dma-x1000.h"
 #include "irq-x1000.h"
@@ -53,7 +54,7 @@ static dma_desc rec_dma_desc;
 static void pcm_rec_dma_int_cb(int event);
 #endif
 
-void pcm_play_dma_init(void)
+static void sink_dma_init(void)
 {
     /* Ungate clock */
     jz_writef(CPM_CLKGR, AIC(0));
@@ -99,14 +100,9 @@ void pcm_play_dma_init(void)
     system_enable_irq(IRQ_AIC);
 }
 
-void pcm_play_dma_postinit(void)
+static void sink_set_freq(uint16_t freq)
 {
-    audiohw_postinit();
-}
-
-void pcm_dma_apply_settings(void)
-{
-    audiohw_set_frequency(pcm_fsel);
+    audiohw_set_frequency(freq);
 }
 
 static void play_dma_start(const void* addr, size_t size)
@@ -157,7 +153,7 @@ static void pcm_play_dma_int_cb(int event)
     }
 }
 
-void pcm_play_dma_start(const void* addr, size_t size)
+static void sink_dma_start(const void* addr, size_t size)
 {
     play_dma_pending_event = DMA_EVENT_NONE;
     aic_state |= AIC_STATE_PLAYING;
@@ -166,7 +162,7 @@ void pcm_play_dma_start(const void* addr, size_t size)
     jz_writef(AIC_CCR, TDMS(1), ETUR(1), ERPL(1));
 }
 
-void pcm_play_dma_stop(void)
+static void sink_dma_stop(void)
 {
     /* disable DMA and underrun interrupts */
     jz_writef(AIC_CCR, TDMS(0), ETUR(0));
@@ -180,7 +176,7 @@ void pcm_play_dma_stop(void)
     if (jz_readf(AIC_I2SCR, STPBK) == 0) {
         while(jz_readf(AIC_SR, TFL) != 0);
     } else {
-        panicf("pcm_play_dma_stop: No bit clock running!");
+        panicf("sink_dma_stop: No bit clock running!");
     }
 
     /* disable playback */
@@ -190,14 +186,14 @@ void pcm_play_dma_stop(void)
     aic_state &= ~AIC_STATE_PLAYING;
 }
 
-void pcm_play_lock(void)
+static void sink_lock(void)
 {
     int irq = disable_irq_save();
     ++play_lock;
     restore_irq(irq);
 }
 
-void pcm_play_unlock(void)
+static void sink_unlock(void)
 {
     int irq = disable_irq_save();
     if(--play_lock == 0 && (aic_state & AIC_STATE_PLAYING)) {
@@ -208,6 +204,23 @@ void pcm_play_unlock(void)
     restore_irq(irq);
 }
 
+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,
+    },
+};
+
 #ifdef HAVE_RECORDING
 /*
  * Recording
-- 
rockbox-cvs mailing list
[email protected]
https://lists.haxx.se/mailman/listinfo/rockbox-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.