2 * Marvell 88w8618 audio emulation extracted from
3 * Marvell MV88w8618 / Freecom MusicPal emulation.
5 * Copyright (c) 2008 Jan Kiszka
7 * This code is licensed under the GNU GPL v2.
9 * Contributions after 2012-01-13 are licensed under the terms of the
10 * GNU GPL, version 2 or (at your option) any later version.
16 #include "audio/audio.h"
18 #define MP_AUDIO_SIZE 0x00001000
20 /* Audio register offsets */
21 #define MP_AUDIO_PLAYBACK_MODE 0x00
22 #define MP_AUDIO_CLOCK_DIV 0x18
23 #define MP_AUDIO_IRQ_STATUS 0x20
24 #define MP_AUDIO_IRQ_ENABLE 0x24
25 #define MP_AUDIO_TX_START_LO 0x28
26 #define MP_AUDIO_TX_THRESHOLD 0x2C
27 #define MP_AUDIO_TX_STATUS 0x38
28 #define MP_AUDIO_TX_START_HI 0x40
30 /* Status register and IRQ enable bits */
31 #define MP_AUDIO_TX_HALF (1 << 6)
32 #define MP_AUDIO_TX_FULL (1 << 7)
34 /* Playback mode bits */
35 #define MP_AUDIO_16BIT_SAMPLE (1 << 0)
36 #define MP_AUDIO_PLAYBACK_EN (1 << 7)
37 #define MP_AUDIO_CLOCK_24MHZ (1 << 9)
38 #define MP_AUDIO_MONO (1 << 14)
40 typedef struct mv88w8618_audio_state
{
44 uint32_t playback_mode
;
48 uint32_t target_buffer
;
54 } mv88w8618_audio_state
;
56 static void mv88w8618_audio_callback(void *opaque
, int free_out
, int free_in
)
58 mv88w8618_audio_state
*s
= opaque
;
59 int16_t *codec_buffer
;
64 if (!(s
->playback_mode
& MP_AUDIO_PLAYBACK_EN
)) {
67 if (s
->playback_mode
& MP_AUDIO_16BIT_SAMPLE
) {
70 if (!(s
->playback_mode
& MP_AUDIO_MONO
)) {
73 block_size
= s
->threshold
/ 2;
74 if (free_out
- s
->last_free
< block_size
) {
77 if (block_size
> 4096) {
80 cpu_physical_memory_read(s
->target_buffer
+ s
->play_pos
, (void *)buf
,
83 if (s
->playback_mode
& MP_AUDIO_16BIT_SAMPLE
) {
84 if (s
->playback_mode
& MP_AUDIO_MONO
) {
85 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
>> 1);
86 for (pos
= 0; pos
< block_size
; pos
+= 2) {
87 *codec_buffer
++ = *(int16_t *)mem_buffer
;
88 *codec_buffer
++ = *(int16_t *)mem_buffer
;
92 memcpy(wm8750_dac_buffer(s
->wm
, block_size
>> 2),
93 (uint32_t *)mem_buffer
, block_size
);
96 if (s
->playback_mode
& MP_AUDIO_MONO
) {
97 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
);
98 for (pos
= 0; pos
< block_size
; pos
++) {
99 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
);
100 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
103 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
>> 1);
104 for (pos
= 0; pos
< block_size
; pos
+= 2) {
105 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
106 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
110 wm8750_dac_commit(s
->wm
);
112 s
->last_free
= free_out
- block_size
;
114 if (s
->play_pos
== 0) {
115 s
->status
|= MP_AUDIO_TX_HALF
;
116 s
->play_pos
= block_size
;
118 s
->status
|= MP_AUDIO_TX_FULL
;
122 if (s
->status
& s
->irq_enable
) {
123 qemu_irq_raise(s
->irq
);
127 static void mv88w8618_audio_clock_update(mv88w8618_audio_state
*s
)
131 if (s
->playback_mode
& MP_AUDIO_CLOCK_24MHZ
) {
132 rate
= 24576000 / 64; /* 24.576MHz */
134 rate
= 11289600 / 64; /* 11.2896MHz */
136 rate
/= ((s
->clock_div
>> 8) & 0xff) + 1;
138 wm8750_set_bclk_in(s
->wm
, rate
);
141 static uint64_t mv88w8618_audio_read(void *opaque
, hwaddr offset
,
144 mv88w8618_audio_state
*s
= opaque
;
147 case MP_AUDIO_PLAYBACK_MODE
:
148 return s
->playback_mode
;
150 case MP_AUDIO_CLOCK_DIV
:
153 case MP_AUDIO_IRQ_STATUS
:
156 case MP_AUDIO_IRQ_ENABLE
:
157 return s
->irq_enable
;
159 case MP_AUDIO_TX_STATUS
:
160 return s
->play_pos
>> 2;
167 static void mv88w8618_audio_write(void *opaque
, hwaddr offset
,
168 uint64_t value
, unsigned size
)
170 mv88w8618_audio_state
*s
= opaque
;
173 case MP_AUDIO_PLAYBACK_MODE
:
174 if (value
& MP_AUDIO_PLAYBACK_EN
&&
175 !(s
->playback_mode
& MP_AUDIO_PLAYBACK_EN
)) {
180 s
->playback_mode
= value
;
181 mv88w8618_audio_clock_update(s
);
184 case MP_AUDIO_CLOCK_DIV
:
185 s
->clock_div
= value
;
188 mv88w8618_audio_clock_update(s
);
191 case MP_AUDIO_IRQ_STATUS
:
195 case MP_AUDIO_IRQ_ENABLE
:
196 s
->irq_enable
= value
;
197 if (s
->status
& s
->irq_enable
) {
198 qemu_irq_raise(s
->irq
);
202 case MP_AUDIO_TX_START_LO
:
203 s
->phys_buf
= (s
->phys_buf
& 0xFFFF0000) | (value
& 0xFFFF);
204 s
->target_buffer
= s
->phys_buf
;
209 case MP_AUDIO_TX_THRESHOLD
:
210 s
->threshold
= (value
+ 1) * 4;
213 case MP_AUDIO_TX_START_HI
:
214 s
->phys_buf
= (s
->phys_buf
& 0xFFFF) | (value
<< 16);
215 s
->target_buffer
= s
->phys_buf
;
222 static void mv88w8618_audio_reset(DeviceState
*d
)
224 mv88w8618_audio_state
*s
= FROM_SYSBUS(mv88w8618_audio_state
,
225 sysbus_from_qdev(d
));
227 s
->playback_mode
= 0;
235 static const MemoryRegionOps mv88w8618_audio_ops
= {
236 .read
= mv88w8618_audio_read
,
237 .write
= mv88w8618_audio_write
,
238 .endianness
= DEVICE_NATIVE_ENDIAN
,
241 static int mv88w8618_audio_init(SysBusDevice
*dev
)
243 mv88w8618_audio_state
*s
= FROM_SYSBUS(mv88w8618_audio_state
, dev
);
245 sysbus_init_irq(dev
, &s
->irq
);
247 wm8750_data_req_set(s
->wm
, mv88w8618_audio_callback
, s
);
249 memory_region_init_io(&s
->iomem
, &mv88w8618_audio_ops
, s
,
250 "audio", MP_AUDIO_SIZE
);
251 sysbus_init_mmio(dev
, &s
->iomem
);
256 static const VMStateDescription mv88w8618_audio_vmsd
= {
257 .name
= "mv88w8618_audio",
259 .minimum_version_id
= 1,
260 .minimum_version_id_old
= 1,
261 .fields
= (VMStateField
[]) {
262 VMSTATE_UINT32(playback_mode
, mv88w8618_audio_state
),
263 VMSTATE_UINT32(status
, mv88w8618_audio_state
),
264 VMSTATE_UINT32(irq_enable
, mv88w8618_audio_state
),
265 VMSTATE_UINT32(phys_buf
, mv88w8618_audio_state
),
266 VMSTATE_UINT32(target_buffer
, mv88w8618_audio_state
),
267 VMSTATE_UINT32(threshold
, mv88w8618_audio_state
),
268 VMSTATE_UINT32(play_pos
, mv88w8618_audio_state
),
269 VMSTATE_UINT32(last_free
, mv88w8618_audio_state
),
270 VMSTATE_UINT32(clock_div
, mv88w8618_audio_state
),
271 VMSTATE_END_OF_LIST()
275 static Property mv88w8618_audio_properties
[] = {
276 DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state
, wm
),
280 static void mv88w8618_audio_class_init(ObjectClass
*klass
, void *data
)
282 DeviceClass
*dc
= DEVICE_CLASS(klass
);
283 SysBusDeviceClass
*k
= SYS_BUS_DEVICE_CLASS(klass
);
285 k
->init
= mv88w8618_audio_init
;
286 dc
->reset
= mv88w8618_audio_reset
;
287 dc
->vmsd
= &mv88w8618_audio_vmsd
;
288 dc
->props
= mv88w8618_audio_properties
;
291 static TypeInfo mv88w8618_audio_info
= {
292 .name
= "mv88w8618_audio",
293 .parent
= TYPE_SYS_BUS_DEVICE
,
294 .instance_size
= sizeof(mv88w8618_audio_state
),
295 .class_init
= mv88w8618_audio_class_init
,
298 static void mv88w8618_register_types(void)
300 type_register_static(&mv88w8618_audio_info
);
303 type_init(mv88w8618_register_types
)