Add access control support to qemu bridge helper
[qemu.git] / hw / lm4549.c
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.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, target_phys_addr_t 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 target_phys_addr_t 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) {
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 };