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