~ubuntu-branches/ubuntu/wily/qemu-kvm-spice/wily

« back to all changes in this revision

Viewing changes to hw/marvell_88w8618_audio.c

  • Committer: Bazaar Package Importer
  • Author(s): Serge Hallyn
  • Date: 2011-10-19 10:44:56 UTC
  • Revision ID: james.westby@ubuntu.com-20111019104456-xgvskumk3sxi97f4
Tags: upstream-0.15.0+noroms
ImportĀ upstreamĀ versionĀ 0.15.0+noroms

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
#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
typedef struct mv88w8618_audio_state {
 
38
    SysBusDevice busdev;
 
39
    qemu_irq irq;
 
40
    uint32_t playback_mode;
 
41
    uint32_t status;
 
42
    uint32_t irq_enable;
 
43
    uint32_t phys_buf;
 
44
    uint32_t target_buffer;
 
45
    uint32_t threshold;
 
46
    uint32_t play_pos;
 
47
    uint32_t last_free;
 
48
    uint32_t clock_div;
 
49
    DeviceState *wm;
 
50
} mv88w8618_audio_state;
 
51
 
 
52
static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
 
53
{
 
54
    mv88w8618_audio_state *s = opaque;
 
55
    int16_t *codec_buffer;
 
56
    int8_t buf[4096];
 
57
    int8_t *mem_buffer;
 
58
    int pos, block_size;
 
59
 
 
60
    if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
 
61
        return;
 
62
    }
 
63
    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
 
64
        free_out <<= 1;
 
65
    }
 
66
    if (!(s->playback_mode & MP_AUDIO_MONO)) {
 
67
        free_out <<= 1;
 
68
    }
 
69
    block_size = s->threshold / 2;
 
70
    if (free_out - s->last_free < block_size) {
 
71
        return;
 
72
    }
 
73
    if (block_size > 4096) {
 
74
        return;
 
75
    }
 
76
    cpu_physical_memory_read(s->target_buffer + s->play_pos, (void *)buf,
 
77
                             block_size);
 
78
    mem_buffer = buf;
 
79
    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
 
80
        if (s->playback_mode & MP_AUDIO_MONO) {
 
81
            codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
 
82
            for (pos = 0; pos < block_size; pos += 2) {
 
83
                *codec_buffer++ = *(int16_t *)mem_buffer;
 
84
                *codec_buffer++ = *(int16_t *)mem_buffer;
 
85
                mem_buffer += 2;
 
86
            }
 
87
        } else {
 
88
            memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
 
89
                   (uint32_t *)mem_buffer, block_size);
 
90
        }
 
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
 
 
123
static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
 
124
{
 
125
    int rate;
 
126
 
 
127
    if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
 
128
        rate = 24576000 / 64; /* 24.576MHz */
 
129
    } else {
 
130
        rate = 11289600 / 64; /* 11.2896MHz */
 
131
    }
 
132
    rate /= ((s->clock_div >> 8) & 0xff) + 1;
 
133
 
 
134
    wm8750_set_bclk_in(s->wm, rate);
 
135
}
 
136
 
 
137
static uint32_t mv88w8618_audio_read(void *opaque, target_phys_addr_t offset)
 
138
{
 
139
    mv88w8618_audio_state *s = opaque;
 
140
 
 
141
    switch (offset) {
 
142
    case MP_AUDIO_PLAYBACK_MODE:
 
143
        return s->playback_mode;
 
144
 
 
145
    case MP_AUDIO_CLOCK_DIV:
 
146
        return s->clock_div;
 
147
 
 
148
    case MP_AUDIO_IRQ_STATUS:
 
149
        return s->status;
 
150
 
 
151
    case MP_AUDIO_IRQ_ENABLE:
 
152
        return s->irq_enable;
 
153
 
 
154
    case MP_AUDIO_TX_STATUS:
 
155
        return s->play_pos >> 2;
 
156
 
 
157
    default:
 
158
        return 0;
 
159
    }
 
160
}
 
161
 
 
162
static void mv88w8618_audio_write(void *opaque, target_phys_addr_t offset,
 
163
                                 uint32_t value)
 
