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.
13 #include "audio/audio.h"
15 #define MP_AUDIO_SIZE 0x00001000
17 /* Audio register offsets */
18 #define MP_AUDIO_PLAYBACK_MODE 0x00
19 #define MP_AUDIO_CLOCK_DIV 0x18
20 #define MP_AUDIO_IRQ_STATUS 0x20
21 #define MP_AUDIO_IRQ_ENABLE 0x24
22 #define MP_AUDIO_TX_START_LO 0x28
23 #define MP_AUDIO_TX_THRESHOLD 0x2C
24 #define MP_AUDIO_TX_STATUS 0x38
25 #define MP_AUDIO_TX_START_HI 0x40
27 /* Status register and IRQ enable bits */
28 #define MP_AUDIO_TX_HALF (1 << 6)
29 #define MP_AUDIO_TX_FULL (1 << 7)
31 /* Playback mode bits */
32 #define MP_AUDIO_16BIT_SAMPLE (1 << 0)
33 #define MP_AUDIO_PLAYBACK_EN (1 << 7)
34 #define MP_AUDIO_CLOCK_24MHZ (1 << 9)
35 #define MP_AUDIO_MONO (1 << 14)
37 typedef struct mv88w8618_audio_state
{
40 uint32_t playback_mode
;
44 uint32_t target_buffer
;
50 } mv88w8618_audio_state
;
52 static void mv88w8618_audio_callback(void *opaque
, int free_out
, int free_in
)
54 mv88w8618_audio_state
*s
= opaque
;
55 int16_t *codec_buffer
;
60 if (!(s
->playback_mode
& MP_AUDIO_PLAYBACK_EN
)) {
63 if (s
->playback_mode
& MP_AUDIO_16BIT_SAMPLE
) {
66 if (!(s
->playback_mode
& MP_AUDIO_MONO
)) {
69 block_size
= s
->threshold
/ 2;
70 if (free_out
- s
->last_free
< block_size
) {
73 if (block_size
> 4096) {
76 cpu_physical_memory_read(s
->target_buffer
+ s
->play_pos
, (void *)buf
,
79 if (s
->playback_mode
& MP_AUDIO_16BIT_SAMPLE
) {
80 if (s
->playback_mode
& MP_AUDIO_MONO
) {
81 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
>> 1);
82 for (pos
= 0; pos
< block_size
; pos
+= 2) {
83 *codec_buffer
++ = *(int16_t *)mem_buffer
;
84 *codec_buffer
++ = *(int16_t *)mem_buffer
;
88 memcpy(wm8750_dac_buffer(s
->wm
, block_size
>> 2),
89 (uint32_t *)mem_buffer
, block_size
);
92 if (s
->playback_mode
& MP_AUDIO_MONO
) {
93 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
);
94 for (pos
= 0; pos
< block_size
; pos
++) {
95 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
);
96 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
99 codec_buffer
= wm8750_dac_buffer(s
->wm
, block_size
>> 1);
100 for (pos
= 0; pos
< block_size
; pos
+= 2) {
101 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
102 *codec_buffer
++ = cpu_to_le16(256 * *mem_buffer
++);
106 wm8750_dac_commit(s
->wm
);
108 s
->last_free
= free_out
- block_size
;
110 if (s
->play_pos
== 0) {
111 s
->status
|= MP_AUDIO_TX_HALF
;
112 s
->play_pos
= block_size
;
114 s
->status
|= MP_AUDIO_TX_FULL
;
118 if (s
->status
& s
->irq_enable
) {
119 qemu_irq_raise(s
->irq
);
123 static void mv88w8618_audio_clock_update(mv88w8618_audio_state
*s
)
127 if (s
->playback_mode
& MP_AUDIO_CLOCK_24MHZ
) {
128 rate
= 24576000 / 64; /* 24.576MHz */
130 rate
= 11289600 / 64; /* 11.2896MHz */
132 rate
/= ((s
->clock_div
>> 8) & 0xff) + 1;
134 wm8750_set_bclk_in(s
->wm
, rate
);
137 static uint32_t mv88w8618_audio_read(void *opaque
, target_phys_addr_t offset
)
139 mv88w8618_audio_state
*s
= opaque
;
142 case MP_AUDIO_PLAYBACK_MODE
:
143 return s
->playback_mode
;
145 case MP_AUDIO_CLOCK_DIV
:
148 case MP_AUDIO_IRQ_STATUS
:
151 case MP_AUDIO_IRQ_ENABLE
:
152 return s
->irq_enable
;
154 case MP_AUDIO_TX_STATUS
:
155 return s
->play_pos
>> 2;
162 static void mv88w8618_audio_write(void *opaque
, target_phys_addr_t offset
,
165 mv88w8618_audio_state
*s
= opaque
;
168 case MP_AUDIO_PLAYBACK_MODE
:
169 if (value
& MP_AUDIO_PLAYBACK_EN
&&
170 !(s
->playback_mode
& MP_AUDIO_PLAYBACK_EN
)) {
175 s
->playback_mode
= value
;
176 mv88w8618_audio_clock_update(s
);
179 case MP_AUDIO_CLOCK_DIV
:
180 s
->clock_div
= value
;
183 mv88w8618_audio_clock_update(s
);
186 case MP_AUDIO_IRQ_STATUS
:
190 case MP_AUDIO_IRQ_ENABLE
:
191 s
->irq_enable
= value
;
192 if (s
->status
& s
->irq_enable
) {
193 qemu_irq_raise(s
->irq
);
197 case MP_AUDIO_TX_START_LO
:
198 s
->phys_buf
= (s
->phys_buf
& 0xFFFF0000) | (value
& 0xFFFF);
199 s
->target_buffer
= s
->phys_buf
;
204 case MP_AUDIO_TX_THRESHOLD
:
205 s
->threshold
= (value
+ 1) * 4;
208 case MP_AUDIO_TX_START_HI
:
209 s
->phys_buf
= (s
->phys_buf
& 0xFFFF) | (value
<< 16);
210 s
->target_buffer
= s
->phys_buf
;
217 static void mv88w8618_audio_reset(DeviceState
*d
)
219 mv88w8618_audio_state
*s
= FROM_SYSBUS(mv88w8618_audio_state
,
220 sysbus_from_qdev(d
));
222 s
->playback_mode
= 0;
230 static CPUReadMemoryFunc
* const mv88w8618_audio_readfn
[] = {
231 mv88w8618_audio_read
,
232 mv88w8618_audio_read
,
236 static CPUWriteMemoryFunc
* const mv88w8618_audio_writefn
[] = {
237 mv88w8618_audio_write
,
238 mv88w8618_audio_write
,
239 mv88w8618_audio_write
242 static int mv88w8618_audio_init(SysBusDevice
*dev
)
244 mv88w8618_audio_state
*s
= FROM_SYSBUS(mv88w8618_audio_state
, dev
);
247 sysbus_init_irq(dev
, &s
->irq
);
249 wm8750_data_req_set(s
->wm
, mv88w8618_audio_callback
, s
);
251 iomemtype
= cpu_register_io_memory(mv88w8618_audio_readfn
,
252 mv88w8618_audio_writefn
, s
,
253 DEVICE_NATIVE_ENDIAN
);
254 sysbus_init_mmio(dev
, MP_AUDIO_SIZE
, iomemtype
);
259 static const VMStateDescription mv88w8618_audio_vmsd
= {
260 .name
= "mv88w8618_audio",
262 .minimum_version_id
= 1,
263 .minimum_version_id_old
= 1,
264 .fields
= (VMStateField
[]) {
265 VMSTATE_UINT32(playback_mode
, mv88w8618_audio_state
),
266 VMSTATE_UINT32(status
, mv88w8618_audio_state
),
267 VMSTATE_UINT32(irq_enable
, mv88w8618_audio_state
),
268 VMSTATE_UINT32(phys_buf
, mv88w8618_audio_state
),
269 VMSTATE_UINT32(target_buffer
, mv88w8618_audio_state
),
270 VMSTATE_UINT32(threshold
, mv88w8618_audio_state
),
271 VMSTATE_UINT32(play_pos
, mv88w8618_audio_state
),
272 VMSTATE_UINT32(last_free
, mv88w8618_audio_state
),
273 VMSTATE_UINT32(clock_div
, mv88w8618_audio_state
),
274 VMSTATE_END_OF_LIST()
278 static SysBusDeviceInfo mv88w8618_audio_info
= {
279 .init
= mv88w8618_audio_init
,
280 .qdev
.name
= "mv88w8618_audio",
281 .qdev
.size
= sizeof(mv88w8618_audio_state
),
282 .qdev
.reset
= mv88w8618_audio_reset
,
283 .qdev
.vmsd
= &mv88w8618_audio_vmsd
,
284 .qdev
.props
= (Property
[]) {
287 .info
= &qdev_prop_ptr
,
288 .offset
= offsetof(mv88w8618_audio_state
, wm
),
294 static void mv88w8618_register_devices(void)
296 sysbus_register_withprop(&mv88w8618_audio_info
);
299 device_init(mv88w8618_register_devices
)