diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2013-04-05 04:36:05 -0400 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2013-04-11 22:55:16 +0200 |
commit | f5a5b946867677de76c405ee72e2ea47e36e4c83 (patch) | |
tree | 8fb97a35059a16681b726973b4a5e13d41f96a35 /firmware | |
parent | a9049a79d706dba61837ad02c7d7e3475cb6c193 (diff) | |
download | rockbox-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>
Diffstat (limited to 'firmware')
-rw-r--r-- | firmware/SOURCES | 3 | ||||
-rw-r--r-- | firmware/export/config/ondavx747.h | 5 | ||||
-rw-r--r-- | firmware/export/config/ondavx777.h | 5 | ||||
-rw-r--r-- | firmware/export/jz4740-codec.h | 2 | ||||
-rw-r--r-- | firmware/export/pcm-internal.h | 54 | ||||
-rw-r--r-- | firmware/export/pcm_sw_volume.h | 40 | ||||
-rw-r--r-- | firmware/export/sound.h | 3 | ||||
-rw-r--r-- | firmware/pcm.c | 195 | ||||
-rw-r--r-- | firmware/pcm_sw_volume.c | 264 | ||||
-rw-r--r-- | firmware/sound.c | 15 | ||||
-rw-r--r-- | firmware/target/mips/ingenic_jz47xx/codec-jz4740.c | 27 |
11 files changed, 478 insertions, 135 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 | |||
355 | pcm_sampr.c | 355 | pcm_sampr.c |
356 | pcm.c | 356 | pcm.c |
357 | pcm_mixer.c | 357 | pcm_mixer.c |
358 | #ifdef HAVE_SW_VOLUME_CONTROL | ||
359 | pcm_sw_volume.c | ||
360 | #endif /* HAVE_SW_VOLUME_CONTROL */ | ||
358 | #ifdef HAVE_RECORDING | 361 | #ifdef HAVE_RECORDING |
359 | enc_base.c | 362 | enc_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 | ||
27 | void audiohw_set_volume(int v); | 27 | void 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 */ | ||
51 | static 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 | |||
37 | static FORCE_INLINE enum pcm_dma_status pcm_call_status_cb( | 67 | static 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 | ||
46 | static FORCE_INLINE enum pcm_dma_status | 76 | static FORCE_INLINE enum pcm_dma_status pcm_play_call_status_cb( |
47 | pcm_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 | ||
84 | static FORCE_INLINE enum pcm_dma_status | ||
85 | pcm_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 | ||
97 | void pcm_play_dma_start_int(const void *addr, size_t size); | ||
98 | void pcm_play_dma_pause_int(bool pause); | ||
99 | void pcm_play_dma_stop_int(void); | ||
100 | void pcm_play_stop_int(void); | ||
101 | const 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. */ |
56 | bool pcm_play_dma_complete_callback(enum pcm_dma_status status, | 106 | bool 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 */ | ||
32 | void pcm_set_prescaler(int prescale); | ||
33 | #endif /* AUDIOHW_HAVE_PRESCALER */ | ||
34 | |||
35 | /* Set the per-channel volume cut/gain for all PCM playback */ | ||
36 | void 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 @@ | |||
86 | static bool pcm_is_ready = false; | 86 | static 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 */ |
89 | static volatile pcm_play_callback_type | 89 | volatile 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 */ |
92 | volatile pcm_status_callback_type | 92 | volatile 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 */ |
103 | int pcm_fsel SHAREDBSS_ATTR = HW_FREQ_DEFAULT; | 103 | int pcm_fsel SHAREDBSS_ATTR = HW_FREQ_DEFAULT; |
104 | 104 | ||
105 | /* Called internally by functions to reset the state */ | 105 | static void pcm_play_data_start_int(const void *addr, size_t size); |
106 | static void pcm_play_stopped(void) | 106 | static void pcm_play_pause_int(bool play); |
107 | void pcm_play_stop_int(void); | ||
108 | |||
109 | #ifndef HAVE_SW_VOLUME_CONTROL | ||
110 | /** Standard hw volume control functions - otherwise, see pcm_sw_volume.c **/ | ||
111 | static inline void pcm_play_dma_start_int(const void *addr, size_t size) | ||
112 | { | ||
113 | pcm_play_dma_start(addr, size); | ||
114 | } | ||
115 | |||
116 | static 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 | |||
129 | static inline void pcm_play_dma_stop_int(void) | ||
130 | { | ||
131 | pcm_play_dma_stop(); | ||
132 | } | ||
133 | |||
134 | static inline const void * pcm_play_dma_get_peak_buffer_int(int *count) | ||
135 | { | ||
136 | return pcm_play_dma_get_peak_buffer(count); | ||
137 | } | ||
138 | |||
139 | bool 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 | |||
155 | static 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 | |||
175 | static 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 | |||
185 | void 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 | ||
210 | const void* pcm_get_peak_buffer(int * count) | 290 | const 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 | ||
215 | bool pcm_is_playing(void) | 295 | bool 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 */ | ||
262 | static 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 | |||
296 | void pcm_play_data(pcm_play_callback_type get_more, | 339 | void 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 | ||
313 | bool 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 | |||
342 | void pcm_play_pause(bool play) | 356 | void 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 */ | ||
30 | static const void * volatile src_buf_addr = NULL; | ||
31 | static 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 */ | ||
36 | static int16_t pcm_dbl_buf[2][PCM_PLAY_DBL_BUF_SAMPLES*2] | ||
37 | PCM_DBL_BUF_BSS MEM_ALIGN_ATTR; | ||
38 | static size_t pcm_dbl_buf_size[2]; | ||
39 | static int pcm_dbl_buf_num = 0; | ||
40 | static size_t frame_size; | ||
41 | static unsigned int frame_count, frame_err, frame_frac; | ||
42 | |||
43 | #ifdef AUDIOHW_HAVE_PRESCALER | ||
44 | static int32_t prescale_factor = PCM_FACTOR_UNITY; | ||
45 | static int32_t vol_factor_l = 0, vol_factor_r = 0; | ||
46 | #endif /* AUDIOHW_HAVE_PRESCALER */ | ||
47 | |||
48 | /* pcm scaling factors */ | ||
49 | static 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 */ | ||
58 | static 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 | |||
86 | bool 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 */ | ||
113 | static 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 */ | ||
125 | enum pcm_dma_status | ||
126 | pcm_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 */ | ||
164 | static 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 | |||
178 | void 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 | |||
185 | void 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 | |||
195 | void 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 */ | ||
203 | const 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 */ | ||
220 | static 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 */ | ||
231 | static 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 */ | ||
240 | void 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 */ | ||
247 | void 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 */ | ||
257 | void 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 */ |
29 | const struct sound_settings_info audiohw_settings[] = { | 30 | const 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 | ||
296 | void audiohw_set_volume(int v) | 297 | void 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 | ||
308 | void audiohw_set_frequency(int freq) | 317 | void audiohw_set_frequency(int freq) |