summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2013-04-05 04:36:05 -0400
committerMichael Sevakis <jethead71@rockbox.org>2013-04-11 22:55:16 +0200
commitf5a5b946867677de76c405ee72e2ea47e36e4c83 (patch)
tree8fb97a35059a16681b726973b4a5e13d41f96a35
parenta9049a79d706dba61837ad02c7d7e3475cb6c193 (diff)
downloadrockbox-f5a5b946867677de76c405ee72e2ea47e36e4c83.tar.gz
rockbox-f5a5b946867677de76c405ee72e2ea47e36e4c83.zip
Implement universal in-PCM-driver software volume control.
Implements double-buffered volume, balance and prescaling control in the main PCM driver when HAVE_SW_VOLUME_CONTROL is defined ensuring that all PCM is volume controlled and level changes are low in latency. Supports -73 to +6 dB using a 15-bit factor so that no large-integer math is needed. Low-level hardware drivers do not have to implement it themselves but parameters can be changed (currently defined in pcm-internal.h) to work best with a particular SoC or to provide different volume ranges. Volume and prescale calls should be made in the codec driver. It should appear as a normal hardware interface. PCM volume calls expect .1 dB units. Change-Id: Idf6316a64ef4fb8abcede10707e1e6c6d01d57db Reviewed-on: http://gerrit.rockbox.org/423 Reviewed-by: Michael Sevakis <jethead71@rockbox.org> Tested-by: Michael Sevakis <jethead71@rockbox.org>
-rw-r--r--firmware/SOURCES3
-rw-r--r--firmware/export/config/ondavx747.h5
-rw-r--r--firmware/export/config/ondavx777.h5
-rw-r--r--firmware/export/jz4740-codec.h2
-rw-r--r--firmware/export/pcm-internal.h54
-rw-r--r--firmware/export/pcm_sw_volume.h40
-rw-r--r--firmware/export/sound.h3
-rw-r--r--firmware/pcm.c195
-rw-r--r--firmware/pcm_sw_volume.c264
-rw-r--r--firmware/sound.c15
-rw-r--r--firmware/target/mips/ingenic_jz47xx/codec-jz4740.c27
-rw-r--r--lib/rbcodec/dsp/dsp_misc.c18
-rw-r--r--lib/rbcodec/dsp/pga.h3
13 files changed, 478 insertions, 156 deletions
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 92b2f5f87b..964d57ff5d 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -355,6 +355,9 @@ sound.c
355pcm_sampr.c 355pcm_sampr.c
356pcm.c 356pcm.c
357pcm_mixer.c 357pcm_mixer.c
358#ifdef HAVE_SW_VOLUME_CONTROL
359pcm_sw_volume.c
360#endif /* HAVE_SW_VOLUME_CONTROL */
358#ifdef HAVE_RECORDING 361#ifdef HAVE_RECORDING
359enc_base.c 362enc_base.c
360#endif /* HAVE_RECORDING */ 363#endif /* HAVE_RECORDING */
diff --git a/firmware/export/config/ondavx747.h b/firmware/export/config/ondavx747.h
index d303ea5925..8499c15ce9 100644
--- a/firmware/export/config/ondavx747.h
+++ b/firmware/export/config/ondavx747.h
@@ -132,11 +132,6 @@
132/* has no volume control, so we use the software ones */ 132/* has no volume control, so we use the software ones */
133#define HAVE_SW_VOLUME_CONTROL 133#define HAVE_SW_VOLUME_CONTROL
134 134
135/* software controlled volume ranges from -73 -> 0 dB, other than that
136 is controlled by hardware */
137#define SW_VOLUME_MIN -73
138#define SW_VOLUME_MAX 0
139
140/* define the bitmask of hardware sample rates */ 135/* define the bitmask of hardware sample rates */
141#define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ 136#define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \
142 SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ 137 SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \
diff --git a/firmware/export/config/ondavx777.h b/firmware/export/config/ondavx777.h
index 33bf6442af..a254b0177c 100644
--- a/firmware/export/config/ondavx777.h
+++ b/firmware/export/config/ondavx777.h
@@ -126,11 +126,6 @@
126/* has no volume control, so we use the software ones */ 126/* has no volume control, so we use the software ones */
127#define HAVE_SW_VOLUME_CONTROL 127#define HAVE_SW_VOLUME_CONTROL
128 128
129/* software controlled volume ranges from -73 -> 0 dB, other than that
130 is controlled by hardware */
131#define SW_VOLUME_MIN -73
132#define SW_VOLUME_MAX 0
133
134/* define the bitmask of hardware sample rates */ 129/* define the bitmask of hardware sample rates */
135#define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ 130#define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \
136 SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ 131 SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \
diff --git a/firmware/export/jz4740-codec.h b/firmware/export/jz4740-codec.h
index 37d2347f5b..3c088f5bf7 100644
--- a/firmware/export/jz4740-codec.h
+++ b/firmware/export/jz4740-codec.h
@@ -24,6 +24,6 @@
24#define VOLUME_MIN -730 24#define VOLUME_MIN -730
25#define VOLUME_MAX 60 25#define VOLUME_MAX 60
26 26
27void audiohw_set_volume(int v); 27void audiohw_set_master_vol(int vol_l, int vol_r);
28 28
29#endif /* __JZ4740_CODEC_H_ */ 29#endif /* __JZ4740_CODEC_H_ */
diff --git a/firmware/export/pcm-internal.h b/firmware/export/pcm-internal.h
index 397cf6832f..03e5c5e6e7 100644
--- a/firmware/export/pcm-internal.h
+++ b/firmware/export/pcm-internal.h
@@ -24,6 +24,19 @@
24 24
25#include "config.h" 25#include "config.h"
26 26
27#ifdef HAVE_SW_VOLUME_CONTROL
28/* Default settings - architecture may have other optimal values */
29
30#define PCM_FACTOR_BITS 15 /* Allows -73 to +6dB gain, sans 64-bit math */
31#define PCM_PLAY_DBL_BUF_SAMPLES 1024 /* Max 4KByte chunks */
32#define PCM_DBL_BUF_BSS /* In DRAM, uncached may be better */
33#define PCM_FACTOR_MIN 0x00000 /* Minimum final factor */
34#define PCM_FACTOR_MAX 0x10000 /* Maximum final factor */
35
36#define PCM_FACTOR_UNITY (1 << PCM_FACTOR_BITS)
37#endif /* HAVE_SW_VOLUME_CONTROL */
38
39#define PCM_SAMPLE_SIZE (2 * sizeof (int16_t))
27/* Cheapo buffer align macro to align to the 16-16 PCM size */ 40/* Cheapo buffer align macro to align to the 16-16 PCM size */
28#define ALIGN_AUDIOBUF(start, size) \ 41#define ALIGN_AUDIOBUF(start, size) \
29 ({ (start) = (void *)(((uintptr_t)(start) + 3) & ~3); \ 42 ({ (start) = (void *)(((uintptr_t)(start) + 3) & ~3); \
@@ -34,6 +47,23 @@ void pcm_do_peak_calculation(struct pcm_peaks *peaks, bool active,
34 47
35/** The following are for internal use between pcm.c and target- 48/** The following are for internal use between pcm.c and target-
36 specific portion **/ 49 specific portion **/
50/* Call registered callback to obtain next buffer */
51static inline bool pcm_get_more_int(const void **addr, size_t *size)
52{
53 extern volatile pcm_play_callback_type pcm_callback_for_more;
54 pcm_play_callback_type get_more = pcm_callback_for_more;
55
56 if (UNLIKELY(!get_more))
57 return false;
58
59 *addr = NULL;
60 *size = 0;
61 get_more(addr, size);
62 ALIGN_AUDIOBUF(*addr, *size);
63
64 return *addr && *size;
65}
66
37static FORCE_INLINE enum pcm_dma_status pcm_call_status_cb( 67static FORCE_INLINE enum pcm_dma_status pcm_call_status_cb(
38 pcm_status_callback_type callback, enum pcm_dma_status status) 68 pcm_status_callback_type callback, enum pcm_dma_status status)
39{ 69{
@@ -43,14 +73,34 @@ static FORCE_INLINE enum pcm_dma_status pcm_call_status_cb(
43 return callback(status); 73 return callback(status);
44} 74}
45 75
46static FORCE_INLINE enum pcm_dma_status 76static FORCE_INLINE enum pcm_dma_status pcm_play_call_status_cb(
47pcm_play_dma_status_callback(enum pcm_dma_status status) 77 enum pcm_dma_status status)
48{ 78{
49 extern enum pcm_dma_status 79 extern enum pcm_dma_status
50 (* volatile pcm_play_status_callback)(enum pcm_dma_status); 80 (* volatile pcm_play_status_callback)(enum pcm_dma_status);
51 return pcm_call_status_cb(pcm_play_status_callback, status); 81 return pcm_call_status_cb(pcm_play_status_callback, status);
52} 82}
53 83
84static FORCE_INLINE enum pcm_dma_status
85pcm_play_dma_status_callback(enum pcm_dma_status status)
86{
87#ifdef HAVE_SW_VOLUME_CONTROL
88 extern enum pcm_dma_status
89 pcm_play_dma_status_callback_int(enum pcm_dma_status status);
90 return pcm_play_dma_status_callback_int(status);
91#else
92 return pcm_play_call_status_cb(status);
93#endif /* HAVE_SW_VOLUME_CONTROL */
94}
95
96#ifdef HAVE_SW_VOLUME_CONTROL
97void pcm_play_dma_start_int(const void *addr, size_t size);
98void pcm_play_dma_pause_int(bool pause);
99void pcm_play_dma_stop_int(void);
100void pcm_play_stop_int(void);
101const void *pcm_play_dma_get_peak_buffer_int(int *count);
102#endif /* HAVE_SW_VOLUME_CONTROL */
103
54/* Called by the bottom layer ISR when more data is needed. Returns true 104/* Called by the bottom layer ISR when more data is needed. Returns true
55 * if a new buffer is available, false otherwise. */ 105 * if a new buffer is available, false otherwise. */
56bool pcm_play_dma_complete_callback(enum pcm_dma_status status, 106bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
diff --git a/firmware/export/pcm_sw_volume.h b/firmware/export/pcm_sw_volume.h
new file mode 100644
index 0000000000..b86e78f500
--- /dev/null
+++ b/firmware/export/pcm_sw_volume.h
@@ -0,0 +1,40 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2013 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef PCM_SW_VOLUME_H
22#define PCM_SW_VOLUME_H
23
24#ifdef HAVE_SW_VOLUME_CONTROL
25
26#include <audiohw.h>
27
28#define PCM_MUTE_LEVEL INT_MIN
29
30#ifdef AUDIOHW_HAVE_PRESCALER
31/* Set the prescaler value for all PCM playback */
32void pcm_set_prescaler(int prescale);
33#endif /* AUDIOHW_HAVE_PRESCALER */
34
35/* Set the per-channel volume cut/gain for all PCM playback */
36void pcm_set_master_volume(int vol_l, int vol_r);
37
38#endif /* HAVE_SW_VOLUME_CONTROL */
39
40#endif /* PCM_SW_VOLUME_H */
diff --git a/firmware/export/sound.h b/firmware/export/sound.h
index ba6120ce8f..ebf728c7c7 100644
--- a/firmware/export/sound.h
+++ b/firmware/export/sound.h
@@ -32,9 +32,6 @@ enum {
32 DSP_CALLBACK_SET_TREBLE, 32 DSP_CALLBACK_SET_TREBLE,
33 DSP_CALLBACK_SET_CHANNEL_CONFIG, 33 DSP_CALLBACK_SET_CHANNEL_CONFIG,
34 DSP_CALLBACK_SET_STEREO_WIDTH, 34 DSP_CALLBACK_SET_STEREO_WIDTH,
35#ifdef HAVE_SW_VOLUME_CONTROL
36 DSP_CALLBACK_SET_SW_VOLUME,
37#endif
38}; 35};
39#endif 36#endif
40 37
diff --git a/firmware/pcm.c b/firmware/pcm.c
index 94b0d6eefb..6bf0e12c8d 100644
--- a/firmware/pcm.c
+++ b/firmware/pcm.c
@@ -86,7 +86,7 @@
86static bool pcm_is_ready = false; 86static bool pcm_is_ready = false;
87 87
88/* The registered callback function to ask for more mp3 data */ 88/* The registered callback function to ask for more mp3 data */
89static volatile pcm_play_callback_type 89volatile pcm_play_callback_type
90 pcm_callback_for_more SHAREDBSS_ATTR = NULL; 90 pcm_callback_for_more SHAREDBSS_ATTR = NULL;
91/* The registered callback function to inform of DMA status */ 91/* The registered callback function to inform of DMA status */
92volatile pcm_status_callback_type 92volatile pcm_status_callback_type
@@ -102,9 +102,89 @@ unsigned long pcm_sampr SHAREDBSS_ATTR = HW_SAMPR_DEFAULT;
102/* samplerate frequency selection index */ 102/* samplerate frequency selection index */
103int pcm_fsel SHAREDBSS_ATTR = HW_FREQ_DEFAULT; 103int pcm_fsel SHAREDBSS_ATTR = HW_FREQ_DEFAULT;
104 104
105/* Called internally by functions to reset the state */ 105static void pcm_play_data_start_int(const void *addr, size_t size);
106static void pcm_play_stopped(void) 106static void pcm_play_pause_int(bool play);
107void pcm_play_stop_int(void);
108
109#ifndef HAVE_SW_VOLUME_CONTROL
110/** Standard hw volume control functions - otherwise, see pcm_sw_volume.c **/
111static inline void pcm_play_dma_start_int(const void *addr, size_t size)
112{
113 pcm_play_dma_start(addr, size);
114}
115
116static inline void pcm_play_dma_pause_int(bool pause)
117{
118 if (pause || pcm_get_bytes_waiting() > 0)
119 {
120 pcm_play_dma_pause(pause);
121 }
122 else
123 {
124 logf(" no data");
125 pcm_play_data_start_int(NULL, 0);
126 }
127}
128
129static inline void pcm_play_dma_stop_int(void)
130{
131 pcm_play_dma_stop();
132}
133
134static inline const void * pcm_play_dma_get_peak_buffer_int(int *count)
135{
136 return pcm_play_dma_get_peak_buffer(count);
137}
138
139bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
140 const void **addr, size_t *size)
141{
142 /* Check status callback first if error */
143 if (status < PCM_DMAST_OK)
144 status = pcm_play_dma_status_callback(status);
145
146 if (status >= PCM_DMAST_OK && pcm_get_more_int(addr, size))
147 return true;
148
149 /* Error, callback missing or no more DMA to do */
150 pcm_play_stop_int();
151 return false;
152}
153#endif /* ndef HAVE_SW_VOLUME_CONTROL */
154
155static void pcm_play_data_start_int(const void *addr, size_t size)
107{ 156{
157 ALIGN_AUDIOBUF(addr, size);
158
159 if ((addr && size) || pcm_get_more_int(&addr, &size))
160 {
161 pcm_apply_settings();
162 logf(" pcm_play_dma_start_int");
163 pcm_play_dma_start_int(addr, size);
164 pcm_playing = true;
165 pcm_paused = false;
166 }
167 else
168 {
169 /* Force a stop */
170 logf(" pcm_play_stop_int");
171 pcm_play_stop_int();
172 }
173}
174
175static void pcm_play_pause_int(bool play)
176{
177 if (play)
178 pcm_apply_settings();
179
180 logf(" pcm_play_dma_pause_int");
181 pcm_play_dma_pause_int(!play);
182 pcm_paused = !play && pcm_playing;
183}
184
185void pcm_play_stop_int(void)
186{
187 pcm_play_dma_stop_int();
108 pcm_callback_for_more = NULL; 188 pcm_callback_for_more = NULL;
109 pcm_play_status_callback = NULL; 189 pcm_play_status_callback = NULL;
110 pcm_paused = false; 190 pcm_paused = false;
@@ -195,7 +275,7 @@ void pcm_calculate_peaks(int *left, int *right)
195 static struct pcm_peaks peaks; 275 static struct pcm_peaks peaks;
196 276
197 int count; 277 int count;
198 const void *addr = pcm_play_dma_get_peak_buffer(&count); 278 const void *addr = pcm_play_dma_get_peak_buffer_int(&count);
199 279
200 pcm_do_peak_calculation(&peaks, pcm_playing && !pcm_paused, 280 pcm_do_peak_calculation(&peaks, pcm_playing && !pcm_paused,
201 addr, count); 281 addr, count);
@@ -207,9 +287,9 @@ void pcm_calculate_peaks(int *left, int *right)
207 *right = peaks.right; 287 *right = peaks.right;
208} 288}
209 289
210const void* pcm_get_peak_buffer(int * count) 290const void * pcm_get_peak_buffer(int *count)
211{ 291{
212 return pcm_play_dma_get_peak_buffer(count); 292 return pcm_play_dma_get_peak_buffer_int(count);
213} 293}
214 294
215bool pcm_is_playing(void) 295bool pcm_is_playing(void)
@@ -233,8 +313,6 @@ void pcm_init(void)
233{ 313{
234 logf("pcm_init"); 314 logf("pcm_init");
235 315
236 pcm_play_stopped();
237
238 pcm_set_frequency(HW_SAMPR_DEFAULT); 316 pcm_set_frequency(HW_SAMPR_DEFAULT);
239 317
240 logf(" pcm_play_dma_init"); 318 logf(" pcm_play_dma_init");
@@ -258,41 +336,6 @@ bool pcm_is_initialized(void)
258 return pcm_is_ready; 336 return pcm_is_ready;
259} 337}
260 338
261/* Common code to pcm_play_data and pcm_play_pause */
262static void pcm_play_data_start(const void *addr, size_t size)
263{
264 ALIGN_AUDIOBUF(addr, size);
265
266 if (!(addr && size))
267 {
268 pcm_play_callback_type get_more = pcm_callback_for_more;
269 addr = NULL;
270 size = 0;
271
272 if (get_more)
273 {
274 logf(" get_more");
275 get_more(&addr, &size);
276 ALIGN_AUDIOBUF(addr, size);
277 }
278 }
279
280 if (addr && size)
281 {
282 logf(" pcm_play_dma_start");
283 pcm_apply_settings();
284 pcm_play_dma_start(addr, size);
285 pcm_playing = true;
286 pcm_paused = false;
287 return;
288 }
289
290 /* Force a stop */
291 logf(" pcm_play_dma_stop");
292 pcm_play_dma_stop();
293 pcm_play_stopped();
294}
295
296void pcm_play_data(pcm_play_callback_type get_more, 339void pcm_play_data(pcm_play_callback_type get_more,
297 pcm_status_callback_type status_cb, 340 pcm_status_callback_type status_cb,
298 const void *start, size_t size) 341 const void *start, size_t size)
@@ -304,41 +347,12 @@ void pcm_play_data(pcm_play_callback_type get_more,
304 pcm_callback_for_more = get_more; 347 pcm_callback_for_more = get_more;
305 pcm_play_status_callback = status_cb; 348 pcm_play_status_callback = status_cb;
306 349
307 logf(" pcm_play_data_start"); 350 logf(" pcm_play_data_start_int");
308 pcm_play_data_start(start, size); 351 pcm_play_data_start_int(start, size);
309 352
310 pcm_play_unlock(); 353 pcm_play_unlock();
311} 354}
312 355
313bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
314 const void **addr, size_t *size)
315{
316 /* Check status callback first if error */
317 if (status < PCM_DMAST_OK)
318 status = pcm_play_dma_status_callback(status);
319
320 pcm_play_callback_type get_more = pcm_callback_for_more;
321
322 if (get_more && status >= PCM_DMAST_OK)
323 {
324 *addr = NULL;
325 *size = 0;
326
327 /* Call registered callback to obtain next buffer */
328 get_more(addr, size);
329 ALIGN_AUDIOBUF(*addr, *size);
330
331 if (*addr && *size)
332 return true;
333 }
334
335 /* Error, callback missing or no more DMA to do */
336 pcm_play_dma_stop();
337 pcm_play_stopped();
338
339 return false;
340}
341
342void pcm_play_pause(bool play) 356void pcm_play_pause(bool play)
343{ 357{
344 logf("pcm_play_pause: %s", play ? "play" : "pause"); 358 logf("pcm_play_pause: %s", play ? "play" : "pause");
@@ -347,28 +361,8 @@ void pcm_play_pause(bool play)
347 361
348 if (play == pcm_paused && pcm_playing) 362 if (play == pcm_paused && pcm_playing)
349 { 363 {
350 if (!play) 364 logf(" pcm_play_pause_int");
351 { 365 pcm_play_pause_int(play);
352 logf(" pcm_play_dma_pause");
353 pcm_play_dma_pause(true);
354 pcm_paused = true;
355 }
356 else if (pcm_get_bytes_waiting() > 0)
357 {
358 logf(" pcm_play_dma_pause");
359 pcm_apply_settings();
360 pcm_play_dma_pause(false);
361 pcm_paused = false;
362 }
363 else
364 {
365 logf(" pcm_play_dma_start: no data");
366 pcm_play_data_start(NULL, 0);
367 }
368 }
369 else
370 {
371 logf(" no change");
372 } 366 }
373 367
374 pcm_play_unlock(); 368 pcm_play_unlock();
@@ -382,13 +376,8 @@ void pcm_play_stop(void)
382 376
383 if (pcm_playing) 377 if (pcm_playing)
384 { 378 {
385 logf(" pcm_play_dma_stop"); 379 logf(" pcm_play_stop_int");
386 pcm_play_dma_stop(); 380 pcm_play_stop_int();
387 pcm_play_stopped();
388 }
389 else
390 {
391 logf(" not playing");
392 } 381 }
393 382
394 pcm_play_unlock(); 383 pcm_play_unlock();
diff --git a/firmware/pcm_sw_volume.c b/firmware/pcm_sw_volume.c
new file mode 100644
index 0000000000..bcd498fe46
--- /dev/null
+++ b/firmware/pcm_sw_volume.c
@@ -0,0 +1,264 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2013 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include "system.h"
23#include "pcm.h"
24#include "pcm-internal.h"
25#include "dsp-util.h"
26#include "fixedpoint.h"
27#include "pcm_sw_volume.h"
28
29/* source buffer from client */
30static const void * volatile src_buf_addr = NULL;
31static size_t volatile src_buf_rem = 0;
32
33#define PCM_PLAY_DBL_BUF_SIZE (PCM_PLAY_DBL_BUF_SAMPLE*PCM_SAMPLE_SIZE)
34
35/* double buffer and frame length control */
36static int16_t pcm_dbl_buf[2][PCM_PLAY_DBL_BUF_SAMPLES*2]
37 PCM_DBL_BUF_BSS MEM_ALIGN_ATTR;
38static size_t pcm_dbl_buf_size[2];
39static int pcm_dbl_buf_num = 0;
40static size_t frame_size;
41static unsigned int frame_count, frame_err, frame_frac;
42
43#ifdef AUDIOHW_HAVE_PRESCALER
44static int32_t prescale_factor = PCM_FACTOR_UNITY;
45static int32_t vol_factor_l = 0, vol_factor_r = 0;
46#endif /* AUDIOHW_HAVE_PRESCALER */
47
48/* pcm scaling factors */
49static int32_t pcm_factor_l = 0, pcm_factor_r = 0;
50
51#define PCM_FACTOR_CLIP(f) \
52 MAX(MIN((f), PCM_FACTOR_MAX), PCM_FACTOR_MIN)
53#define PCM_SCALE_SAMPLE(f, s) \
54 (((f) * (s) + PCM_FACTOR_UNITY/2) >> PCM_FACTOR_BITS)
55
56
57/* TODO: #include CPU-optimized routines and move this to /firmware/asm */
58static inline void pcm_copy_buffer(int16_t *dst, const int16_t *src,
59 size_t size)
60{
61 int32_t factor_l = pcm_factor_l;
62 int32_t factor_r = pcm_factor_r;
63
64 if (LIKELY(factor_l <= PCM_FACTOR_UNITY && factor_r <= PCM_FACTOR_UNITY))
65 {
66 /* All cut or unity */
67 while (size)
68 {
69 *dst++ = PCM_SCALE_SAMPLE(factor_l, *src++);
70 *dst++ = PCM_SCALE_SAMPLE(factor_r, *src++);
71 size -= PCM_SAMPLE_SIZE;
72 }
73 }
74 else
75 {
76 /* Any positive gain requires clipping */
77 while (size)
78 {
79 *dst++ = clip_sample_16(PCM_SCALE_SAMPLE(factor_l, *src++));
80 *dst++ = clip_sample_16(PCM_SCALE_SAMPLE(factor_r, *src++));
81 size -= PCM_SAMPLE_SIZE;
82 }
83 }
84}
85
86bool pcm_play_dma_complete_callback(enum pcm_dma_status status,
87 const void **addr, size_t *size)
88{
89 /* Check status callback first if error */
90 if (status < PCM_DMAST_OK)
91 status = pcm_play_call_status_cb(status);
92
93 size_t sz = pcm_dbl_buf_size[pcm_dbl_buf_num];
94
95 if (status >= PCM_DMAST_OK && sz)
96 {
97 /* Do next chunk */
98 *addr = pcm_dbl_buf[pcm_dbl_buf_num];
99 *size = sz;
100 return true;
101 }
102 else
103 {
104 /* This is a stop chunk or error */
105 pcm_play_stop_int();
106 return false;
107 }
108}
109
110/* Equitably divide large source buffers amongst double buffer frames;
111 frames smaller than or equal to the double buffer chunk size will play
112 in one chunk */
113static void update_frame_params(size_t size)
114{
115 int count = size / PCM_SAMPLE_SIZE;
116 frame_count = (count + PCM_PLAY_DBL_BUF_SAMPLES - 1) /
117 PCM_PLAY_DBL_BUF_SAMPLES;
118 int perframe = count / frame_count;
119 frame_size = perframe * PCM_SAMPLE_SIZE;
120 frame_frac = count - perframe * frame_count;
121 frame_err = 0;
122}
123
124/* Obtain the next buffer and prepare it for pcm driver playback */
125enum pcm_dma_status
126pcm_play_dma_status_callback_int(enum pcm_dma_status status)
127{
128 if (status != PCM_DMAST_STARTED)
129 return status;
130
131 size_t size = pcm_dbl_buf_size[pcm_dbl_buf_num];
132 const void *addr = src_buf_addr + size;
133
134 size = src_buf_rem - size;
135
136 if (size == 0 && pcm_get_more_int(&addr, &size))
137 {
138 update_frame_params(size);
139 pcm_play_call_status_cb(PCM_DMAST_STARTED);
140 }
141
142 src_buf_addr = addr;
143 src_buf_rem = size;
144
145 if (size != 0)
146 {
147 size = frame_size;
148
149 if ((frame_err += frame_frac) >= frame_count)
150 {
151 frame_err -= frame_count;
152 size += PCM_SAMPLE_SIZE;
153 }
154 }
155
156 pcm_dbl_buf_num ^= 1;
157 pcm_dbl_buf_size[pcm_dbl_buf_num] = size;
158 pcm_copy_buffer(pcm_dbl_buf[pcm_dbl_buf_num], addr, size);
159
160 return PCM_DMAST_OK;
161}
162
163/* Prefill double buffer and start pcm driver */
164static void start_pcm(bool reframe)
165{
166 pcm_dbl_buf_num = 0;
167 pcm_dbl_buf_size[0] = 0;
168
169 if (reframe)
170 update_frame_params(src_buf_rem);
171
172 pcm_play_dma_status_callback(PCM_DMAST_STARTED);
173 pcm_play_dma_status_callback(PCM_DMAST_STARTED);
174
175 pcm_play_dma_start(pcm_dbl_buf[1], pcm_dbl_buf_size[1]);
176}
177
178void pcm_play_dma_start_int(const void *addr, size_t size)
179{
180 src_buf_addr = addr;
181 src_buf_rem = size;
182 start_pcm(true);
183}
184
185void pcm_play_dma_pause_int(bool pause)
186{
187 if (pause)
188 pcm_play_dma_pause(true);
189 else if (src_buf_rem)
190 start_pcm(false); /* Reprocess in case volume level changed */
191 else
192 pcm_play_stop_int(); /* Playing frame was last frame */
193}
194
195void pcm_play_dma_stop_int(void)
196{
197 pcm_play_dma_stop();
198 src_buf_addr = NULL;
199 src_buf_rem = 0;
200}
201
202/* Return playing buffer from the source buffer */
203const void * pcm_play_dma_get_peak_buffer_int(int *count)
204{
205 const void *addr = src_buf_addr;
206 size_t size = src_buf_rem;
207 const void *addr2 = src_buf_addr;
208
209 if (addr == addr2 && size)
210 {
211 *count = size / PCM_SAMPLE_SIZE;
212 return addr;
213 }
214
215 *count = 0;
216 return NULL;
217}
218
219/* Return the scale factor corresponding to the centibel level */
220static int32_t pcm_centibels_to_factor(int volume)
221{
222 if (volume == PCM_MUTE_LEVEL)
223 return 0; /* mute */
224
225 /* Centibels -> fixedpoint */
226 return fp_factor(PCM_FACTOR_UNITY*volume / 10, PCM_FACTOR_BITS);
227}
228
229#ifdef AUDIOHW_HAVE_PRESCALER
230/* Produce final pcm scale factor */
231static void pcm_sync_prescaler(void)
232{
233 int32_t factor_l = fp_mul(prescale_factor, vol_factor_l, PCM_FACTOR_BITS);
234 int32_t factor_r = fp_mul(prescale_factor, vol_factor_r, PCM_FACTOR_BITS);
235 pcm_factor_l = PCM_FACTOR_CLIP(factor_l);
236 pcm_factor_r = PCM_FACTOR_CLIP(factor_r);
237}
238
239/* Set the prescaler value for all PCM playback */
240void pcm_set_prescaler(int prescale)
241{
242 prescale_factor = pcm_centibels_to_factor(-prescale);
243 pcm_sync_prescaler();
244}
245
246/* Set the per-channel volume cut/gain for all PCM playback */
247void pcm_set_master_volume(int vol_l, int vol_r)
248{
249 vol_factor_l = pcm_centibels_to_factor(vol_l);
250 vol_factor_r = pcm_centibels_to_factor(vol_r);
251 pcm_sync_prescaler();
252}
253
254#else /* ndef AUDIOHW_HAVE_PRESCALER */
255
256/* Set the per-channel volume cut/gain for all PCM playback */
257void pcm_set_master_volume(int vol_l, int vol_r)
258{
259 int32_t factor_l = pcm_centibels_to_factor(vol_l);
260 int32_t factor_r = pcm_centibels_to_factor(vol_r);
261 pcm_factor_l = PCM_FACTOR_CLIP(factor_l);
262 pcm_factor_r = PCM_FACTOR_CLIP(factor_r);
263}
264#endif /* AUDIOHW_HAVE_PRESCALER */
diff --git a/firmware/sound.c b/firmware/sound.c
index e442e87f8c..2ffef0e72b 100644
--- a/firmware/sound.c
+++ b/firmware/sound.c
@@ -26,6 +26,9 @@
26#include "logf.h" 26#include "logf.h"
27#include "system.h" 27#include "system.h"
28#include "i2c.h" 28#include "i2c.h"
29#ifdef HAVE_SW_VOLUME_CONTROL
30#include "pcm_sw_volume.h"
31#endif /* HAVE_SW_VOLUME_CONTROL */
29 32
30/* TODO 33/* TODO
31 * find a nice way to handle 1.5db steps -> see wm8751 ifdef in sound_set_bass/treble 34 * find a nice way to handle 1.5db steps -> see wm8751 ifdef in sound_set_bass/treble
@@ -215,7 +218,7 @@ static void set_prescaled_volume(void)
215 dsp_callback(DSP_CALLBACK_SET_PRESCALE, prescale); 218 dsp_callback(DSP_CALLBACK_SET_PRESCALE, prescale);
216#endif 219#endif
217 220
218 if (current_volume == VOLUME_MIN) 221 if (current_volume <= VOLUME_MIN)
219 prescale = 0; /* Make sure the chip gets muted at VOLUME_MIN */ 222 prescale = 0; /* Make sure the chip gets muted at VOLUME_MIN */
220 223
221 l = r = current_volume + prescale; 224 l = r = current_volume + prescale;
@@ -231,13 +234,11 @@ static void set_prescaled_volume(void)
231 r += ((r - (VOLUME_MIN - ONE_DB)) * current_balance) / VOLUME_RANGE; 234 r += ((r - (VOLUME_MIN - ONE_DB)) * current_balance) / VOLUME_RANGE;
232 } 235 }
233 236
234#ifdef HAVE_SW_VOLUME_CONTROL
235 dsp_callback(DSP_CALLBACK_SET_SW_VOLUME, 0);
236#endif
237
238/* ypr0 with sdl has separate volume controls */ 237/* ypr0 with sdl has separate volume controls */
239#if !defined(HAVE_SDL_AUDIO) || defined(SAMSUNG_YPR0) 238#if !defined(HAVE_SDL_AUDIO) || defined(SAMSUNG_YPR0)
240#if CONFIG_CODEC == MAS3507D 239#if defined(HAVE_SW_VOLUME_CONTROL) || defined(HAVE_JZ4740_CODEC)
240 audiohw_set_master_vol(l, r);
241#elif CONFIG_CODEC == MAS3507D
241 dac_volume(tenthdb2reg(l), tenthdb2reg(r), false); 242 dac_volume(tenthdb2reg(l), tenthdb2reg(r), false);
242#elif defined(HAVE_UDA1380) || defined(HAVE_WM8975) || defined(HAVE_WM8758) \ 243#elif defined(HAVE_UDA1380) || defined(HAVE_WM8975) || defined(HAVE_WM8758) \
243 || defined(HAVE_WM8711) || defined(HAVE_WM8721) || defined(HAVE_WM8731) \ 244 || defined(HAVE_WM8711) || defined(HAVE_WM8721) || defined(HAVE_WM8731) \
@@ -253,7 +254,7 @@ static void set_prescaled_volume(void)
253 254
254#elif defined(HAVE_TLV320) || defined(HAVE_WM8978) || defined(HAVE_WM8985) || defined(HAVE_IMX233_CODEC) || defined(HAVE_AIC3X) 255#elif defined(HAVE_TLV320) || defined(HAVE_WM8978) || defined(HAVE_WM8985) || defined(HAVE_IMX233_CODEC) || defined(HAVE_AIC3X)
255 audiohw_set_headphone_vol(tenthdb2master(l), tenthdb2master(r)); 256 audiohw_set_headphone_vol(tenthdb2master(l), tenthdb2master(r));
256#elif defined(HAVE_JZ4740_CODEC) || defined(HAVE_SDL_AUDIO) || defined(ANDROID) 257#elif defined(HAVE_SDL_AUDIO) || defined(ANDROID)
257 audiohw_set_volume(current_volume); 258 audiohw_set_volume(current_volume);
258#endif 259#endif
259#else /* HAVE_SDL_AUDIO */ 260#else /* HAVE_SDL_AUDIO */
diff --git a/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c b/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c
index ab9efc91b0..065433e12a 100644
--- a/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c
@@ -24,11 +24,12 @@
24#include "sound.h" 24#include "sound.h"
25#include "jz4740.h" 25#include "jz4740.h"
26#include "system.h" 26#include "system.h"
27#include "pcm_sw_volume.h"
27 28
28/* TODO */ 29/* TODO */
29const struct sound_settings_info audiohw_settings[] = { 30const struct sound_settings_info audiohw_settings[] = {
30#ifdef HAVE_SW_VOLUME_CONTROL 31#ifdef HAVE_SW_VOLUME_CONTROL
31 [SOUND_VOLUME] = {"dB", 0, 1, SW_VOLUME_MIN, 6, 0}, 32 [SOUND_VOLUME] = {"dB", 0, 1, -74, 6, -25},
32#else 33#else
33 [SOUND_VOLUME] = {"dB", 0, 1, 0, 6, 0}, 34 [SOUND_VOLUME] = {"dB", 0, 1, 0, 6, 0},
34#endif 35#endif
@@ -293,16 +294,24 @@ void audiohw_init(void)
293 i2s_codec_init(); 294 i2s_codec_init();
294} 295}
295 296
296void audiohw_set_volume(int v) 297void audiohw_set_master_vol(int vol_l, int vol_r)
297{ 298{
298 if(v >= 0) 299#ifdef HAVE_SW_VOLUME_CONTROL
299 { 300 /* SW volume for <= 1.0 gain, HW at unity, < VOLUME_MIN == MUTE */
300 /* 0 <= v <= 60 */ 301 int sw_volume_l = vol_l < VOLUME_MIN ? PCM_MUTE_LEVEL : MIN(vol_l, 0);
301 unsigned int codec_volume = ICDC_CDCCR2_HPVOL(v / 20); 302 int sw_volume_r = vol_r < VOLUME_MIN ? PCM_MUTE_LEVEL : MIN(vol_r, 0);
303 pcm_set_master_volume(sw_volume_l, sw_volume_r);
304#endif /* HAVE_SW_VOLUME_CONTROL */
302 305
303 if((REG_ICDC_CDCCR2 & ICDC_CDCCR2_HPVOL(0x3)) != codec_volume) 306 /* NOTE: the channel being cut if balance is not equal will need
304 REG_ICDC_CDCCR2 = (REG_ICDC_CDCCR2 & ~ICDC_CDCCR2_HPVOL(0x3)) | codec_volume; 307 adjusting downward so maintain proportion if using volume boost */
305 } 308
309 /* HW volume for > 1.0 gain */
310 int v = MAX(vol_l, vol_r);
311 unsigned int hw_volume = v > 0 ? ICDC_CDCCR2_HPVOL(v / 20) : 0;
312
313 if((REG_ICDC_CDCCR2 & ICDC_CDCCR2_HPVOL(0x3)) != hw_volume)
314 REG_ICDC_CDCCR2 = (REG_ICDC_CDCCR2 & ~ICDC_CDCCR2_HPVOL(0x3)) | hw_volume;
306} 315}
307 316
308void audiohw_set_frequency(int freq) 317void audiohw_set_frequency(int freq)
diff --git a/lib/rbcodec/dsp/dsp_misc.c b/lib/rbcodec/dsp/dsp_misc.c
index 1083215c17..a8c9423cfb 100644
--- a/lib/rbcodec/dsp/dsp_misc.c
+++ b/lib/rbcodec/dsp/dsp_misc.c
@@ -35,11 +35,6 @@
35#endif 35#endif
36#include <string.h> 36#include <string.h>
37 37
38#if defined(HAVE_SW_TONE_CONTROLS) && defined(HAVE_SW_VOLUME_CONTROL)
39/* Still need this for volume control */
40#include "settings.h"
41#endif
42
43/** Firmware callback interface **/ 38/** Firmware callback interface **/
44 39
45/* Hook back from firmware/ part of audio, which can't/shouldn't call apps/ 40/* Hook back from firmware/ part of audio, which can't/shouldn't call apps/
@@ -58,19 +53,6 @@ int dsp_callback(int msg, intptr_t param)
58 case DSP_CALLBACK_SET_TREBLE: 53 case DSP_CALLBACK_SET_TREBLE:
59 tone_set_treble(param); 54 tone_set_treble(param);
60 break; 55 break;
61 /* FIXME: This must be done by bottom-level PCM driver so it works with
62 all PCM, not here and not in mixer. I won't fully support it
63 here with all streams. -- jethead71 */
64#ifdef HAVE_SW_VOLUME_CONTROL
65 case DSP_CALLBACK_SET_SW_VOLUME:
66 if (global_settings.volume < SW_VOLUME_MAX ||
67 global_settings.volume > SW_VOLUME_MIN)
68 {
69 int vol_gain = get_replaygain_int(global_settings.volume * 100);
70 pga_set_gain(PGA_VOLUME, vol_gain);
71 }
72 break;
73#endif /* HAVE_SW_VOLUME_CONTROL */
74#endif /* HAVE_SW_TONE_CONTROLS */ 56#endif /* HAVE_SW_TONE_CONTROLS */
75 case DSP_CALLBACK_SET_CHANNEL_CONFIG: 57 case DSP_CALLBACK_SET_CHANNEL_CONFIG:
76 channel_mode_set_config(param); 58 channel_mode_set_config(param);
diff --git a/lib/rbcodec/dsp/pga.h b/lib/rbcodec/dsp/pga.h
index f0c4c4717f..c05265873b 100644
--- a/lib/rbcodec/dsp/pga.h
+++ b/lib/rbcodec/dsp/pga.h
@@ -28,9 +28,6 @@ enum pga_gain_ids
28{ 28{
29 PGA_EQ_PRECUT = 0, 29 PGA_EQ_PRECUT = 0,
30 PGA_REPLAYGAIN, 30 PGA_REPLAYGAIN,
31#ifdef HAVE_SW_VOLUME_CONTROL
32 PGA_VOLUME,
33#endif
34 PGA_NUM_GAINS, 31 PGA_NUM_GAINS,
35}; 32};
36 33