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