xref: /openbmc/qemu/hw/audio/marvell_88w8618.c (revision 9944d320)
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 #include "hw/sysbus.h"
13 #include "hw/hw.h"
14 #include "hw/i2c/i2c.h"
15 #include "hw/sysbus.h"
16 #include "audio/audio.h"
17 
18 #define MP_AUDIO_SIZE           0x00001000
19 
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
29 
30 /* Status register and IRQ enable bits */
31 #define MP_AUDIO_TX_HALF        (1 << 6)
32 #define MP_AUDIO_TX_FULL        (1 << 7)
33 
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)
39 
40 typedef struct mv88w8618_audio_state {
41     SysBusDevice busdev;
42     MemoryRegion iomem;
43     qemu_irq irq;
44     uint32_t playback_mode;
45     uint32_t status;
46     uint32_t irq_enable;
47     uint32_t phys_buf;
48     uint32_t target_buffer;
49     uint32_t threshold;
50     uint32_t play_pos;
51     uint32_t last_free;
52     uint32_t clock_div;
53     void *wm;
54 } mv88w8618_audio_state;
55 
56 static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
57 {
58     mv88w8618_audio_state *s = opaque;
59     int16_t *codec_buffer;
60     int8_t buf[4096];
61     int8_t *mem_buffer;
62     int pos, block_size;
63 
64     if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
65         return;
66     }
67     if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
68         free_out <<= 1;
69     }
70     if (!(s->playback_mode & MP_AUDIO_MONO)) {
71         free_out <<= 1;
72     }
73     block_size = s->threshold / 2;
74     if (free_out - s->last_free < block_size) {
75         return;
76     }
77     if (block_size > 4096) {
78         return;
79     }
80     cpu_physical_memory_read(s->target_buffer + s->play_pos, (void *)buf,
81                              block_size);
82     mem_buffer = 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;
89                 mem_buffer += 2;
90             }
91         } else {
92             memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
93                    (uint32_t *)mem_buffer, block_size);
94         }
95     } else {
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++);
101             }
102         } else {
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++);
107             }
108         }
109     }
110     wm8750_dac_commit(s->wm);
111 
112     s->last_free = free_out - block_size;
113 
114     if (s->play_pos == 0) {
115         s->status |= MP_AUDIO_TX_HALF;
116         s->play_pos = block_size;
117     } else {
118         s->status |= MP_AUDIO_TX_FULL;
119         s->play_pos = 0;
120     }
121 
122     if (s->status & s->irq_enable) {
123         qemu_irq_raise(s->irq);
124     }
125 }
126 
127 static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
128 {
129     int rate;
130 
131     if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
132         rate = 24576000 / 64; /* 24.576MHz */
133     } else {
134         rate = 11289600 / 64; /* 11.2896MHz */
135     }
136     rate /= ((s->clock_div >> 8) & 0xff) + 1;
137 
138     wm8750_set_bclk_in(s->wm, rate);
139 }
140 
141 static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
142                                     unsigned size)
143 {
144     mv88w8618_audio_state *s = opaque;
145 
146     switch (offset) {
147     case MP_AUDIO_PLAYBACK_MODE:
148         return s->playback_mode;
149 
150     case MP_AUDIO_CLOCK_DIV:
151         return s->clock_div;
152 
153     case MP_AUDIO_IRQ_STATUS:
154         return s->status;
155 
156     case MP_AUDIO_IRQ_ENABLE:
157         return s->irq_enable;
158 
159     case MP_AUDIO_TX_STATUS:
160         return s->play_pos >> 2;
161 
162     default:
163         return 0;
164     }
165 }
166 
167 static void mv88w8618_audio_write(void *opaque, hwaddr offset,
168                                   uint64_t value, unsigned size)
169 {
170     mv88w8618_audio_state *s = opaque;
171 
172     switch (offset) {
173     case MP_AUDIO_PLAYBACK_MODE:
174         if (value & MP_AUDIO_PLAYBACK_EN &&
175             !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
176             s->status = 0;
177             s->last_free = 0;
178             s->play_pos = 0;
179         }
180         s->playback_mode = value;
181         mv88w8618_audio_clock_update(s);
182         break;
183 
184     case MP_AUDIO_CLOCK_DIV:
185         s->clock_div = value;
186         s->last_free = 0;
187         s->play_pos = 0;
188         mv88w8618_audio_clock_update(s);
189         break;
190 
191     case MP_AUDIO_IRQ_STATUS:
192         s->status &= ~value;
193         break;
194 
195     case MP_AUDIO_IRQ_ENABLE:
196         s->irq_enable = value;
197         if (s->status & s->irq_enable) {
198             qemu_irq_raise(s->irq);
199         }
200         break;
201 
202     case MP_AUDIO_TX_START_LO:
203         s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
204         s->target_buffer = s->phys_buf;
205         s->play_pos = 0;
206         s->last_free = 0;
207         break;
208 
209     case MP_AUDIO_TX_THRESHOLD:
210         s->threshold = (value + 1) * 4;
211         break;
212 
213     case MP_AUDIO_TX_START_HI:
214         s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
215         s->target_buffer = s->phys_buf;
216         s->play_pos = 0;
217         s->last_free = 0;
218         break;
219     }
220 }
221 
222 static void mv88w8618_audio_reset(DeviceState *d)
223 {
224     mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state,
225                                            SYS_BUS_DEVICE(d));
226 
227     s->playback_mode = 0;
228     s->status = 0;
229     s->irq_enable = 0;
230     s->clock_div = 0;
231     s->threshold = 0;
232     s->phys_buf = 0;
233 }
234 
235 static const MemoryRegionOps mv88w8618_audio_ops = {
236     .read = mv88w8618_audio_read,
237     .write = mv88w8618_audio_write,
238     .endianness = DEVICE_NATIVE_ENDIAN,
239 };
240 
241 static int mv88w8618_audio_init(SysBusDevice *dev)
242 {
243     mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev);
244 
245     sysbus_init_irq(dev, &s->irq);
246 
247     wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
248 
249     memory_region_init_io(&s->iomem, &mv88w8618_audio_ops, s,
250                           "audio", MP_AUDIO_SIZE);
251     sysbus_init_mmio(dev, &s->iomem);
252 
253     return 0;
254 }
255 
256 static const VMStateDescription mv88w8618_audio_vmsd = {
257     .name = "mv88w8618_audio",
258     .version_id = 1,
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()
272     }
273 };
274 
275 static Property mv88w8618_audio_properties[] = {
276     DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state, wm),
277     {/* end of list */},
278 };
279 
280 static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
281 {
282     DeviceClass *dc = DEVICE_CLASS(klass);
283     SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
284 
285     k->init = mv88w8618_audio_init;
286     dc->reset = mv88w8618_audio_reset;
287     dc->vmsd = &mv88w8618_audio_vmsd;
288     dc->props = mv88w8618_audio_properties;
289 }
290 
291 static const 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,
296 };
297 
298 static void mv88w8618_register_types(void)
299 {
300     type_register_static(&mv88w8618_audio_info);
301 }
302 
303 type_init(mv88w8618_register_types)
304