164
{
 
165
    mv88w8618_audio_state *s = opaque;
 
166
 
 
167
    switch (offset) {
 
168
    case MP_AUDIO_PLAYBACK_MODE:
 
169
        if (value & MP_AUDIO_PLAYBACK_EN &&
 
170
            !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
 
171
            s->status = 0;
 
172
            s->last_free = 0;
 
173
            s->play_pos = 0;
 
174
        }
 
175
        s->playback_mode = value;
 
176
        mv88w8618_audio_clock_update(s);
 
177
        break;
 
178
 
 
179
    case MP_AUDIO_CLOCK_DIV:
 
180
        s->clock_div = value;
 
181
        s->last_free = 0;
 
182
        s->play_pos = 0;
 
183
        mv88w8618_audio_clock_update(s);
 
184
        break;
 
185
 
 
186
    case MP_AUDIO_IRQ_STATUS:
 
187
        s->status &= ~value;
 
188
        break;
 
189
 
 
190
    case MP_AUDIO_IRQ_ENABLE:
 
191
        s->irq_enable = value;
 
192
        if (s->status & s->irq_enable) {
 
193
            qemu_irq_raise(s->irq);
 
194
        }
 
195
        break;
 
196
 
 
197
    case MP_AUDIO_TX_START_LO:
 
198
        s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
 
199
        s->target_buffer = s->phys_buf;
 
200
        s->play_pos = 0;
 
201
        s->last_free = 0;
 
202
        break;
 
203
 
 
204
    case MP_AUDIO_TX_THRESHOLD:
 
205
        s->threshold = (value + 1) * 4;
 
206
        break;
 
207
 
 
208
    case MP_AUDIO_TX_START_HI:
 
209
        s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
 
210
        s->target_buffer = s->phys_buf;
 
211
        s->play_pos = 0;
 
212
        s->last_free = 0;
 
213
        break;
 
214
    }
 
215
}
 
216
 
 
217
static void mv88w8618_audio_reset(DeviceState *d)
 
218
{
 
219
    mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state,
 
220
                                           sysbus_from_qdev(d));
 
221
 
 
222
    s->playback_mode = 0;
 
223
    s->status = 0;
 
224
    s->irq_enable = 0;
 
225
    s->clock_div = 0;
 
226
    s->threshold = 0;
 
227
    s->phys_buf = 0;
 
228
}
 
229
 
 
230
static CPUReadMemoryFunc * const mv88w8618_audio_readfn[] = {
 
231
    mv88w8618_audio_read,
 
232
    mv88w8618_audio_read,
 
233
    mv88w8618_audio_read
 
234
};
 
235
 
 
236
static CPUWriteMemoryFunc * const mv88w8618_audio_writefn[] = {
 
237
    mv88w8618_audio_write,
 
238
    mv88w8618_audio_write,
 
239
    mv88w8618_audio_write
 
240
};
 
241
 
 
242
static int mv88w8618_audio_init(SysBusDevice *dev)
 
243
{
 
244
    mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev);
 
245
    int iomemtype;
 
246
 
 
247
    sysbus_init_irq(dev, &s->irq);
 
248
 
 
249
    wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
 
250
 
 
251
    iomemtype = cpu_register_io_memory(mv88w8618_audio_readfn,
 
252
                                       mv88w8618_audio_writefn, s,
 
253
                                       DEVICE_NATIVE_ENDIAN);
 
254
    sysbus_init_mmio(dev, MP_AUDIO_SIZE, iomemtype);
 
255
 
 
256
    return 0;
 
257
}
 
258
 
 
259
static const VMStateDescription mv88w8618_audio_vmsd = {
 
260
    .name = "mv88w8618_audio",
 
261
    .version_id = 1,
 
262
    .minimum_version_id = 1,
 
263
    .minimum_version_id_old = 1,
 
264
    .fields = (VMStateField[]) {
 
265
        VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
 
266
        VMSTATE_UINT32(status, mv88w8618_audio_state),
 
267
        VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
 
268
        VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
 
269
        VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
 
270
        VMSTATE_UINT32(threshold, mv88w8618_audio_state),
 
271
        VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
 
272
        VMSTATE_UINT32(last_free, mv88w8618_audio_state),
 
273
        VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
 
274
        VMSTATE_END_OF_LIST()
 
275
    }
 
276
};
 
277
 
 
278
static SysBusDeviceInfo mv88w8618_audio_info = {
 
279
    .init = mv88w8618_audio_init,
 
280
    .qdev.name  = "mv88w8618_audio",
 
281
    .qdev.size  = sizeof(mv88w8618_audio_state),
 
282
    .qdev.reset = mv88w8618_audio_reset,
 
283
    .qdev.vmsd  = &mv88w8618_audio_vmsd,
 
284
    .qdev.props = (Property[]) {
 
285
        {
 
286
            .name   = "wm8750",
 
287
            .info   = &qdev_prop_ptr,
 
288
            .offset = offsetof(mv88w8618_audio_state, wm),
 
289
        },
 
290
        {/* end of list */}
 
291
    }
 
292
};
 
293
 
 
294
static void mv88w8618_register_devices(void)
 
295
{
 
296
    sysbus_register_withprop(&mv88w8618_audio_info);
 
297
}
 
298
 
 
299
device_init(mv88w8618_register_devices)