xref: /openbmc/qemu/hw/audio/marvell_88w8618.c (revision 2993683b)
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, buf, block_size);
81     mem_buffer = buf;
82     if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
83         if (s->playback_mode & MP_AUDIO_MONO) {
84             codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
85             for (pos = 0; pos < block_size; pos += 2) {
86                 *codec_buffer++ = *(int16_t *)mem_buffer;
87                 *codec_buffer++ = *(int16_t *)mem_buffer;
88                 mem_buffer += 2;
89             }
90         } else {
91             memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
92                    (uint32_t *)mem_buffer, block_size);
93         }
94     } else {
95         if (s->playback_mode & MP_AUDIO_MONO) {
96             codec_buffer = wm8750_dac_buffer(s->wm, block_size);
97             for (pos = 0; pos < block_size; pos++) {
98                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
99                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
100             }
101         } else {
102             codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
103             for (pos = 0; pos < block_size; pos += 2) {
104                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
105                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
106             }
107         }
108     }
109     wm8750_dac_commit(s->wm);
110 
111     s->last_free = free_out - block_size;
112 
113     if (s->play_pos == 0) {
114         s->status |= MP_AUDIO_TX_HALF;
115         s->play_pos = block_size;
116     } else {
117         s->status |= MP_AUDIO_TX_FULL;
118         s->play_pos = 0;
119     }
120 
121     if (s->status & s->irq_enable) {
122         qemu_irq_raise(s->irq);
123     }
124 }
125 
126 static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
127 {
128     int rate;
129 
130     if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
131         rate = 24576000 / 64; /* 24.576MHz */
132     } else {
133         rate = 11289600 / 64; /* 11.2896MHz */
134     }
135     rate /= ((s->clock_div >> 8) & 0xff) + 1;
136 
137     wm8750_set_bclk_in(s->wm, rate);
138 }
139 
140 static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
141                                     unsigned size)
142 {
143     mv88w8618_audio_state *s = opaque;
144 
145     switch (offset) {
146     case MP_AUDIO_PLAYBACK_MODE:
147         return s->playback_mode;
148 
149     case MP_AUDIO_CLOCK_DIV:
150         return s->clock_div;
151 
152     case MP_AUDIO_IRQ_STATUS:
153         return s->status;
154 
155     case MP_AUDIO_IRQ_ENABLE:
156         return s->irq_enable;
157 
158     case MP_AUDIO_TX_STATUS:
159         return s->play_pos >> 2;
160 
161     default:
162         return 0;
163     }
164 }
165 
166 static void mv88w8618_audio_write(void *opaque, hwaddr offset,
167                                   uint64_t value, unsigned size)
168 {
169     mv88w8618_audio_state *s = opaque;
170 
171     switch (offset) {
172     case MP_AUDIO_PLAYBACK_MODE:
173         if (value & MP_AUDIO_PLAYBACK_EN &&
174             !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
175             s->status = 0;
176             s->last_free = 0;
177             s->play_pos = 0;
178         }
179         s->playback_mode = value;
180         mv88w8618_audio_clock_update(s);
181         break;
182 
183     case MP_AUDIO_CLOCK_DIV:
184         s->clock_div = value;
185         s->last_free = 0;
186         s->play_pos = 0;
187         mv88w8618_audio_clock_update(s);
188         break;
189 
190     case MP_AUDIO_IRQ_STATUS:
191         s->status &= ~value;
192         break;
193 
194     case MP_AUDIO_IRQ_ENABLE:
195         s->irq_enable = value;
196         if (s->status & s->irq_enable) {
197             qemu_irq_raise(s->irq);
198         }
199         break;
200 
201     case MP_AUDIO_TX_START_LO:
202         s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
203         s->target_buffer = s->phys_buf;
204         s->play_pos = 0;
205         s->last_free = 0;
206         break;
207 
208     case MP_AUDIO_TX_THRESHOLD:
209         s->threshold = (value + 1) * 4;
210         break;
211 
212     case MP_AUDIO_TX_START_HI:
213         s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
214         s->target_buffer = s->phys_buf;
215         s->play_pos = 0;
216         s->last_free = 0;
217         break;
218     }
219 }
220 
221 static void mv88w8618_audio_reset(DeviceState *d)
222 {
223     mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state,
224                                            SYS_BUS_DEVICE(d));
225 
226     s->playback_mode = 0;
227     s->status = 0;
228     s->irq_enable = 0;
229     s->clock_div = 0;
230     s->threshold = 0;
231     s->phys_buf = 0;
232 }
233 
234 static const MemoryRegionOps mv88w8618_audio_ops = {
235     .read = mv88w8618_audio_read,
236     .write = mv88w8618_audio_write,
237     .endianness = DEVICE_NATIVE_ENDIAN,
238 };
239 
240 static int mv88w8618_audio_init(SysBusDevice *dev)
241 {
242     mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev);
243 
244     sysbus_init_irq(dev, &s->irq);
245 
246     wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
247 
248     memory_region_init_io(&s->iomem, &mv88w8618_audio_ops, s,
249                           "audio", MP_AUDIO_SIZE);
250     sysbus_init_mmio(dev, &s->iomem);
251 
252     return 0;
253 }
254 
255 static const VMStateDescription mv88w8618_audio_vmsd = {
256     .name = "mv88w8618_audio",
257     .version_id = 1,
258     .minimum_version_id = 1,
259     .minimum_version_id_old = 1,
260     .fields = (VMStateField[]) {
261         VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
262         VMSTATE_UINT32(status, mv88w8618_audio_state),
263         VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
264         VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
265         VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
266         VMSTATE_UINT32(threshold, mv88w8618_audio_state),
267         VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
268         VMSTATE_UINT32(last_free, mv88w8618_audio_state),
269         VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
270         VMSTATE_END_OF_LIST()
271     }
272 };
273 
274 static Property mv88w8618_audio_properties[] = {
275     DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state, wm),
276     {/* end of list */},
277 };
278 
279 static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
280 {
281     DeviceClass *dc = DEVICE_CLASS(klass);
282     SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
283 
284     k->init = mv88w8618_audio_init;
285     dc->reset = mv88w8618_audio_reset;
286     dc->vmsd = &mv88w8618_audio_vmsd;
287     dc->props = mv88w8618_audio_properties;
288 }
289 
290 static const TypeInfo mv88w8618_audio_info = {
291     .name          = "mv88w8618_audio",
292     .parent        = TYPE_SYS_BUS_DEVICE,
293     .instance_size = sizeof(mv88w8618_audio_state),
294     .class_init    = mv88w8618_audio_class_init,
295 };
296 
297 static void mv88w8618_register_types(void)
298 {
299     type_register_static(&mv88w8618_audio_info);
300 }
301 
302 type_init(mv88w8618_register_types)
303