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