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