xref: /qemu/hw/audio/marvell_88w8618.c (revision e3a6e0da)
1 /*
2  * Marvell 88w8618 audio emulation extracted from
3  * Marvell MV88w8618 / Freecom MusicPal emulation.
4  *
5  * Copyright (c) 2008 Jan Kiszka
6  *
7  * This code is licensed under the GNU GPL v2.
8  *
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.
11  */
12 
13 #include "qemu/osdep.h"
14 #include "hw/sysbus.h"
15 #include "migration/vmstate.h"
16 #include "hw/irq.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"
23 
24 #define MP_AUDIO_SIZE           0x00001000
25 
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
35 
36 /* Status register and IRQ enable bits */
37 #define MP_AUDIO_TX_HALF        (1 << 6)
38 #define MP_AUDIO_TX_FULL        (1 << 7)
39 
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)
45 
46 typedef struct mv88w8618_audio_state mv88w8618_audio_state;
47 DECLARE_INSTANCE_CHECKER(mv88w8618_audio_state, MV88W8618_AUDIO,
48                          TYPE_MV88W8618_AUDIO)
49 
50 struct mv88w8618_audio_state {
51     SysBusDevice parent_obj;
52 
53     MemoryRegion iomem;
54     qemu_irq irq;
55     uint32_t playback_mode;
56     uint32_t status;
57     uint32_t irq_enable;
58     uint32_t phys_buf;
59     uint32_t target_buffer;
60     uint32_t threshold;
61     uint32_t play_pos;
62     uint32_t last_free;
63     uint32_t clock_div;
64     void *wm;
65 };
66 
67 static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
68 {
69     mv88w8618_audio_state *s = opaque;
70     int16_t *codec_buffer;
71     int8_t buf[4096];
72     int8_t *mem_buffer;
73     int pos, block_size;
74 
75     if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
76         return;
77     }
78     if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
79         free_out <<= 1;
80     }
81     if (!(s->playback_mode & MP_AUDIO_MONO)) {
82         free_out <<= 1;
83     }
84     block_size = s->threshold / 2;
85     if (free_out - s->last_free < block_size) {
86         return;
87     }
88     if (block_size > 4096) {
89         return;
90     }
91     cpu_physical_memory_read(s->target_buffer + s->play_pos, buf, block_size);
92     mem_buffer = buf;
93     if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
94         if (s->playback_mode & MP_AUDIO_MONO) {
95             codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
96             for (pos = 0; pos < block_size; pos += 2) {
97                 *codec_buffer++ = *(int16_t *)mem_buffer;
98                 *codec_buffer++ = *(int16_t *)mem_buffer;
99                 mem_buffer += 2;
100             }
101         } else {
102             memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
103                    (uint32_t *)mem_buffer, block_size);
104         }
105     } else {
106         if (s->playback_mode & MP_AUDIO_MONO) {
107             codec_buffer = wm8750_dac_buffer(s->wm, block_size);
108             for (pos = 0; pos < block_size; pos++) {
109                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
110                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
111             }
112         } else {
113             codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
114             for (pos = 0; pos < block_size; pos += 2) {
115                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
116                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
117             }
118         }
119     }
120     wm8750_dac_commit(s->wm);
121 
122     s->last_free = free_out - block_size;
123 
124     if (s->play_pos == 0) {
125         s->status |= MP_AUDIO_TX_HALF;
126         s->play_pos = block_size;
127     } else {
128         s->status |= MP_AUDIO_TX_FULL;
129         s->play_pos = 0;
130     }
131 
132     if (s->status & s->irq_enable) {
133         qemu_irq_raise(s->irq);
134     }
135 }
136 
137 static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
138 {
139     int rate;
140 
141     if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
142         rate = 24576000 / 64; /* 24.576MHz */
143     } else {
144         rate = 11289600 / 64; /* 11.2896MHz */
145     }
146     rate /= ((s->clock_div >> 8) & 0xff) + 1;
147 
148     wm8750_set_bclk_in(s->wm, rate);
149 }
150 
151 static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
152                                     unsigned size)
153 {
154     mv88w8618_audio_state *s = opaque;
155 
156     switch (offset) {
157     case MP_AUDIO_PLAYBACK_MODE:
158         return s->playback_mode;
159 
160     case MP_AUDIO_CLOCK_DIV:
161         return s->clock_div;
162 
163     case MP_AUDIO_IRQ_STATUS:
164         return s->status;
165 
166     case MP_AUDIO_IRQ_ENABLE:
167         return s->irq_enable;
168 
169     case MP_AUDIO_TX_STATUS:
170         return s->play_pos >> 2;
171 
172     default:
173         return 0;
174     }
175 }
176 
177 static void mv88w8618_audio_write(void *opaque, hwaddr offset,
178                                   uint64_t value, unsigned size)
179 {
180     mv88w8618_audio_state *s = opaque;
181 
182     switch (offset) {
183     case MP_AUDIO_PLAYBACK_MODE:
184         if (value & MP_AUDIO_PLAYBACK_EN &&
185             !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
186             s->status = 0;
187             s->last_free = 0;
188             s->play_pos = 0;
189         }
190         s->playback_mode = value;
191         mv88w8618_audio_clock_update(s);
192         break;
193 
194     case MP_AUDIO_CLOCK_DIV:
195         s->clock_div = value;
196         s->last_free = 0;
197         s->play_pos = 0;
198         mv88w8618_audio_clock_update(s);
199         break;
200 
201     case MP_AUDIO_IRQ_STATUS:
202         s->status &= ~value;
203         break;
204 
205     case MP_AUDIO_IRQ_ENABLE:
206         s->irq_enable = value;
207         if (s->status & s->irq_enable) {
208             qemu_irq_raise(s->irq);
209         }
210         break;
211 
212     case MP_AUDIO_TX_START_LO:
213         s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
214         s->target_buffer = s->phys_buf;
215         s->play_pos = 0;
216         s->last_free = 0;
217         break;
218 
219     case MP_AUDIO_TX_THRESHOLD:
220         s->threshold = (value + 1) * 4;
221         break;
222 
223     case MP_AUDIO_TX_START_HI:
224         s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
225         s->target_buffer = s->phys_buf;
226         s->play_pos = 0;
227         s->last_free = 0;
228         break;
229     }
230 }
231 
232 static void mv88w8618_audio_reset(DeviceState *d)
233 {
234     mv88w8618_audio_state *s = MV88W8618_AUDIO(d);
235 
236     s->playback_mode = 0;
237     s->status = 0;
238     s->irq_enable = 0;
239     s->clock_div = 0;
240     s->threshold = 0;
241     s->phys_buf = 0;
242 }
243 
244 static const MemoryRegionOps mv88w8618_audio_ops = {
245     .read = mv88w8618_audio_read,
246     .write = mv88w8618_audio_write,
247     .endianness = DEVICE_NATIVE_ENDIAN,
248 };
249 
250 static void mv88w8618_audio_init(Object *obj)
251 {
252     SysBusDevice *dev = SYS_BUS_DEVICE(obj);
253     mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
254 
255     sysbus_init_irq(dev, &s->irq);
256 
257     memory_region_init_io(&s->iomem, obj, &mv88w8618_audio_ops, s,
258                           "audio", MP_AUDIO_SIZE);
259     sysbus_init_mmio(dev, &s->iomem);
260 
261     object_property_add_link(OBJECT(dev), "wm8750", TYPE_WM8750,
262                              (Object **) &s->wm,
263                              qdev_prop_allow_set_link_before_realize,
264                              0);
265 }
266 
267 static void mv88w8618_audio_realize(DeviceState *dev, Error **errp)
268 {
269     mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
270 
271     wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
272 }
273 
274 static const VMStateDescription mv88w8618_audio_vmsd = {
275     .name = "mv88w8618_audio",
276     .version_id = 1,
277     .minimum_version_id = 1,
278     .fields = (VMStateField[]) {
279         VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
280         VMSTATE_UINT32(status, mv88w8618_audio_state),
281         VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
282         VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
283         VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
284         VMSTATE_UINT32(threshold, mv88w8618_audio_state),
285         VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
286         VMSTATE_UINT32(last_free, mv88w8618_audio_state),
287         VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
288         VMSTATE_END_OF_LIST()
289     }
290 };
291 
292 static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
293 {
294     DeviceClass *dc = DEVICE_CLASS(klass);
295 
296     dc->realize = mv88w8618_audio_realize;
297     dc->reset = mv88w8618_audio_reset;
298     dc->vmsd = &mv88w8618_audio_vmsd;
299     dc->user_creatable = false;
300 }
301 
302 static const TypeInfo mv88w8618_audio_info = {
303     .name          = TYPE_MV88W8618_AUDIO,
304     .parent        = TYPE_SYS_BUS_DEVICE,
305     .instance_size = sizeof(mv88w8618_audio_state),
306     .instance_init = mv88w8618_audio_init,
307     .class_init    = mv88w8618_audio_class_init,
308 };
309 
310 static void mv88w8618_register_types(void)
311 {
312     type_register_static(&mv88w8618_audio_info);
313 }
314 
315 type_init(mv88w8618_register_types)
316