Statistics
| Branch: | Revision:

root / hw / audio / marvell_88w8618.c @ 831aab9b

History | View | Annotate | Download (8.3 kB)

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 "audio/audio.h"
16

    
17
#define MP_AUDIO_SIZE           0x00001000
18

    
19
/* Audio register offsets */
20
#define MP_AUDIO_PLAYBACK_MODE  0x00
21
#define MP_AUDIO_CLOCK_DIV      0x18
22
#define MP_AUDIO_IRQ_STATUS     0x20
23
#define MP_AUDIO_IRQ_ENABLE     0x24
24
#define MP_AUDIO_TX_START_LO    0x28
25
#define MP_AUDIO_TX_THRESHOLD   0x2C
26
#define MP_AUDIO_TX_STATUS      0x38
27
#define MP_AUDIO_TX_START_HI    0x40
28

    
29
/* Status register and IRQ enable bits */
30
#define MP_AUDIO_TX_HALF        (1 << 6)
31
#define MP_AUDIO_TX_FULL        (1 << 7)
32

    
33
/* Playback mode bits */
34
#define MP_AUDIO_16BIT_SAMPLE   (1 << 0)
35
#define MP_AUDIO_PLAYBACK_EN    (1 << 7)
36
#define MP_AUDIO_CLOCK_24MHZ    (1 << 9)
37
#define MP_AUDIO_MONO           (1 << 14)
38

    
39
#define TYPE_MV88W8618_AUDIO "mv88w8618_audio"
40
#define MV88W8618_AUDIO(obj) \
41
    OBJECT_CHECK(mv88w8618_audio_state, (obj), TYPE_MV88W8618_AUDIO)
42

    
43
typedef struct mv88w8618_audio_state {
44
    SysBusDevice parent_obj;
45

    
46
    MemoryRegion iomem;
47
    qemu_irq irq;
48
    uint32_t playback_mode;
49
    uint32_t status;
50
    uint32_t irq_enable;
51
    uint32_t phys_buf;
52
    uint32_t target_buffer;
53
    uint32_t threshold;
54
    uint32_t play_pos;
55
    uint32_t last_free;
56
    uint32_t clock_div;
57
    void *wm;
58
} mv88w8618_audio_state;
59

    
60
static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
61
{
62
    mv88w8618_audio_state *s = opaque;
63
    int16_t *codec_buffer;
64
    int8_t buf[4096];
65
    int8_t *mem_buffer;
66
    int pos, block_size;
67

    
68
    if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
69
        return;
70
    }
71
    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
72
        free_out <<= 1;
73
    }
74
    if (!(s->playback_mode & MP_AUDIO_MONO)) {
75
        free_out <<= 1;
76
    }
77
    block_size = s->threshold / 2;
78
    if (free_out - s->last_free < block_size) {
79
        return;
80
    }
81
    if (block_size > 4096) {
82
        return;
83
    }
84
    cpu_physical_memory_read(s->target_buffer + s->play_pos, buf, block_size);
85
    mem_buffer = buf;
86
    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
87
        if (s->playback_mode & MP_AUDIO_MONO) {
88
            codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
89
            for (pos = 0; pos < block_size; pos += 2) {
90
                *codec_buffer++ = *(int16_t *)mem_buffer;
91
                *codec_buffer++ = *(int16_t *)mem_buffer;
92
                mem_buffer += 2;
93
            }
94
        } else {
95
            memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
96
                   (uint32_t *)mem_buffer, block_size);
97
        }
98
    } else {
99
        if (s->playback_mode & MP_AUDIO_MONO) {
100
            codec_buffer = wm8750_dac_buffer(s->wm, block_size);
101
            for (pos = 0; pos < block_size; pos++) {
102
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
103
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
104
            }
105
        } else {
106
            codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
107
            for (pos = 0; pos < block_size; pos += 2) {
108
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
109
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
110
            }
111
        }
112
    }
113
    wm8750_dac_commit(s->wm);
114

    
115
    s->last_free = free_out - block_size;
116

    
117
    if (s->play_pos == 0) {
118
        s->status |= MP_AUDIO_TX_HALF;
119
        s->play_pos = block_size;
120
    } else {
121
        s->status |= MP_AUDIO_TX_FULL;
122
        s->play_pos = 0;
123
    }
124

    
125
    if (s->status & s->irq_enable) {
126
        qemu_irq_raise(s->irq);
127
    }
128
}
129

    
130
static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
131
{
132
    int rate;
133

    
134
    if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
135
        rate = 24576000 / 64; /* 24.576MHz */
136
    } else {
137
        rate = 11289600 / 64; /* 11.2896MHz */
138
    }
139
    rate /= ((s->clock_div >> 8) & 0xff) + 1;
140

    
141
    wm8750_set_bclk_in(s->wm, rate);
142
}
143

    
144
static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
145
                                    unsigned size)
