root / hw / audio / lm4549.c @ 5a37532d
History | View | Annotate | Download (9 kB)
1 |
/*
|
---|---|
2 |
* LM4549 Audio Codec Interface
|
3 |
*
|
4 |
* Copyright (c) 2011
|
5 |
* Written by Mathieu Sonet - www.elasticsheep.com
|
6 |
*
|
7 |
* This code is licensed under the GPL.
|
8 |
*
|
9 |
* *****************************************************************
|
10 |
*
|
11 |
* This driver emulates the LM4549 codec.
|
12 |
*
|
13 |
* It supports only one playback voice and no record voice.
|
14 |
*/
|
15 |
|
16 |
#include "hw/hw.h" |
17 |
#include "audio/audio.h" |
18 |
#include "lm4549.h" |
19 |
|
20 |
#if 0
|
21 |
#define LM4549_DEBUG 1
|
22 |
#endif
|
23 |
|
24 |
#if 0
|
25 |
#define LM4549_DUMP_DAC_INPUT 1
|
26 |
#endif
|
27 |
|
28 |
#ifdef LM4549_DEBUG
|
29 |
#define DPRINTF(fmt, ...) \
|
30 |
do { printf("lm4549: " fmt , ## __VA_ARGS__); } while (0) |
31 |
#else
|
32 |
#define DPRINTF(fmt, ...) do {} while (0) |
33 |
#endif
|
34 |
|
35 |
#if defined(LM4549_DUMP_DAC_INPUT)
|
36 |
#include <stdio.h> |
37 |
static FILE *fp_dac_input;
|
38 |
#endif
|
39 |
|
40 |
/* LM4549 register list */
|
41 |
enum {
|
42 |
LM4549_Reset = 0x00,
|
43 |
LM4549_Master_Volume = 0x02,
|
44 |
LM4549_Line_Out_Volume = 0x04,
|
45 |
LM4549_Master_Volume_Mono = 0x06,
|
46 |
LM4549_PC_Beep_Volume = 0x0A,
|
47 |
LM4549_Phone_Volume = 0x0C,
|
48 |
LM4549_Mic_Volume = 0x0E,
|
49 |
LM4549_Line_In_Volume = 0x10,
|
50 |
LM4549_CD_Volume = 0x12,
|
51 |
LM4549_Video_Volume = 0x14,
|
52 |
LM4549_Aux_Volume = 0x16,
|
53 |
LM4549_PCM_Out_Volume = 0x18,
|
54 |
LM4549_Record_Select = 0x1A,
|
55 |
LM4549_Record_Gain = 0x1C,
|
56 |
LM4549_General_Purpose = 0x20,
|
57 |
LM4549_3D_Control = 0x22,
|
58 |
LM4549_Powerdown_Ctrl_Stat = 0x26,
|
59 |
LM4549_Ext_Audio_ID = 0x28,
|
60 |
LM4549_Ext_Audio_Stat_Ctrl = 0x2A,
|
61 |
LM4549_PCM_Front_DAC_Rate = 0x2C,
|
62 |
LM4549_PCM_ADC_Rate = 0x32,
|
63 |
LM4549_Vendor_ID1 = 0x7C,
|
64 |
LM4549_Vendor_ID2 = 0x7E
|
65 |
}; |
66 |
|
67 |
static void lm4549_reset(lm4549_state *s) |
68 |
{ |
69 |
uint16_t *regfile = s->regfile; |
70 |
|
71 |
regfile[LM4549_Reset] = 0x0d50;
|
72 |
regfile[LM4549_Master_Volume] = 0x8008;
|
73 |
regfile[LM4549_Line_Out_Volume] = 0x8000;
|
74 |
regfile[LM4549_Master_Volume_Mono] = 0x8000;
|
75 |
regfile[LM4549_PC_Beep_Volume] = 0x0000;
|
76 |
regfile[LM4549_Phone_Volume] = 0x8008;
|
77 |
regfile[LM4549_Mic_Volume] = 0x8008;
|
78 |
regfile[LM4549_Line_In_Volume] = 0x8808;
|
79 |
regfile[LM4549_CD_Volume] = 0x8808;
|
80 |
regfile[LM4549_Video_Volume] = 0x8808;
|
81 |
regfile[LM4549_Aux_Volume] = 0x8808;
|
82 |
regfile[LM4549_PCM_Out_Volume] = 0x8808;
|
83 |
regfile[LM4549_Record_Select] = 0x0000;
|
84 |
regfile[LM4549_Record_Gain] = 0x8000;
|
85 |
regfile[LM4549_General_Purpose] = 0x0000;
|
86 |
regfile[LM4549_3D_Control] = 0x0101;
|
87 |
regfile[LM4549_Powerdown_Ctrl_Stat] = 0x000f;
|
88 |
regfile[LM4549_Ext_Audio_ID] = 0x0001;
|
89 |
regfile[LM4549_Ext_Audio_Stat_Ctrl] = 0x0000;
|
90 |
regfile[LM4549_PCM_Front_DAC_Rate] = 0xbb80;
|
91 |
regfile[LM4549_PCM_ADC_Rate] = 0xbb80;
|
92 |
regfile[LM4549_Vendor_ID1] = 0x4e53;
|
93 |
regfile[LM4549_Vendor_ID2] = 0x4331;
|
94 |
} |
95 |
|
96 |
static void lm4549_audio_transfer(lm4549_state *s) |
97 |
{ |
98 |
uint32_t written_bytes, written_samples; |
99 |
uint32_t i; |
100 |
|
101 |
/* Activate the voice */
|
102 |
AUD_set_active_out(s->voice, 1);
|
103 |
s->voice_is_active = 1;
|
104 |
|
105 |
/* Try to write the buffer content */
|
106 |
written_bytes = AUD_write(s->voice, s->buffer, |
107 |
s->buffer_level * sizeof(uint16_t));
|
108 |
written_samples = written_bytes >> 1;
|
109 |
|
110 |
#if defined(LM4549_DUMP_DAC_INPUT)
|
111 |
fwrite(s->buffer, sizeof(uint8_t), written_bytes, fp_dac_input);
|
112 |
#endif
|
113 |
|
114 |
s->buffer_level -= written_samples; |
115 |
|
116 |
if (s->buffer_level > 0) { |
117 |
/* Move the data back to the start of the buffer */
|
118 |
for (i = 0; i < s->buffer_level; i++) { |
119 |
s->buffer[i] = s->buffer[i + written_samples]; |
120 |
} |
121 |
} |
122 |
} |
123 |
|
124 |
static void lm4549_audio_out_callback(void *opaque, int free) |
125 |
{ |
126 |
lm4549_state *s = (lm4549_state *)opaque; |
127 |
static uint32_t prev_buffer_level;
|
128 |
|
129 |
#ifdef LM4549_DEBUG
|
130 |
int size = AUD_get_buffer_size_out(s->voice);
|
131 |
DPRINTF("audio_out_callback size = %i free = %i\n", size, free);
|
132 |
#endif
|
133 |
|
134 |
/* Detect that no data are consumed
|
135 |
=> disable the voice */
|
136 |
if (s->buffer_level == prev_buffer_level) {
|
137 |
AUD_set_active_out(s->voice, 0);
|
138 |
s->voice_is_active = 0;
|
139 |
} |
140 |
prev_buffer_level = s->buffer_level; |
141 |
|
142 |
/* Check if a buffer transfer is pending */
|
143 |
if (s->buffer_level == LM4549_BUFFER_SIZE) {
|
144 |
lm4549_audio_transfer(s); |
145 |
|
146 |
/* Request more data */
|
147 |
if (s->data_req_cb != NULL) { |
148 |
(s->data_req_cb)(s->opaque); |
149 |
} |
150 |
} |
151 |
} |
152 |
|
153 |
uint32_t lm4549_read(lm4549_state *s, hwaddr offset) |
154 |
{ |
155 |
uint16_t *regfile = s->regfile; |
156 |
uint32_t value = 0;
|
157 |
|
158 |
/* Read the stored value */
|
159 |
assert(offset < 128);
|
160 |
value = regfile[offset]; |
161 |
|
162 |
DPRINTF("read [0x%02x] = 0x%04x\n", offset, value);
|
163 |
|
164 |
return value;
|
165 |
} |
166 |
|
167 |
void lm4549_write(lm4549_state *s,
|
168 |
hwaddr offset, uint32_t value) |
169 |
{ |
170 |
uint16_t *regfile = s->regfile; |
171 |
|
172 |
assert(offset < 128);
|
173 |
DPRINTF("write [0x%02x] = 0x%04x\n", offset, value);
|
174 |
|
175 |
switch (offset) {
|
176 |
case LM4549_Reset:
|
177 |
lm4549_reset(s); |
178 |
break;
|
179 |
|
180 |
case LM4549_PCM_Front_DAC_Rate:
|
181 |
regfile[LM4549_PCM_Front_DAC_Rate] = value; |
182 |
DPRINTF("DAC rate change = %i\n", value);
|
183 |
|
184 |
/* Re-open a voice with the new sample rate */
|
185 |
struct audsettings as;
|
186 |
as.freq = value; |
187 |
as.nchannels = 2;
|
188 |
as.fmt = AUD_FMT_S16; |
189 |
as.endianness = 0;
|
190 |
|
191 |
s->voice = AUD_open_out( |
192 |
&s->card, |
193 |
s->voice, |
194 |
"lm4549.out",
|
195 |
s, |
196 |
lm4549_audio_out_callback, |
197 |
&as |
198 |
); |
199 |
break;
|
200 |
|
201 |
case LM4549_Powerdown_Ctrl_Stat:
|
202 |
value &= ~0xf;
|
203 |
value |= regfile[LM4549_Powerdown_Ctrl_Stat] & 0xf;
|
204 |
regfile[LM4549_Powerdown_Ctrl_Stat] = value; |
205 |
break;
|
206 |
|
207 |
case LM4549_Ext_Audio_ID:
|
208 |
case LM4549_Vendor_ID1:
|
209 |
case LM4549_Vendor_ID2:
|
210 |
DPRINTF("Write to read-only register 0x%x\n", (int)offset); |
211 |
break;
|
212 |
|
213 |
default:
|
214 |
/* Store the new value */
|
215 |
regfile[offset] = value; |
216 |
break;
|
217 |
} |
218 |
} |
219 |
|
220 |
uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right) |
221 |
{ |
222 |
/* The left and right samples are in 20-bit resolution.
|
223 |
The LM4549 has 18-bit resolution and only uses the bits [19:2].
|
224 |
This model supports 16-bit playback.
|
225 |
*/
|
226 |
|
227 |
if (s->buffer_level > LM4549_BUFFER_SIZE - 2) { |
228 |
DPRINTF("write_sample Buffer full\n");
|
229 |
return 0; |
230 |
} |
231 |
|
232 |
/* Store 16-bit samples in the buffer */
|
233 |
s->buffer[s->buffer_level++] = (left >> 4);
|
234 |
s->buffer[s->buffer_level++] = (right >> 4);
|
235 |
|
236 |
if (s->buffer_level == LM4549_BUFFER_SIZE) {
|
237 |
/* Trigger the transfer of the buffer to the audio host */
|
238 |
lm4549_audio_transfer(s); |
239 |
} |
240 |
|
241 |
return 1; |
242 |
} |
243 |
|
244 |
static int lm4549_post_load(void *opaque, int version_id) |
245 |
{ |
246 |
lm4549_state *s = (lm4549_state *)opaque; |
247 |
uint16_t *regfile = s->regfile; |
248 |
|
249 |
/* Re-open a voice with the current sample rate */
|
250 |
uint32_t freq = regfile[LM4549_PCM_Front_DAC_Rate]; |
251 |
|
252 |
DPRINTF("post_load freq = %i\n", freq);
|
253 |
DPRINTF("post_load voice_is_active = %i\n", s->voice_is_active);
|
254 |
|
255 |
struct audsettings as;
|
256 |
as.freq = freq; |
257 |
as.nchannels = 2;
|
258 |
as.fmt = AUD_FMT_S16; |
259 |
as.endianness = 0;
|
260 |
|
261 |
s->voice = AUD_open_out( |
262 |
&s->card, |
263 |
s->voice, |
264 |
"lm4549.out",
|
265 |
s, |
266 |
lm4549_audio_out_callback, |
267 |
&as |
268 |
); |
269 |
|
270 |
/* Request data */
|
271 |
if (s->voice_is_active == 1) { |
272 |
lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->voice)); |
273 |
} |
274 |
|
275 |
return 0; |
276 |
} |
277 |
|
278 |
void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque) |
279 |
{ |
280 |
struct audsettings as;
|
281 |
|
282 |
/* Store the callback and opaque pointer */
|
283 |
s->data_req_cb = data_req_cb; |
284 |
s->opaque = opaque; |
285 |
|
286 |
/* Init the registers */
|
287 |
lm4549_reset(s); |
288 |
|
289 |
/* Register an audio card */
|
290 |
AUD_register_card("lm4549", &s->card);
|
291 |
|
292 |
/* Open a default voice */
|
293 |
as.freq = 48000;
|
294 |
as.nchannels = 2;
|
295 |
as.fmt = AUD_FMT_S16; |
296 |
as.endianness = 0;
|
297 |
|
298 |
s->voice = AUD_open_out( |
299 |
&s->card, |
300 |
s->voice, |
301 |
"lm4549.out",
|
302 |
s, |
303 |
lm4549_audio_out_callback, |
304 |
&as |
305 |
); |
306 |
|
307 |
AUD_set_volume_out(s->voice, 0, 255, 255); |
308 |
|
309 |
s->voice_is_active = 0;
|
310 |
|
311 |
/* Reset the input buffer */
|
312 |
memset(s->buffer, 0x00, sizeof(s->buffer)); |
313 |
s->buffer_level = 0;
|
314 |
|
315 |
#if defined(LM4549_DUMP_DAC_INPUT)
|
316 |
fp_dac_input = fopen("lm4549_dac_input.pcm", "wb"); |
317 |
if (!fp_dac_input) {
|
318 |
hw_error("Unable to open lm4549_dac_input.pcm for writing\n");
|
319 |
} |
320 |
#endif
|
321 |
} |
322 |
|
323 |
const VMStateDescription vmstate_lm4549_state = {
|
324 |
.name = "lm4549_state",
|
325 |
.version_id = 1,
|
326 |
.minimum_version_id = 1,
|
327 |
.minimum_version_id_old = 1,
|
328 |
.post_load = &lm4549_post_load, |
329 |
.fields = (VMStateField[]) { |
330 |
VMSTATE_UINT32(voice_is_active, lm4549_state), |
331 |
VMSTATE_UINT16_ARRAY(regfile, lm4549_state, 128),
|
332 |
VMSTATE_UINT16_ARRAY(buffer, lm4549_state, LM4549_BUFFER_SIZE), |
333 |
VMSTATE_UINT32(buffer_level, lm4549_state), |
334 |
VMSTATE_END_OF_LIST() |
335 |
} |
336 |
}; |