linux-user: fix ppc target_stat64 st_blocks layout
[qemu] / hw / marvell_88w8618_audio.c
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 licenced under the GNU GPL v2.
8  */
9 #include "sysbus.h"
10 #include "hw.h"
11 #include "i2c.h"
12 #include "sysbus.h"
13 #include "audio/audio.h"
14
15 #define MP_AUDIO_SIZE           0x00001000
16
17 /* Audio register offsets */
18 #define MP_AUDIO_PLAYBACK_MODE  0x00
19 #define MP_AUDIO_CLOCK_DIV      0x18
20 #define MP_AUDIO_IRQ_STATUS     0x20
21 #define MP_AUDIO_IRQ_ENABLE     0x24
22 #define MP_AUDIO_TX_START_LO    0x28
23 #define MP_AUDIO_TX_THRESHOLD   0x2C
24 #define MP_AUDIO_TX_STATUS      0x38
25 #define MP_AUDIO_TX_START_HI    0x40
26
27 /* Status register and IRQ enable bits */
28 #define MP_AUDIO_TX_HALF        (1 << 6)
29 #define MP_AUDIO_TX_FULL        (1 << 7)
30
31 /* Playback mode bits */
32 #define MP_AUDIO_16BIT_SAMPLE   (1 << 0)
33 #define MP_AUDIO_PLAYBACK_EN    (1 << 7)
34 #define MP_AUDIO_CLOCK_24MHZ    (1 << 9)
35 #define MP_AUDIO_MONO           (1 << 14)
36
37 #ifdef HAS_AUDIO
38 typedef struct mv88w8618_audio_state {
39     SysBusDevice busdev;
40     qemu_irq irq;
41     uint32_t playback_mode;
42     uint32_t status;
43     uint32_t irq_enable;
44     unsigned long phys_buf;
45     uint32_t target_buffer;
46     unsigned int threshold;
47     unsigned int play_pos;
48     unsigned int last_free;
49     uint32_t clock_div;
50     DeviceState *wm;
51 } mv88w8618_audio_state;
52
53 static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
54 {
55     mv88w8618_audio_state *s = opaque;
56     int16_t *codec_buffer;
57     int8_t buf[4096];
58     int8_t *mem_buffer;
59     int pos, block_size;
60
61     if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN))
62         return;
63
64     if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE)
65         free_out <<= 1;
66
67     if (!(s->playback_mode & MP_AUDIO_MONO))
68         free_out <<= 1;
69
70     block_size = s->threshold / 2;
71     if (free_out - s->last_free < block_size)
72         return;
73
74     if (block_size > 4096)
75         return;
76
77     cpu_physical_memory_read(s->target_buffer + s->play_pos, (void *)buf,
78                              block_size);
79     mem_buffer = buf;
80     if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
81         if (s->playback_mode & MP_AUDIO_MONO) {
82             codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
83             for (pos = 0; pos < block_size; pos += 2) {
84                 *codec_buffer++ = *(int16_t *)mem_buffer;
85                 *codec_buffer++ = *(int16_t *)mem_buffer;
86                 mem_buffer += 2;
87             }
88         } else
89             memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
90                    (uint32_t *)mem_buffer, block_size);
91     } else {
92         if (s->playback_mode & MP_AUDIO_MONO) {
93             codec_buffer = wm8750_dac_buffer(s->wm, block_size);
94             for (pos = 0; pos < block_size; pos++) {
95                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
96                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
97             }
98         } else {
99             codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
100             for (pos = 0; pos < block_size; pos += 2) {
101                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
102                 *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
103             }
104         }
105     }
106     wm8750_dac_commit(s->wm);
107
108     s->last_free = free_out - block_size;
109
110     if (s->play_pos == 0) {
111         s->status |= MP_AUDIO_TX_HALF;
112         s->play_pos = block_size;
113     } else {
114         s->status |= MP_AUDIO_TX_FULL;
115         s->play_pos = 0;
116     }
117
118     if (s->status & s->irq_enable)
119         qemu_irq_raise(s->irq);
120 }
121
122 static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
123 {
124     int rate;
125
126     if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ)
127         rate = 24576000 / 64; /* 24.576MHz */
128     else
129         rate = 11289600 / 64; /* 11.2896MHz */
130
131     rate /= ((s->clock_div >> 8) & 0xff) + 1;
132
133     wm8750_set_bclk_in(s->wm, rate);
134 }
135
136 static uint32_t mv88w8618_audio_read(void *opaque, target_phys_addr_t offset)
137 {
138     mv88w8618_audio_state *s = opaque;
139
140     switch (offset) {
141     case MP_AUDIO_PLAYBACK_MODE:
142         return s->playback_mode;
143
144     case MP_AUDIO_CLOCK_DIV:
145         return s->clock_div;
146
147     case MP_AUDIO_IRQ_STATUS:
148         return s->status;
149
150     case MP_AUDIO_IRQ_ENABLE:
151         return s->irq_enable;
152
153     case MP_AUDIO_TX_STATUS:
154         return s->play_pos >> 2;
155
156     default:
157         return 0;
158     }
159 }
160
161 static void mv88w8618_audio_write(void *opaque, target_phys_addr_t offset,
162                                  uint32_t value)
163 {
164     mv88w8618_audio_state *s = opaque;
165
166     switch (offset) {
167     case MP_AUDIO_PLAYBACK_MODE:
168         if (value & MP_AUDIO_PLAYBACK_EN &&
169             !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
170             s->status = 0;
171             s->last_free = 0;
172             s->play_pos = 0;
173         }
174         s->playback_mode = value;
175         mv88w8618_audio_clock_update(s);
176         break;
177
178     case MP_AUDIO_CLOCK_DIV:
179         s->clock_div = value;
180         s->last_free = 0;
181         s->play_pos = 0;
182         mv88w8618_audio_clock_update(s);
183         break;
184
185     case MP_AUDIO_IRQ_STATUS:
186         s->status &= ~value;
187         break;
188
189     case MP_AUDIO_IRQ_ENABLE:
190         s->irq_enable = value;
191         if (s->status & s->irq_enable)
192             qemu_irq_raise(s->irq);
193         break;
194
195     case MP_AUDIO_TX_START_LO:
196         s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
197         s->target_buffer = s->phys_buf;
198         s->play_pos = 0;
199         s->last_free = 0;
200         break;
201
202     case MP_AUDIO_TX_THRESHOLD:
203         s->threshold = (value + 1) * 4;
204         break;
205
206     case MP_AUDIO_TX_START_HI:
207         s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
208         s->target_buffer = s->phys_buf;
209         s->play_pos = 0;
210         s->last_free = 0;
211         break;
212     }
213 }
214
215 static void mv88w8618_audio_reset(void *opaque)
216 {
217     mv88w8618_audio_state *s = opaque;
218
219     s->playback_mode = 0;
220     s->status = 0;
221     s->irq_enable = 0;
222 }
223
224 static CPUReadMemoryFunc * const mv88w8618_audio_readfn[] = {
225     mv88w8618_audio_read,
226     mv88w8618_audio_read,
227     mv88w8618_audio_read
228 };
229
230 static CPUWriteMemoryFunc * const mv88w8618_audio_writefn[] = {
231     mv88w8618_audio_write,
232     mv88w8618_audio_write,
233     mv88w8618_audio_write
234 };
235
236 static int mv88w8618_audio_init(SysBusDevice *dev)
237 {
238     mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev);
239     int iomemtype;
240
241     sysbus_init_irq(dev, &s->irq);
242
243     wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
244
245     iomemtype = cpu_register_io_memory(mv88w8618_audio_readfn,
246                                        mv88w8618_audio_writefn, s);
247     sysbus_init_mmio(dev, MP_AUDIO_SIZE, iomemtype);
248
249     qemu_register_reset(mv88w8618_audio_reset, s);
250
251     return 0;
252 }
253
254 static SysBusDeviceInfo mv88w8618_audio_info = {
255     .init = mv88w8618_audio_init,
256     .qdev.name  = "mv88w8618_audio",
257     .qdev.size  = sizeof(mv88w8618_audio_state),
258     .qdev.props = (Property[]) {
259         {
260             .name   = "wm8750",
261             .info   = &qdev_prop_ptr,
262             .offset = offsetof(mv88w8618_audio_state, wm),
263         },
264         {/* end of list */}
265     }
266 };
267 #endif
268
269 static void mv88w8618_register_devices(void)
270 {
271 #ifdef HAS_AUDIO
272     sysbus_register_withprop(&mv88w8618_audio_info);
273 #endif
274 }
275
276 device_init(mv88w8618_register_devices)