2 * LM4549 Audio Codec Interface
5 * Written by Mathieu Sonet - www.elasticsheep.com
7 * This code is licensed under the GPL.
9 * *****************************************************************
11 * This driver emulates the LM4549 codec.
13 * It supports only one playback voice and no record voice.
16 #include "qemu/osdep.h"
18 #include "audio/audio.h"
20 #include "migration/vmstate.h"
23 #define LM4549_DEBUG 1
27 #define LM4549_DUMP_DAC_INPUT 1
31 #define DPRINTF(fmt, ...) \
32 do { printf("lm4549: " fmt , ## __VA_ARGS__); } while (0)
34 #define DPRINTF(fmt, ...) do {} while (0)
37 #if defined(LM4549_DUMP_DAC_INPUT)
38 static FILE *fp_dac_input
;
41 /* LM4549 register list */
44 LM4549_Master_Volume
= 0x02,
45 LM4549_Line_Out_Volume
= 0x04,
46 LM4549_Master_Volume_Mono
= 0x06,
47 LM4549_PC_Beep_Volume
= 0x0A,
48 LM4549_Phone_Volume
= 0x0C,
49 LM4549_Mic_Volume
= 0x0E,
50 LM4549_Line_In_Volume
= 0x10,
51 LM4549_CD_Volume
= 0x12,
52 LM4549_Video_Volume
= 0x14,
53 LM4549_Aux_Volume
= 0x16,
54 LM4549_PCM_Out_Volume
= 0x18,
55 LM4549_Record_Select
= 0x1A,
56 LM4549_Record_Gain
= 0x1C,
57 LM4549_General_Purpose
= 0x20,
58 LM4549_3D_Control
= 0x22,
59 LM4549_Powerdown_Ctrl_Stat
= 0x26,
60 LM4549_Ext_Audio_ID
= 0x28,
61 LM4549_Ext_Audio_Stat_Ctrl
= 0x2A,
62 LM4549_PCM_Front_DAC_Rate
= 0x2C,
63 LM4549_PCM_ADC_Rate
= 0x32,
64 LM4549_Vendor_ID1
= 0x7C,
65 LM4549_Vendor_ID2
= 0x7E
68 static void lm4549_reset(lm4549_state
*s
)
70 uint16_t *regfile
= s
->regfile
;
72 regfile
[LM4549_Reset
] = 0x0d50;
73 regfile
[LM4549_Master_Volume
] = 0x8008;
74 regfile
[LM4549_Line_Out_Volume
] = 0x8000;
75 regfile
[LM4549_Master_Volume_Mono
] = 0x8000;
76 regfile
[LM4549_PC_Beep_Volume
] = 0x0000;
77 regfile
[LM4549_Phone_Volume
] = 0x8008;
78 regfile
[LM4549_Mic_Volume
] = 0x8008;
79 regfile
[LM4549_Line_In_Volume
] = 0x8808;
80 regfile
[LM4549_CD_Volume
] = 0x8808;
81 regfile
[LM4549_Video_Volume
] = 0x8808;
82 regfile
[LM4549_Aux_Volume
] = 0x8808;
83 regfile
[LM4549_PCM_Out_Volume
] = 0x8808;
84 regfile
[LM4549_Record_Select
] = 0x0000;
85 regfile
[LM4549_Record_Gain
] = 0x8000;
86 regfile
[LM4549_General_Purpose
] = 0x0000;
87 regfile
[LM4549_3D_Control
] = 0x0101;
88 regfile
[LM4549_Powerdown_Ctrl_Stat
] = 0x000f;
89 regfile
[LM4549_Ext_Audio_ID
] = 0x0001;
90 regfile
[LM4549_Ext_Audio_Stat_Ctrl
] = 0x0000;
91 regfile
[LM4549_PCM_Front_DAC_Rate
] = 0xbb80;
92 regfile
[LM4549_PCM_ADC_Rate
] = 0xbb80;
93 regfile
[LM4549_Vendor_ID1
] = 0x4e53;
94 regfile
[LM4549_Vendor_ID2
] = 0x4331;
97 static void lm4549_audio_transfer(lm4549_state
*s
)
99 uint32_t written_bytes
, written_samples
;
102 /* Activate the voice */
103 AUD_set_active_out(s
->voice
, 1);
104 s
->voice_is_active
= 1;
106 /* Try to write the buffer content */
107 written_bytes
= AUD_write(s
->voice
, s
->buffer
,
108 s
->buffer_level
* sizeof(uint16_t));
109 written_samples
= written_bytes
>> 1;
111 #if defined(LM4549_DUMP_DAC_INPUT)
112 fwrite(s
->buffer
, sizeof(uint8_t), written_bytes
, fp_dac_input
);
115 s
->buffer_level
-= written_samples
;
117 if (s
->buffer_level
> 0) {
118 /* Move the data back to the start of the buffer */
119 for (i
= 0; i
< s
->buffer_level
; i
++) {
120 s
->buffer
[i
] = s
->buffer
[i
+ written_samples
];
125 static void lm4549_audio_out_callback(void *opaque
, int free
)
127 lm4549_state
*s
= (lm4549_state
*)opaque
;
128 static uint32_t prev_buffer_level
;
131 int size
= AUD_get_buffer_size_out(s
->voice
);
132 DPRINTF("audio_out_callback size = %i free = %i\n", size
, free
);
135 /* Detect that no data are consumed
136 => disable the voice */
137 if (s
->buffer_level
== prev_buffer_level
) {
138 AUD_set_active_out(s
->voice
, 0);
139 s
->voice_is_active
= 0;
141 prev_buffer_level
= s
->buffer_level
;
143 /* Check if a buffer transfer is pending */
144 if (s
->buffer_level
== LM4549_BUFFER_SIZE
) {
145 lm4549_audio_transfer(s
);
147 /* Request more data */
148 if (s
->data_req_cb
!= NULL
) {
149 (s
->data_req_cb
)(s
->opaque
);
154 uint32_t lm4549_read(lm4549_state
*s
, hwaddr offset
)
156 uint16_t *regfile
= s
->regfile
;
159 /* Read the stored value */
160 assert(offset
< 128);
161 value
= regfile
[offset
];
163 DPRINTF("read [0x%02x] = 0x%04x\n", offset
, value
);
168 void lm4549_write(lm4549_state
*s
,
169 hwaddr offset
, uint32_t value
)
171 uint16_t *regfile
= s
->regfile
;
173 assert(offset
< 128);
174 DPRINTF("write [0x%02x] = 0x%04x\n", offset
, value
);
181 case LM4549_PCM_Front_DAC_Rate
:
182 regfile
[LM4549_PCM_Front_DAC_Rate
] = value
;
183 DPRINTF("DAC rate change = %i\n", value
);
185 /* Re-open a voice with the new sample rate */
186 struct audsettings as
;
189 as
.fmt
= AUDIO_FORMAT_S16
;
192 s
->voice
= AUD_open_out(
197 lm4549_audio_out_callback
,
202 case LM4549_Powerdown_Ctrl_Stat
:
204 value
|= regfile
[LM4549_Powerdown_Ctrl_Stat
] & 0xf;
205 regfile
[LM4549_Powerdown_Ctrl_Stat
] = value
;
208 case LM4549_Ext_Audio_ID
:
209 case LM4549_Vendor_ID1
:
210 case LM4549_Vendor_ID2
:
211 DPRINTF("Write to read-only register 0x%x\n", (int)offset
);
215 /* Store the new value */
216 regfile
[offset
] = value
;
221 uint32_t lm4549_write_samples(lm4549_state
*s
, uint32_t left
, uint32_t right
)
223 /* The left and right samples are in 20-bit resolution.
224 The LM4549 has 18-bit resolution and only uses the bits [19:2].
225 This model supports 16-bit playback.
228 if (s
->buffer_level
> LM4549_BUFFER_SIZE
- 2) {
229 DPRINTF("write_sample Buffer full\n");
233 /* Store 16-bit samples in the buffer */
234 s
->buffer
[s
->buffer_level
++] = (left
>> 4);
235 s
->buffer
[s
->buffer_level
++] = (right
>> 4);
237 if (s
->buffer_level
== LM4549_BUFFER_SIZE
) {
238 /* Trigger the transfer of the buffer to the audio host */
239 lm4549_audio_transfer(s
);
245 static int lm4549_post_load(void *opaque
, int version_id
)
247 lm4549_state
*s
= (lm4549_state
*)opaque
;
248 uint16_t *regfile
= s
->regfile
;
250 /* Re-open a voice with the current sample rate */
251 uint32_t freq
= regfile
[LM4549_PCM_Front_DAC_Rate
];
253 DPRINTF("post_load freq = %i\n", freq
);
254 DPRINTF("post_load voice_is_active = %i\n", s
->voice_is_active
);
256 struct audsettings as
;
259 as
.fmt
= AUDIO_FORMAT_S16
;
262 s
->voice
= AUD_open_out(
267 lm4549_audio_out_callback
,
272 if (s
->voice_is_active
== 1) {
273 lm4549_audio_out_callback(s
, AUD_get_buffer_size_out(s
->voice
));
279 void lm4549_init(lm4549_state
*s
, lm4549_callback data_req_cb
, void* opaque
,
282 struct audsettings as
;
284 /* Register an audio card */
285 if (!AUD_register_card("lm4549", &s
->card
, errp
)) {
289 /* Store the callback and opaque pointer */
290 s
->data_req_cb
= data_req_cb
;
293 /* Init the registers */
296 /* Open a default voice */
299 as
.fmt
= AUDIO_FORMAT_S16
;
302 s
->voice
= AUD_open_out(
307 lm4549_audio_out_callback
,
311 AUD_set_volume_out(s
->voice
, 0, 255, 255);
313 s
->voice_is_active
= 0;
315 /* Reset the input buffer */
316 memset(s
->buffer
, 0x00, sizeof(s
->buffer
));
319 #if defined(LM4549_DUMP_DAC_INPUT)
320 fp_dac_input
= fopen("lm4549_dac_input.pcm", "wb");
322 hw_error("Unable to open lm4549_dac_input.pcm for writing\n");
327 const VMStateDescription vmstate_lm4549_state
= {
328 .name
= "lm4549_state",
330 .minimum_version_id
= 1,
331 .post_load
= lm4549_post_load
,
332 .fields
= (VMStateField
[]) {
333 VMSTATE_UINT32(voice_is_active
, lm4549_state
),
334 VMSTATE_UINT16_ARRAY(regfile
, lm4549_state
, 128),
335 VMSTATE_UINT16_ARRAY(buffer
, lm4549_state
, LM4549_BUFFER_SIZE
),
336 VMSTATE_UINT32(buffer_level
, lm4549_state
),
337 VMSTATE_END_OF_LIST()