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.
13 #include "qemu/osdep.h"
14 #include "hw/sysbus.h"
15 #include "migration/vmstate.h"
17 #include "hw/qdev-properties.h"
18 #include "hw/audio/wm8750.h"
19 #include "audio/audio.h"
20 #include "qapi/error.h"
21 #include "qemu/module.h"
22 #include "qom/object.h"
24 #define MP_AUDIO_SIZE 0x00001000
26 /* Audio register offsets */
27 #define MP_AUDIO_PLAYBACK_MODE 0x00
28 #define MP_AUDIO_CLOCK_DIV 0x18
29 #define MP_AUDIO_IRQ_STATUS 0x20
30 #define MP_AUDIO_IRQ_ENABLE 0x24
31 #define MP_AUDIO_TX_START_LO 0x28
32 #define MP_AUDIO_TX_THRESHOLD 0x2C
33 #define MP_AUDIO_TX_STATUS 0x38
34 #define MP_AUDIO_TX_START_HI 0x40
36 /* Status register and IRQ enable bits */
37 #define MP_AUDIO_TX_HALF (1 << 6)
38 #define MP_AUDIO_TX_FULL (1 << 7)
40 /* Playback mode bits */
41 #define MP_AUDIO_16BIT_SAMPLE (1 << 0)
42 #define MP_AUDIO_PLAYBACK_EN (1 << 7)
43 #define MP_AUDIO_CLOCK_24MHZ (1 << 9)
44 #define MP_AUDIO_MONO (1 << 14)
46 OBJECT_DECLARE_SIMPLE_TYPE(mv88w8618_audio_state
, MV88W8618_AUDIO
)
48 struct mv88w8618_audio_state
{
49 SysBusDevice parent_obj
;
53 uint32_t playback_mode
;
57 uint32_t target_buffer
;
65 static void mv88w8618_audio_callback(void *opaque
, int free_out
, int free_in
)
67 mv88w8618_audio_state
*s
= opaque
;
68 int16_t *codec_buffer
;
73 if (!(s
->playback_mode
& MP_AUDIO_PLAYBACK_EN
)) {
76 if (s
->playback_mode
& MP_AUDIO_16BIT_SAMPLE
) {
79 if (!(s
->playback_mode
& MP_AUDIO_MONO
)) {
82 block_size
= s
->threshold
/ 2;
83 if (free_out
- s
->last_free
< block_size
) {
86 if (block_size
> 4096) {
89 cpu_physical_memory_read(s
->target_buffer
+ s
->play_pos
, buf
, block_size
);
91 if (s
->playback_mode
& MP_AUDIO_16BIT_SAMPLE
) {
92 if (s
->playback_mode
& MP_AUDIO_MONO
) {
93 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
>> 1);
94 for (pos
= 0; pos
< block_size
; pos
+= 2) {
95 *codec_buffer
++ = *(int16_t *)mem_buffer
;
96 *codec_buffer
++ = *(int16_t *)mem_buffer
;
100 memcpy(wm8750_dac_buffer(s
->wm
, block_size
>> 2),
101 (uint32_t *)mem_buffer
, block_size
);
104 if (s
->playback_mode
& MP_AUDIO_MONO
) {
105 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
);
106 for (pos
= 0; pos
< block_size
; pos
++) {
107 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
);
108 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
111 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
>> 1);
112 for (pos
= 0; pos
< block_size
; pos
+= 2) {
113 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
114 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
118 wm8750_dac_commit(s
->wm
);
120 s
->last_free
= free_out
- block_size
;
122 if (s
->play_pos
== 0) {
123 s
->status
|= MP_AUDIO_TX_HALF
;
124 s
->play_pos
= block_size
;
126 s
->status
|= MP_AUDIO_TX_FULL
;
130 if (s
->status
& s
->irq_enable
) {
131 qemu_irq_raise(s
->irq
);
135 static void mv88w8618_audio_clock_update(mv88w8618_audio_state
*s
)
139 if (s
->playback_mode
& MP_AUDIO_CLOCK_24MHZ
) {
140 rate
= 24576000 / 64; /* 24.576MHz */
142 rate
= 11289600 / 64; /* 11.2896MHz */
144 rate
/= ((s
->clock_div
>> 8) & 0xff) + 1;
146 wm8750_set_bclk_in(s
->wm
, rate
);
149 static uint64_t mv88w8618_audio_read(void *opaque
, hwaddr offset
,
152 mv88w8618_audio_state
*s
= opaque
;
155 case MP_AUDIO_PLAYBACK_MODE
:
156 return s
->playback_mode
;
158 case MP_AUDIO_CLOCK_DIV
:
161 case MP_AUDIO_IRQ_STATUS
:
164 case MP_AUDIO_IRQ_ENABLE
:
165 return s
->irq_enable
;
167 case MP_AUDIO_TX_STATUS
:
168 return s
->play_pos
>> 2;
175 static void mv88w8618_audio_write(void *opaque
, hwaddr offset
,
176 uint64_t value
, unsigned size
)
178 mv88w8618_audio_state
*s
= opaque
;
181 case MP_AUDIO_PLAYBACK_MODE
:
182 if (value
& MP_AUDIO_PLAYBACK_EN
&&
183 !(s
->playback_mode
& MP_AUDIO_PLAYBACK_EN
)) {
188 s
->playback_mode
= value
;
189 mv88w8618_audio_clock_update(s
);
192 case MP_AUDIO_CLOCK_DIV
:
193 s
->clock_div
= value
;
196 mv88w8618_audio_clock_update(s
);
199 case MP_AUDIO_IRQ_STATUS
:
203 case MP_AUDIO_IRQ_ENABLE
:
204 s
->irq_enable
= value
;
205 if (s
->status
& s
->irq_enable
) {
206 qemu_irq_raise(s
->irq
);
210 case MP_AUDIO_TX_START_LO
:
211 s
->phys_buf
= (s
->phys_buf
& 0xFFFF0000) | (value
& 0xFFFF);
212 s
->target_buffer
= s
->phys_buf
;
217 case MP_AUDIO_TX_THRESHOLD
:
218 s
->threshold
= (value
+ 1) * 4;
221 case MP_AUDIO_TX_START_HI
:
222 s
->phys_buf
= (s
->phys_buf
& 0xFFFF) | (value
<< 16);
223 s
->target_buffer
= s
->phys_buf
;
230 static void mv88w8618_audio_reset(DeviceState
*d
)
232 mv88w8618_audio_state
*s
= MV88W8618_AUDIO(d
);
234 s
->playback_mode
= 0;
242 static const MemoryRegionOps mv88w8618_audio_ops
= {
243 .read
= mv88w8618_audio_read
,
244 .write
= mv88w8618_audio_write
,
245 .endianness
= DEVICE_NATIVE_ENDIAN
,
248 static void mv88w8618_audio_init(Object
*obj
)
250 SysBusDevice
*dev
= SYS_BUS_DEVICE(obj
);
251 mv88w8618_audio_state
*s
= MV88W8618_AUDIO(dev
);
253 sysbus_init_irq(dev
, &s
->irq
);
255 memory_region_init_io(&s
->iomem
, obj
, &mv88w8618_audio_ops
, s
,
256 "audio", MP_AUDIO_SIZE
);
257 sysbus_init_mmio(dev
, &s
->iomem
);
259 object_property_add_link(OBJECT(dev
), "wm8750", TYPE_WM8750
,
261 qdev_prop_allow_set_link_before_realize
,
265 static void mv88w8618_audio_realize(DeviceState
*dev
, Error
**errp
)
267 mv88w8618_audio_state
*s
= MV88W8618_AUDIO(dev
);
269 wm8750_data_req_set(s
->wm
, mv88w8618_audio_callback
, s
);
272 static const VMStateDescription mv88w8618_audio_vmsd
= {
273 .name
= "mv88w8618_audio",
275 .minimum_version_id
= 1,
276 .fields
= (VMStateField
[]) {
277 VMSTATE_UINT32(playback_mode
, mv88w8618_audio_state
),
278 VMSTATE_UINT32(status
, mv88w8618_audio_state
),
279 VMSTATE_UINT32(irq_enable
, mv88w8618_audio_state
),
280 VMSTATE_UINT32(phys_buf
, mv88w8618_audio_state
),
281 VMSTATE_UINT32(target_buffer
, mv88w8618_audio_state
),
282 VMSTATE_UINT32(threshold
, mv88w8618_audio_state
),
283 VMSTATE_UINT32(play_pos
, mv88w8618_audio_state
),
284 VMSTATE_UINT32(last_free
, mv88w8618_audio_state
),
285 VMSTATE_UINT32(clock_div
, mv88w8618_audio_state
),
286 VMSTATE_END_OF_LIST()
290 static void mv88w8618_audio_class_init(ObjectClass
*klass
, void *data
)
292 DeviceClass
*dc
= DEVICE_CLASS(klass
);
294 dc
->realize
= mv88w8618_audio_realize
;
295 dc
->reset
= mv88w8618_audio_reset
;
296 dc
->vmsd
= &mv88w8618_audio_vmsd
;
297 dc
->user_creatable
= false;
300 static const TypeInfo mv88w8618_audio_info
= {
301 .name
= TYPE_MV88W8618_AUDIO
,
302 .parent
= TYPE_SYS_BUS_DEVICE
,
303 .instance_size
= sizeof(mv88w8618_audio_state
),
304 .instance_init
= mv88w8618_audio_init
,
305 .class_init
= mv88w8618_audio_class_init
,
308 static void mv88w8618_register_types(void)
310 type_register_static(&mv88w8618_audio_info
);
313 type_init(mv88w8618_register_types
)