|
|
1.1 root 1: /*
2: * LM4549 Audio Codec Interface
3: *
4: * Copyright (c) 2011
5: * Written by Mathieu Sonet - www.elasticsheep.com
6: *
1.1.1.2 ! root 7: * This code is licensed under the GPL.
1.1 root 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: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.