Statistics
| Branch: | Revision:

root / hw / audio / marvell_88w8618.c @ 997aba8e

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
typedef struct mv88w8618_audio_state {
40
    SysBusDevice busdev;
41
    MemoryRegion iomem;
42
    qemu_irq irq;
43
    uint32_t playback_mode;
44
    uint32_t status;
45
    uint32_t irq_enable;
46
    uint32_t phys_buf;
47
    uint32_t target_buffer;
48
    uint32_t threshold;
49
    uint32_t play_pos;
50
    uint32_t last_free;
51
    uint32_t clock_div;
52
    void *wm;
53
} mv88w8618_audio_state;
54

    
55
static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
56
{
57
    mv88w8618_audio_state *s = opaque;
58
    int16_t *codec_buffer;
59
    int8_t buf[4096];
60
    int8_t *mem_buffer;
61
    int pos, block_size;
62

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

    
110
    s->last_free = free_out - block_size;
111

    
112
    if (s->play_pos == 0) {
113
        s->status |= MP_AUDIO_TX_HALF;
114
        s->play_pos = block_size;
115
    } else {
116
        s->status |= MP_AUDIO_TX_FULL;
117
        s->play_pos = 0;
118
    }
119

    
120
    if (s->status & s->irq_enable) {
121
        qemu_irq_raise(s->irq);
122
    }
123
}
124

    
125
static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
126
{
127
    int rate;
128

    
129
    if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
130
        rate = 24576000 / 64; /* 24.576MHz */
131
    } else {
132
        rate = 11289600 / 64; /* 11.2896MHz */
133
    }
134
    rate /= ((s->clock_div >> 8) & 0xff) + 1;
135

    
136
    wm8750_set_bclk_in(s->wm, rate);
137
}
138

    
139
static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
140
                                    unsigned size)
141
{
142
    mv88w8618_audio_state *s = opaque;
143

    
144
    switch (offset) {
145
    case MP_AUDIO_PLAYBACK_MODE:
146
        return s->playback_mode;
147

    
148
    case MP_AUDIO_CLOCK_DIV:
149
        return s->clock_div;
150

    
151
    case MP_AUDIO_IRQ_STATUS:
152
        return s->status;
153

    
154
    case MP_AUDIO_IRQ_ENABLE:
155
        return s->irq_enable;
156

    
157
    case MP_AUDIO_TX_STATUS:
158
        return s->play_pos >> 2;
159

    
160
    default:
161
        return 0;
162
    }
163
}
164

    
165
static void mv88w8618_audio_write(void *opaque, hwaddr offset,
166
                                  uint64_t value, unsigned size)
167
{
168
    mv88w8618_audio_state *s = opaque;
169

    
170
    switch (offset) {
171
    case MP_AUDIO_PLAYBACK_MODE:
172
        if (value & MP_AUDIO_PLAYBACK_EN &&
173
            !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
174
            s->status = 0;
175
            s->last_free = 0;
176
            s->play_pos = 0;
177
        }
178
        s->playback_mode = value;
179
        mv88w8618_audio_clock_update(s);
180
        break;
181

    
182
    case MP_AUDIO_CLOCK_DIV:
183
        s->clock_div = value;
184
        s->last_free = 0;
185
        s->play_pos = 0;
186
        mv88w8618_audio_clock_update(s);
187
        break;
188

    
189
    case MP_AUDIO_IRQ_STATUS:
190
        s->status &= ~value;
191
        break;
192

    
193
    case MP_AUDIO_IRQ_ENABLE:
194
        s->irq_enable = value;
195
        if (s->status & s->irq_enable) {
196
            qemu_irq_raise(s->irq);
197
        }
198
        break;
199

    
200
    case MP_AUDIO_TX_START_LO:
201
        s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
202
        s->target_buffer = s->phys_buf;
203
        s->play_pos = 0;
204
        s->last_free = 0;
205
        break;
206

    
207
    case MP_AUDIO_TX_THRESHOLD:
208
        s->threshold = (value + 1) * 4;
209
        break;
210

    
211
    case MP_AUDIO_TX_START_HI:
212
        s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
213
        s->target_buffer = s->phys_buf;
214
        s->play_pos = 0;
215
        s->last_free = 0;
216
        break;
217
    }
218
}
219

    
220
static void mv88w8618_audio_reset(DeviceState *d)
221
{
222
    mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state,
223
                                           SYS_BUS_DEVICE(d));
224

    
225
    s->playback_mode = 0;
226
    s->status = 0;
227
    s->irq_enable = 0;
228
    s->clock_div = 0;
229
    s->threshold = 0;
230
    s->phys_buf = 0;
231
}
232

    
233
static const MemoryRegionOps mv88w8618_audio_ops = {
234
    .read = mv88w8618_audio_read,
235
    .write = mv88w8618_audio_write,
236
    .endianness = DEVICE_NATIVE_ENDIAN,
237
};
238

    
239
static int mv88w8618_audio_init(SysBusDevice *dev)
240
{
241
    mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev);
242

    
243
    sysbus_init_irq(dev, &s->irq);
244

    
245
    wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
246

    
247
    memory_region_init_io(&s->iomem, &mv88w8618_audio_ops, s,
248
                          "audio", MP_AUDIO_SIZE);
249
    sysbus_init_mmio(dev, &s->iomem);
250

    
251
    return 0;
252
}
253

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

    
273
static Property mv88w8618_audio_properties[] = {
274
    DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state, wm),
275
    {/* end of list */},
276
};
277

    
278
static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
279
{
280
    DeviceClass *dc = DEVICE_CLASS(klass);
281
    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
282

    
283
    k->init = mv88w8618_audio_init;
284
    dc->reset = mv88w8618_audio_reset;
285
    dc->vmsd = &mv88w8618_audio_vmsd;
286
    dc->props = mv88w8618_audio_properties;
287
}
288

    
289
static const TypeInfo mv88w8618_audio_info = {
290
    .name          = "mv88w8618_audio",
291
    .parent        = TYPE_SYS_BUS_DEVICE,
292
    .instance_size = sizeof(mv88w8618_audio_state),
293
    .class_init    = mv88w8618_audio_class_init,
294
};
295

    
296
static void mv88w8618_register_types(void)
297
{
298
    type_register_static(&mv88w8618_audio_info);
299
}
300

    
301
type_init(mv88w8618_register_types)