146
{
147
    mv88w8618_audio_state *s = opaque;
148

    
149
    switch (offset) {
150
    case MP_AUDIO_PLAYBACK_MODE:
151
        return s->playback_mode;
152

    
153
    case MP_AUDIO_CLOCK_DIV:
154
        return s->clock_div;
155

    
156
    case MP_AUDIO_IRQ_STATUS:
157
        return s->status;
158

    
159
    case MP_AUDIO_IRQ_ENABLE:
160
        return s->irq_enable;
161

    
162
    case MP_AUDIO_TX_STATUS:
163
        return s->play_pos >> 2;
164

    
165
    default:
166
        return 0;
167
    }
168
}
169

    
170
static void mv88w8618_audio_write(void *opaque, hwaddr offset,
171
                                  uint64_t value, unsigned size)
172
{
173
    mv88w8618_audio_state *s = opaque;
174

    
175
    switch (offset) {
176
    case MP_AUDIO_PLAYBACK_MODE:
177
        if (value & MP_AUDIO_PLAYBACK_EN &&
178
            !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
179
            s->status = 0;
180
            s->last_free = 0;
181
            s->play_pos = 0;
182
        }
183
        s->playback_mode = value;
184
        mv88w8618_audio_clock_update(s);
185
        break;
186

    
187
    case MP_AUDIO_CLOCK_DIV:
188
        s->clock_div = value;
189
        s->last_free = 0;
190
        s->play_pos = 0;
191
        mv88w8618_audio_clock_update(s);
192
        break;
193

    
194
    case MP_AUDIO_IRQ_STATUS:
195
        s->status &= ~value;
196
        break;
197

    
198
    case MP_AUDIO_IRQ_ENABLE:
199
        s->irq_enable = value;
200
        if (s->status & s->irq_enable) {
201
            qemu_irq_raise(s->irq);
202
        }
203
        break;
204

    
205
    case MP_AUDIO_TX_START_LO:
206
        s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
207
        s->target_buffer = s->phys_buf;
208
        s->play_pos = 0;
209
        s->last_free = 0;
210
        break;
211

    
212
    case MP_AUDIO_TX_THRESHOLD:
213
        s->threshold = (value + 1) * 4;
214
        break;
215

    
216
    case MP_AUDIO_TX_START_HI:
217
        s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
218
        s->target_buffer = s->phys_buf;
219
        s->play_pos = 0;
220
        s->last_free = 0;
221
        break;
222
    }
223
}
224

    
225
static void mv88w8618_audio_reset(DeviceState *d)
226
{
227
    mv88w8618_audio_state *s = MV88W8618_AUDIO(d);
228

    
229
    s->playback_mode = 0;
230
    s->status = 0;
231
    s->irq_enable = 0;
232
    s->clock_div = 0;
233
    s->threshold = 0;
234
    s->phys_buf = 0;
235
}
236

    
237
static const MemoryRegionOps mv88w8618_audio_ops = {
238
    .read = mv88w8618_audio_read,
239
    .write = mv88w8618_audio_write,
240
    .endianness = DEVICE_NATIVE_ENDIAN,
241
};
242

    
243
static int mv88w8618_audio_init(SysBusDevice *dev)
244
{
245
    mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
246

    
247
    sysbus_init_irq(dev, &s->irq);
248

    
249
    wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
250

    
251
    memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_audio_ops, s,
252
                          "audio", MP_AUDIO_SIZE);
253
    sysbus_init_mmio(dev, &s->iomem);
254

    
255
    return 0;
256
}
257

    
258
static const VMStateDescription mv88w8618_audio_vmsd = {
259
    .name = "mv88w8618_audio",
260
    .version_id = 1,
261
    .minimum_version_id = 1,
262
    .minimum_version_id_old = 1,
263
    .fields = (VMStateField[]) {
264
        VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
265
        VMSTATE_UINT32(status, mv88w8618_audio_state),
266
        VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
267
        VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
268
        VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
269
        VMSTATE_UINT32(threshold, mv88w8618_audio_state),
270
        VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
271
        VMSTATE_UINT32(last_free, mv88w8618_audio_state),
272
        VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
273
        VMSTATE_END_OF_LIST()
274
    }
275
};
276

    
277
static Property mv88w8618_audio_properties[] = {
278
    DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state, wm),
279
    {/* end of list */},
280
};
281

    
282
static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
283
{
284
    DeviceClass *dc = DEVICE_CLASS(klass);
285
    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
286

    
287
    k->init = mv88w8618_audio_init;
288
    dc->reset = mv88w8618_audio_reset;
289
    dc->vmsd = &mv88w8618_audio_vmsd;
290
    dc->props = mv88w8618_audio_properties;
291
}
292

    
293
static const TypeInfo mv88w8618_audio_info = {
294
    .name          = TYPE_MV88W8618_AUDIO,
295
    .parent        = TYPE_SYS_BUS_DEVICE,
296
    .instance_size = sizeof(mv88w8618_audio_state),
297
    .class_init    = mv88w8618_audio_class_init,
298
};
299

    
300
static void mv88w8618_register_types(void)
301
{
302
    type_register_static(&mv88w8618_audio_info);
303
}
304

    
305
type_init(mv88w8618_register_types)